refactor: E2E 시나리오 전면 개선 (43파일)

- Phase 0: 미구현 모듈 시나리오 13개 삭제 (구매관리, 중복, 라우트 없음)
- Phase 2: Settings URL 불일치 수정 (position, attendance, vacation-policy, bank-account, account, notification)
- Phase 3-4: 비설정 시나리오 URL/메뉴/UI 수정 (inventory-status, receiving-management, price-management, customer-inquiry, shipment-management, sales-client, quality-certification, customer-notice, production-* 등)
- Phase 5-6: 복잡 시나리오 재작성 (draft-box 50→14스텝, department-add 18→10스텝, free-board 70→22스텝, crud-delete-freeboard 14→17스텝)
- 16개 disabled 시나리오 enabled 전환
- 비표준 액션(fillInModal, randomData, usePlaywrightNative 등) → step-executor 표준 액션으로 통일
This commit is contained in:
김보곤
2026-02-06 17:37:35 +09:00
parent b2509ee2dc
commit 3bc23c5884
43 changed files with 696 additions and 8094 deletions

View File

@@ -1,527 +1,223 @@
{
"enabled": true,
"id": "crud-delete-freeboard",
"name": "자유게시판 CRUD 삭제 기능 테스트",
"name": "자유게시판 CRUD 삭제 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": [
"error",
"fail",
"timeout",
"404",
"500",
"blocked"
]
"captureOn": ["error", "fail", "timeout", "404", "500", "blocked"]
},
"description": "자유게시판에서 생성 → 수정 → 삭제 전체 CRUD 흐름 테스트. 테스트용 게시글을 생성하고, 수정한 후, 삭제하여 기존 데이터에 영향 없이 삭제 기능을 검증",
"description": "자유게시판에서 생성 → 수정 → 삭제 전체 CRUD 흐름 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"menuNavigation": {
"level1": "게시판",
"level2": "자유게시판",
"expectedUrl": "/ko/boards/free"
"expectedUrl": "/boards/free",
"searchWithinParent": true,
"closeOtherMenus": true
},
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"testPolicy": {
"deleteAllowed": true,
"deleteCondition": "CRUD 흐름 내에서만 허용 (생성 → 수정 → 삭제)",
"protectExistingData": true,
"description": "이 시나리오에서 생성한 테스트 데이터만 삭제"
},
"modalHandling": {
"description": "모달 창 처리 규칙",
"closeMethods": [
{
"priority": 1,
"method": "완료 버튼",
"selector": "button:has-text('확인'), button:has-text('등록'), button:has-text('저장')"
},
{
"priority": 2,
"method": "취소 버튼",
"selector": "button:has-text('취소'), [class*='cancel']"
},
{
"priority": 3,
"method": "X 버튼",
"selector": "button[class*='close'], [aria-label='닫기'], [aria-label='Close']"
},
{
"priority": 4,
"method": "ESC 키",
"action": "press_key",
"value": "Escape"
},
{
"priority": 5,
"method": "외부 클릭",
"selector": "[class*='backdrop'], [class*='overlay']"
}
],
"deleteDialogSelector": "[role='alertdialog'] button:has-text('삭제')",
"note": "삭제 확인 다이얼로그는 Playwright 네이티브 셀렉터 사용 필수 (JavaScript click 미동작)",
"rule": "모달이 열린 상태로 다음 단계 진행 금지"
},
"testData": {
"newPost": {
"title": "E2E테스트_삭제용_",
"content": "이 게시글은 E2E 테스트용으로 생성되었습니다. 테스트 완료 후 자동 삭제됩니다."
"create": {
"title": "E2E테스트_삭제용_{timestamp}",
"content": "이 게시글은 E2E 테스트용으로 생성되었습니다."
},
"updateData": {
"title": "E2E테스트_수정완료_"
},
"uniqueIdentifier": "timestamp를 붙여서 고유성 보장 (MMDDHHmmss)"
"update": {
"title": "E2E테스트_수정완료_{timestamp}"
}
},
"steps": [
{
"id": "step-0",
"name": "사이드바 메뉴 탐색",
"description": "게시판 > 자유게시판 메뉴로 이동",
"actions": [
{
"type": "scroll",
"target": "sidebar",
"direction": "top",
"description": "사이드바 상단으로 스크롤"
},
{
"type": "wait",
"duration": 300
},
{
"type": "click_if_exists",
"target": "게시판",
"description": "1차 메뉴 클릭"
},
{
"type": "wait",
"duration": 500
},
{
"type": "click_if_exists",
"target": "자유게시판",
"description": "2차 메뉴 클릭"
},
{
"type": "wait",
"duration": 2000
}
],
"expect": {
"url": "/ko/boards/free",
"no404": true
"id": 1,
"name": "메뉴 진입: 게시판 > 자유게시판",
"action": "menu_navigate",
"level1": "게시판",
"level2": "자유게시판",
"expected": {
"url_contains": "/boards/free",
"visible": ["자유게시판"]
}
},
{
"id": "step-1",
"id": 2,
"phase": "CREATE",
"name": "[CREATE] 초기 상태 확인",
"description": "등록 전 게시글 수 확인",
"actions": [
{
"type": "capture",
"variable": "initialRowCount",
"selector": "table tbody tr",
"extract": "count",
"description": "등록 전 행 수 저장"
},
{
"type": "wait",
"duration": 500
}
],
"expect": {
"tableExists": true
}
"name": "[CREATE] 글쓰기 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('글쓰기'), button:has-text('등록'), button:has-text('작성')"
},
{
"id": "step-2",
"id": 3,
"phase": "CREATE",
"name": "[CREATE] 등록 버튼 클릭",
"description": "새 게시글을 등록하기 위해 등록 버튼 클릭",
"actions": [
{
"type": "click_if_exists",
"target": "button:has-text('등록')",
"description": "등록 버튼 클릭"
},
{
"type": "wait",
"duration": 1500
}
],
"expect": {
"url": "/ko/boards/free/new",
"pageTitle": "게시글 작성"
}
"name": "[CREATE] 작성 페이지 대기",
"action": "wait",
"duration": 2000
},
{
"id": "step-3",
"id": 4,
"phase": "CREATE",
"name": "[CREATE] 게시글 정보 입력",
"description": "테스트용 게시글 정보 입력 (타임스탬프로 고유성 보장)",
"actions": [
{
"type": "generateTimestamp",
"variable": "testTimestamp",
"format": "MMDDHHmmss"
},
{
"type": "fill",
"target": "input[name='title'], input[placeholder*='제목']",
"value": "E2E테스트_삭제용_{testTimestamp}",
"description": "고유한 제목 입력"
},
{
"type": "click_if_exists",
"target": "textarea, [class*='editor'], [contenteditable='true']",
"value": "E2E 테스트용 게시글입니다. 자동 삭제 예정.",
"description": "본문 입력"
}
],
"note": "타임스탬프를 사용하여 매 테스트마다 고유한 데이터 생성"
"name": "[CREATE] 제목 입력",
"action": "fill",
"target": "input#title, input[name='title'], input[placeholder*='제목']",
"value": "E2E테스트_삭제용_{timestamp}",
"clear": true
},
{
"id": "step-4",
"id": 5,
"phase": "CREATE",
"name": "[CREATE] 등록 실행",
"description": "입력된 정보로 게시글 등록 실행",
"actions": [
{
"type": "click_if_exists",
"target": "button:has-text('등록')",
"description": "등록 버튼 클릭"
},
{
"type": "wait",
"duration": 2000
}
],
"expect": {
"toast": "등록|완료|성공",
"redirect": "/ko/boards/free"
"name": "[CREATE] 내용 입력",
"action": "fill",
"target": "textarea#content, textarea[name='content'], [contenteditable='true']",
"value": "이 게시글은 E2E 테스트용으로 생성되었습니다.",
"clear": true
},
{
"id": 6,
"phase": "CREATE",
"name": "[CREATE] 필수 검증 #2: 게시글 등록",
"action": "click_if_exists",
"target": "button:has-text('등록'), button[type='submit']",
"verify": {
"no_error_page": true,
"toast": "등록|완료|성공"
},
"verification": {
"level": 4,
"checks": [
"성공 토스트 표시",
"목록 페이지로 이동"
]
}
"expected": "게시글 등록 완료"
},
{
"id": "step-5",
"id": 7,
"phase": "CREATE",
"name": "[CREATE] 등록 후 대기",
"action": "wait",
"duration": 2000
},
{
"id": 8,
"phase": "CREATE",
"name": "[CREATE] 등록 결과 확인",
"description": "테이블에 새로 등록한 게시글이 표시되는지 확인",
"actions": [
{
"type": "wait",
"duration": 1000
},
{
"type": "capture",
"variable": "afterCreateCount",
"selector": "table tbody tr",
"extract": "count"
},
{
"type": "verify",
"condition": "afterCreateCount > initialRowCount",
"description": "행 수 증가 확인"
}
"action": "verify_detail",
"checks": [
"E2E테스트_삭제용 제목 표시"
],
"expect": {
"rowCountIncreased": true,
"rowContains": "E2E테스트_삭제용"
},
"verification": {
"level": 4,
"description": "생성된 데이터가 목록에 표시되어야 함"
}
"expected": "게시글 등록 확인"
},
{
"id": "step-6",
"phase": "UPDATE",
"name": "[UPDATE] 생성된 게시글 상세 페이지 진입",
"description": "생성한 테스트 게시글의 상세 페이지로 이동",
"actions": [
{
"type": "click_if_exists",
"target": "table tbody tr:first-child td:nth-child(2)",
"description": "첫 번째 행 (방금 생성한 게시글) 클릭"
},
{
"type": "wait",
"duration": 2000
}
],
"expect": {
"url": "/ko/boards/free/",
"pageContains": "E2E테스트_삭제용"
}
},
{
"id": "step-7",
"id": 9,
"phase": "UPDATE",
"name": "[UPDATE] 수정 버튼 클릭",
"description": "수정 버튼을 클릭하여 편집 모드로 전환",
"actions": [
{
"type": "click_if_exists",
"target": "button:has-text('수정')",
"description": "수정 버튼 클릭"
},
{
"type": "wait",
"duration": 1500
}
],
"expect": {
"url": "/edit",
"fieldsEditable": true
}
"action": "click_if_exists",
"target": "button:has-text('수정')"
},
{
"id": "step-8",
"id": 10,
"phase": "UPDATE",
"name": "[UPDATE] 수정 페이지 대기",
"action": "wait",
"duration": 1500
},
{
"id": 11,
"phase": "UPDATE",
"name": "[UPDATE] 제목 수정",
"description": "게시글 제목을 수정하여 UPDATE 동작 확인",
"actions": [
{
"type": "clear",
"target": "input[name='title'], input[placeholder*='제목']",
"description": "기존 제목 삭제"
},
{
"type": "fill",
"target": "input[name='title'], input[placeholder*='제목']",
"value": "E2E테스트_수정완료_{testTimestamp}",
"description": "수정된 제목 입력"
}
]
"action": "fill",
"target": "input#title, input[name='title'], input[placeholder*='제목']",
"value": "E2E테스트_수정완료_{timestamp}",
"clear": true
},
{
"id": "step-9",
"id": 12,
"phase": "UPDATE",
"name": "[UPDATE] 수정 저장",
"description": "수정된 내용 저장",
"actions": [
{
"type": "click_if_exists",
"target": "button:has-text('수정')",
"description": "수정 버튼 클릭"
},
{
"type": "wait",
"duration": 2000
}
],
"expect": {
"toast": "수정|완료|성공",
"redirect": "/ko/boards/free/"
"action": "click_if_exists",
"target": "button:has-text('저장'), button:has-text('수정'), button[type='submit']",
"verify": {
"toast": "수정|저장|완료|성공"
},
"verification": {
"level": 4,
"description": "수정 성공 토스트 확인"
}
"expected": "수정 완료"
},
{
"id": "step-10",
"id": 13,
"phase": "UPDATE",
"name": "[UPDATE] 수정 결과 확인",
"description": "수정된 내용이 반영되었는지 확인",
"actions": [
{
"type": "wait",
"duration": 1000
},
{
"type": "verify",
"target": "page",
"contains": "E2E테스트_수정완료",
"description": "수정된 제목 표시 확인"
}
],
"expect": {
"contains": "E2E테스트_수정완료"
}
"name": "[UPDATE] 수정 후 대기",
"action": "wait",
"duration": 2000
},
{
"id": "step-11",
"id": 14,
"phase": "DELETE",
"name": "[DELETE] 삭제 버튼 클릭",
"description": "테스트용으로 생성한 게시글 삭제 시작",
"actions": [
{
"type": "click_if_exists",
"target": "button:has-text('삭제')",
"description": "삭제 버튼 클릭"
},
{
"type": "wait",
"duration": 500
}
],
"expect": {
"confirmDialog": true,
"dialogRole": "alertdialog"
},
"warning": "이 단계에서 삭제 확인 다이얼로그가 표시되어야 함"
},
{
"id": "step-12",
"phase": "DELETE",
"name": "[DELETE] 삭제 확인",
"description": "삭제 확인 다이얼로그에서 삭제 버튼 클릭",
"actions": [
{
"type": "click_if_exists",
"target": "[role='alertdialog'] button:has-text('삭제')",
"usePlaywrightNative": true,
"description": "삭제 확인 클릭 (Playwright 네이티브 셀렉터 필수)"
},
{
"type": "wait",
"duration": 2000
}
],
"expect": {
"toast": "삭제|완료|성공",
"redirect": "/ko/boards/free"
},
"verification": {
"level": 4,
"checks": [
"성공 토스트",
"목록 페이지로 리다이렉트"
]
},
"note": "JavaScript click()은 동작하지 않음. Playwright playwright_click 사용 필수"
},
{
"id": "step-13",
"phase": "VERIFY",
"name": "[VERIFY] 삭제 결과 확인",
"description": "삭제된 게시글이 목록에서 제거되었는지 확인",
"actions": [
{
"type": "wait",
"duration": 1000
},
{
"type": "verifyUrl",
"contains": "/ko/boards/free",
"description": "목록 페이지 확인"
},
{
"type": "capture",
"variable": "afterDeleteCount",
"selector": "table tbody tr",
"extract": "count"
}
],
"expect": {
"rowCountDecreased": true,
"condition": "afterDeleteCount === initialRowCount"
},
"verification": {
"level": 4,
"description": "행 수가 원래대로 복구되어야 함"
"action": "click_if_exists",
"target": "button:has-text('삭제')",
"expected": {
"confirm_dialog": true
}
},
{
"id": "step-14",
"phase": "VERIFY",
"name": "[VERIFY] 최종 검증",
"description": "테스트 데이터가 완전히 삭제되었는지 확인",
"actions": [
{
"type": "verify",
"target": "table",
"notContains": "E2E테스트_수정완료",
"description": "삭제된 게시글이 목록에 없음 확인"
}
],
"expect": {
"testDataDeleted": true,
"noTestDataInList": true
"id": 15,
"phase": "DELETE",
"name": "[DELETE] 필수 검증 #6: 삭제 확인",
"action": "click_and_confirm",
"target": "button:has-text('확인'), button:has-text('삭제'), [role='alertdialog'] button:has-text('삭제')",
"verify": {
"toast": "삭제|완료|성공",
"redirect": "/boards/free"
},
"verification": {
"level": 4,
"description": "테스트 데이터가 완전히 삭제되어 목록에 표시되지 않아야 함"
"expected": "삭제 완료 및 목록 복귀"
},
{
"id": 16,
"phase": "DELETE",
"name": "[DELETE] 삭제 후 대기",
"action": "wait",
"duration": 2000
},
{
"id": 17,
"phase": "DELETE",
"name": "[DELETE] 삭제 결과 확인",
"action": "verify_detail",
"search": "E2E테스트_수정완료",
"expected": {
"row_exists": false,
"message": "테스트 게시글이 목록에서 제거됨"
}
}
],
"expectedAPIs": [
{
"phase": "CREATE",
"method": "POST",
"endpoint": "/api/v1/free-board",
"endpoint": "/api/v1/boards/free/posts",
"description": "게시글 등록"
},
{
"phase": "UPDATE",
"method": "PUT",
"endpoint": "/api/v1/free-board/{id}",
"endpoint": "/api/v1/boards/free/posts/{id}",
"description": "게시글 수정"
},
{
"phase": "DELETE",
"method": "DELETE",
"endpoint": "/api/v1/free-board/{id}",
"endpoint": "/api/v1/boards/free/posts/{id}",
"description": "게시글 삭제"
}
],
"requiredVerifications": [
{
"id": 1,
"name": "CREATE - 등록 기능",
"steps": [
"step-2",
"step-3",
"step-4",
"step-5"
],
"criteria": "게시글 생성 + 목록에 표시"
},
{
"id": 2,
"name": "UPDATE - 수정 기능",
"steps": [
"step-7",
"step-8",
"step-9",
"step-10"
],
"criteria": "게시글 수정 + 변경 내용 반영"
"name": "등록/저장 버튼",
"steps": [6, 12],
"criteria": "API 호출 + 성공 토스트 + 데이터 반영"
},
{
"id": 3,
"name": "DELETE - 삭제 기능",
"steps": [
"step-11",
"step-12",
"step-13",
"step-14"
],
"criteria": "게시글 삭제 + 목록에서 제거"
}
],
"knownIssues": [
{
"issue": "삭제 확인 다이얼로그 JavaScript click 미동작",
"description": "JavaScript의 element.click() 또는 dispatchEvent로는 삭제가 실행되지 않음",
"workaround": "Playwright의 playwright_click 도구와 [role='alertdialog'] button:has-text('삭제') 셀렉터 사용",
"testedOn": "2026-01-29"
"id": 6,
"name": "삭제 기능",
"steps": [14, 15, 17],
"criteria": "DELETE API + 목록에서 제거"
}
],
"rollbackPlan": {
"description": "테스트 실패 시 롤백 계획",
"onCreateFail": "영향 없음 - 다음 테스트 정상 진행",
"onUpdateFail": "테스트 데이터 수동 삭제 필요",
"onDeleteFail": "테스트 데이터 수동 삭제 필요",
"cleanupRequired": "E2E테스트_ 로 시작하는 게시글은 테스트 데이터이므로 수동 삭제 가능"
"onCreateFail": "영향 없음",
"onUpdateFail": "테스트 게시글 수동 삭제 필요",
"onDeleteFail": "테스트 게시글 수동 삭제 필요",
"cleanupRequired": "E2E테스트_ 접두사 게시글은 테스트 데이터"
}
}

View File

@@ -1,666 +0,0 @@
{
"id": "crud-delete-vendor",
"name": "거래처 CRUD 삭제 기능 테스트",
"status": "BLOCKED",
"blockedReason": "거래처관리 페이지에 등록 버튼이 없음 - CREATE 단계 불가",
"discoveredOn": "2026-01-29",
"alternative": "crud-delete-freeboard.json 사용 (자유게시판 CRUD 테스트)",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": [
"error",
"fail",
"timeout",
"404",
"500",
"blocked"
]
},
"description": "거래처관리에서 생성 → 수정 → 삭제 전체 CRUD 흐름 테스트. 테스트용 데이터를 생성하고, 수정한 후, 삭제하여 기존 데이터에 영향 없이 삭제 기능을 검증",
"baseUrl": "https://dev.codebridge-x.com",
"menuNavigation": {
"level1": "회계관리",
"level2": "거래처관리",
"expectedUrl": "/ko/accounting/vendors"
},
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"testPolicy": {
"deleteAllowed": true,
"deleteCondition": "CRUD 흐름 내에서만 허용 (생성 → 수정 → 삭제)",
"protectExistingData": true,
"description": "이 시나리오에서 생성한 테스트 데이터만 삭제"
},
"modalHandling": {
"description": "모달 창 처리 규칙",
"closeMethods": [
{
"priority": 1,
"method": "완료 버튼",
"selector": "button:has-text('확인'), button:has-text('등록'), button:has-text('저장')"
},
{
"priority": 2,
"method": "취소 버튼",
"selector": "button:has-text('취소'), [class*='cancel']"
},
{
"priority": 3,
"method": "X 버튼",
"selector": "button[class*='close'], [aria-label='닫기'], [aria-label='Close']"
},
{
"priority": 4,
"method": "ESC 키",
"action": "press_key",
"value": "Escape"
},
{
"priority": 5,
"method": "외부 클릭",
"selector": "[class*='backdrop'], [class*='overlay']"
}
],
"rule": "모달이 열린 상태로 다음 단계 진행 금지"
},
"testData": {
"newVendor": {
"vendorName": "E2E테스트_삭제용_",
"businessNumber": "123-45-67890",
"representative": "테스트대표",
"vendorType": "매출",
"phone": "02-1234-5678",
"email": "test@e2etest.com",
"address": "서울시 테스트구 테스트동 123"
},
"updateData": {
"vendorName": "E2E테스트_수정완료_",
"representative": "수정대표"
},
"uniqueIdentifier": "timestamp를 붙여서 고유성 보장"
},
"steps": [
{
"id": "step-0",
"name": "사이드바 메뉴 탐색",
"description": "회계관리 > 거래처관리 메뉴로 이동",
"actions": [
{
"type": "scroll",
"target": "sidebar",
"direction": "top"
},
{
"type": "wait",
"duration": 300
},
{
"type": "click_if_exists",
"target": "회계관리",
"description": "1차 메뉴 클릭"
},
{
"type": "wait",
"duration": 500
},
{
"type": "click_if_exists",
"target": "거래처관리",
"description": "2차 메뉴 클릭"
},
{
"type": "wait",
"duration": 2000
}
],
"expect": {
"url": "/ko/accounting/vendors",
"no404": true
}
},
{
"id": "step-1",
"phase": "CREATE",
"name": "📝 [CREATE] 거래처 등록 버튼 클릭",
"description": "새 거래처를 등록하기 위해 등록 버튼 클릭",
"actions": [
{
"type": "capture",
"variable": "initialRowCount",
"selector": "table tbody tr",
"extract": "count",
"description": "등록 전 행 수 저장"
},
{
"type": "click_if_exists",
"target": "button:has-text('등록'), button:has-text('추가'), [class*='add'], [class*='register']",
"description": "등록 버튼 클릭"
},
{
"type": "wait",
"duration": 1000
}
],
"expect": {
"modal": true,
"modalTitle": "거래처 등록"
}
},
{
"id": "step-2",
"phase": "CREATE",
"name": "📝 [CREATE] 등록 모달 - 필수 정보 입력",
"description": "테스트용 거래처 정보 입력 (타임스탬프로 고유성 보장)",
"actions": [
{
"type": "generateTimestamp",
"variable": "testTimestamp",
"format": "MMDDHHmmss"
},
{
"type": "click_if_exists",
"target": "거래처명",
"description": "거래처명 입력 필드 확인"
},
{
"type": "click_if_exists",
"target": "사업자등록번호",
"description": "사업자번호 입력 필드 확인"
},
{
"type": "click_if_exists",
"target": "대표자명",
"description": "대표자명 입력 필드 확인"
},
{
"type": "click_if_exists",
"target": "거래처 유형",
"description": "거래처 유형 필드 확인"
},
{
"type": "click_if_exists",
"target": "전화번호",
"description": "전화번호 입력 필드 확인"
},
{
"type": "click_if_exists",
"target": "이메일",
"description": "이메일 입력 필드 확인"
}
],
"note": "타임스탬프를 사용하여 매 테스트마다 고유한 데이터 생성"
},
{
"id": "step-3",
"phase": "CREATE",
"name": "📝 [CREATE] 등록 모달 - 등록 버튼 클릭",
"description": "입력된 정보로 거래처 등록 실행",
"actions": [
{
"type": "click_if_exists",
"target": "button:has-text('등록'), button:has-text('저장')",
"description": "모달 내 등록 버튼 클릭"
},
{
"type": "wait",
"duration": 2000
}
],
"expect": {
"toast": "등록|완료|성공",
"modalClosed": true,
"apiCall": "POST /api/v1/clients"
},
"verification": {
"level": 4,
"checks": [
"API 호출 확인",
"성공 토스트 표시",
"모달 닫힘"
]
}
},
{
"id": "step-3-modal-close",
"phase": "CREATE",
"name": "📝 [CREATE] 모달 닫기 확인",
"description": "모달이 열려있으면 닫기 시도",
"condition": "modalStillOpen",
"actions": [
{
"type": "click_if_exists",
"target": "[role='dialog'] button[class*='close'], [aria-label='닫기'], [aria-label='Close']",
"description": "모달 닫기 시도"
},
{
"type": "wait",
"duration": 500
}
],
"expect": {
"modalClosed": true
}
},
{
"id": "step-4",
"phase": "CREATE",
"name": "📝 [CREATE] 등록 결과 확인",
"description": "테이블에 새로 등록한 거래처가 표시되는지 확인",
"actions": [
{
"type": "fill",
"target": "검색",
"value": "E2E테스트_삭제용",
"description": "생성한 거래처 검색"
},
{
"type": "pressKey",
"key": "Enter"
},
{
"type": "wait",
"duration": 1500
},
{
"type": "capture",
"variable": "createdVendorRow",
"selector": "table tbody tr",
"extract": "exists",
"description": "테스트 거래처 행 존재 여부 확인"
}
],
"expect": {
"rowExists": true,
"rowContains": "E2E테스트_삭제용"
},
"verification": {
"level": 4,
"description": "생성된 데이터가 목록에 표시되어야 함"
}
},
{
"id": "step-5",
"phase": "UPDATE",
"name": "✏️ [UPDATE] 생성된 거래처 상세 페이지 진입",
"description": "생성한 테스트 거래처의 상세 페이지로 이동",
"actions": [
{
"type": "click_if_exists",
"target": "table tbody tr",
"description": "첫 번째 거래처 행 클릭"
},
{
"type": "wait",
"duration": 2000
}
],
"expect": {
"url": "/ko/accounting/vendors/",
"pageTitle": "거래처 상세"
}
},
{
"id": "step-6",
"phase": "UPDATE",
"name": "✏️ [UPDATE] 수정 모드 진입",
"description": "수정 버튼을 클릭하여 편집 모드로 전환",
"actions": [
{
"type": "click_if_exists",
"target": "button:has-text('수정')",
"description": "수정 버튼 클릭"
},
{
"type": "wait",
"duration": 1000
}
],
"expect": {
"url": "mode=edit",
"fieldsEditable": true
}
},
{
"id": "step-7",
"phase": "UPDATE",
"name": "✏️ [UPDATE] 거래처명 수정",
"description": "거래처명을 수정하여 UPDATE 동작 확인",
"actions": [
{
"type": "clear",
"target": "거래처명",
"description": "기존 값 삭제"
},
{
"type": "fill",
"target": "거래처명",
"value": "E2E테스트_수정완료_{testTimestamp}",
"description": "수정된 거래처명 입력"
},
{
"type": "fill",
"target": "대표자명",
"value": "수정대표",
"description": "대표자명 수정"
}
]
},
{
"id": "step-8",
"phase": "UPDATE",
"name": "✏️ [UPDATE] 수정 저장",
"description": "수정된 내용 저장",
"actions": [
{
"type": "click_if_exists",
"target": "button:has-text('저장')",
"description": "저장 버튼 클릭"
},
{
"type": "wait",
"duration": 500
},
{
"type": "click_if_exists",
"target": "button:has-text('확인')",
"description": "저장 확인 다이얼로그"
},
{
"type": "wait",
"duration": 2000
}
],
"expect": {
"toast": "수정|완료|성공",
"apiCall": "PUT /api/v1/clients/"
},
"verification": {
"level": 4,
"description": "수정 API 호출 및 성공 토스트 확인"
}
},
{
"id": "step-8-modal-close",
"phase": "UPDATE",
"name": "✏️ [UPDATE] 다이얼로그 닫기 확인",
"description": "다이얼로그가 열려있으면 닫기",
"condition": "dialogStillOpen",
"actions": [
{
"type": "click_if_exists",
"target": "[role='dialog'] button[class*='close'], [aria-label='닫기'], [aria-label='Close']",
"description": "다이얼로그 닫기 시도"
}
]
},
{
"id": "step-9",
"phase": "UPDATE",
"name": "✏️ [UPDATE] 수정 결과 확인",
"description": "수정된 내용이 반영되었는지 확인",
"actions": [
{
"type": "wait",
"duration": 1000
},
{
"type": "capture",
"variable": "updatedVendorName",
"selector": "[class*='vendor-name'], h1, h2",
"extract": "text"
}
],
"expect": {
"contains": "E2E테스트_수정완료"
}
},
{
"id": "step-10",
"phase": "DELETE",
"name": "🗑️ [DELETE] 삭제 버튼 클릭",
"description": "테스트용으로 생성한 거래처 삭제 시작",
"actions": [
{
"type": "click_if_exists",
"target": "button:has-text('삭제')",
"description": "삭제 버튼 클릭"
},
{
"type": "wait",
"duration": 500
}
],
"expect": {
"confirmDialog": true,
"dialogMessage": "삭제|정말|확인"
},
"warning": "이 단계에서 삭제 확인 다이얼로그가 표시되어야 함"
},
{
"id": "step-11",
"phase": "DELETE",
"name": "🗑️ [DELETE] 삭제 확인 다이얼로그 검증",
"description": "삭제 확인 다이얼로그 UI 요소 확인",
"actions": [
{
"type": "verify",
"target": "dialog",
"checks": [
"삭제 확인 메시지",
"취소 버튼",
"확인/삭제 버튼"
]
}
],
"expect": {
"dialogElements": [
"취소",
"확인|삭제"
]
}
},
{
"id": "step-12",
"phase": "DELETE",
"name": "🗑️ [DELETE] 삭제 확인 버튼 클릭",
"description": "삭제를 최종 확인하여 실행",
"actions": [
{
"type": "click_if_exists",
"target": "button:has-text('확인'), button:has-text('삭제')",
"description": "삭제 확인 클릭"
},
{
"type": "wait",
"duration": 2000
}
],
"expect": {
"toast": "삭제|완료|성공",
"apiCall": "DELETE /api/v1/clients/",
"redirect": "/ko/accounting/vendors"
},
"verification": {
"level": 4,
"checks": [
"DELETE API 호출",
"성공 토스트",
"목록 페이지로 리다이렉트"
]
}
},
{
"id": "step-12-modal-close",
"phase": "DELETE",
"name": "🗑️ [DELETE] 다이얼로그 닫기 확인",
"description": "다이얼로그가 열려있으면 닫기",
"condition": "dialogStillOpen",
"actions": [
{
"type": "click_if_exists",
"target": "[role='dialog'] button[class*='close'], [aria-label='닫기'], [aria-label='Close']",
"description": "다이얼로그 닫기 시도"
}
]
},
{
"id": "step-13",
"phase": "VERIFY",
"name": "✅ [VERIFY] 삭제 결과 확인 - 목록 페이지",
"description": "삭제된 거래처가 목록에서 제거되었는지 확인",
"actions": [
{
"type": "wait",
"duration": 1000
},
{
"type": "verifyUrl",
"contains": "/ko/accounting/vendors",
"description": "목록 페이지 확인"
},
{
"type": "fill",
"target": "검색",
"value": "E2E테스트_수정완료",
"description": "삭제된 거래처 검색"
},
{
"type": "pressKey",
"key": "Enter"
},
{
"type": "wait",
"duration": 1500
}
],
"expect": {
"noResults": true,
"rowNotExists": "E2E테스트_수정완료"
}
},
{
"id": "step-14",
"phase": "VERIFY",
"name": "✅ [VERIFY] 최종 검증 - 데이터 삭제 확인",
"description": "검색 결과에서 테스트 데이터가 없음을 최종 확인",
"actions": [
{
"type": "capture",
"variable": "searchResultCount",
"selector": "table tbody tr",
"extract": "count"
},
{
"type": "verify",
"condition": "searchResultCount === 0 OR no row contains 'E2E테스트'"
}
],
"expect": {
"testDataDeleted": true,
"noTestDataInList": true
},
"verification": {
"level": 4,
"description": "테스트 데이터가 완전히 삭제되어 검색되지 않아야 함"
}
},
{
"id": "step-15",
"phase": "CLEANUP",
"name": "🧹 [CLEANUP] 검색 초기화",
"description": "테스트 종료 후 검색어 삭제하여 원래 상태로 복원",
"actions": [
{
"type": "clear",
"target": "검색"
},
{
"type": "pressKey",
"key": "Enter"
},
{
"type": "wait",
"duration": 1000
}
],
"expect": {
"originalListRestored": true
}
}
],
"expectedAPIs": [
{
"phase": "CREATE",
"method": "POST",
"endpoint": "/api/v1/clients",
"description": "거래처 등록"
},
{
"phase": "UPDATE",
"method": "PUT",
"endpoint": "/api/v1/clients/{id}",
"description": "거래처 수정"
},
{
"phase": "DELETE",
"method": "DELETE",
"endpoint": "/api/v1/clients/{id}",
"description": "거래처 삭제"
}
],
"requiredVerifications": [
{
"id": 1,
"name": "CREATE - 등록 기능",
"steps": [
"step-1",
"step-2",
"step-3",
"step-4"
],
"criteria": "POST API 호출 + 성공 토스트 + 목록에 데이터 표시"
},
{
"id": 2,
"name": "UPDATE - 수정 기능",
"steps": [
"step-6",
"step-7",
"step-8",
"step-9"
],
"criteria": "PUT API 호출 + 성공 토스트 + 데이터 변경 반영"
},
{
"id": 3,
"name": "DELETE - 삭제 기능",
"steps": [
"step-10",
"step-11",
"step-12",
"step-13",
"step-14"
],
"criteria": "DELETE API 호출 + 성공 토스트 + 목록에서 데이터 제거"
},
{
"id": 4,
"name": "모달/다이얼로그 닫기",
"steps": [
"step-3-modal-close",
"step-8-modal-close",
"step-12-modal-close"
],
"criteria": "모든 모달/다이얼로그가 정상적으로 닫혀야 함"
}
],
"rollbackPlan": {
"description": "테스트 실패 시 롤백 계획",
"onCreateFail": "모달 닫기 → 다음 테스트 영향 없음",
"onUpdateFail": "테스트 데이터 수동 삭제 필요 (DB 또는 UI)",
"onDeleteFail": "테스트 데이터 수동 삭제 필요",
"cleanupRequired": "E2E테스트_ 로 시작하는 거래처는 테스트 데이터이므로 수동 삭제 가능"
}
}

View File

@@ -10,7 +10,7 @@
"menuNavigation": {
"level1": "고객센터",
"level2": "1:1 문의",
"expectedUrl": "/customer-center/inquiry",
"expectedUrl": "/customer-center/qna",
"searchWithinParent": true,
"closeOtherMenus": true
},

View File

@@ -10,7 +10,7 @@
"menuNavigation": {
"level1": "고객센터",
"level2": "공지사항",
"expectedUrl": "/support/notice",
"expectedUrl": "/customer-center/notices",
"searchWithinParent": true,
"closeOtherMenus": true
},
@@ -26,7 +26,7 @@
"level1": "고객센터",
"level2": "공지사항",
"expected": {
"url_contains": "/support",
"url_contains": "/customer-center/notices",
"visible": ["공지사항"]
}
},
@@ -172,12 +172,12 @@
"expectedAPIs": [
{
"method": "GET",
"endpoint": "/api/v1/support/notices",
"endpoint": "/api/v1/customer-center/noticess",
"description": "공지사항 목록 조회"
},
{
"method": "GET",
"endpoint": "/api/v1/support/notices/:id",
"endpoint": "/api/v1/customer-center/noticess/:id",
"description": "공지사항 상세 조회"
}
],

View File

@@ -1,464 +1,183 @@
{
"enabled": true,
"id": "department-add",
"name": "부서 추가 테스트 (랜덤 + 하위부서)",
"name": "부서 추가 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": ["error", "fail", "timeout", "404", "500", "blocked"]
},
"description": "랜덤 상위 부서 생성 후 하위 부서까지 추가하는 고도화된 E2E 테스트",
"description": "인사관리 > 부서관리 메뉴에서 부서 추가/수정/삭제 CRUD 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"url": "/ko/hr/department-management",
"navigation": {
"targetUrl": "/hr/department-management",
"urlPattern": "/hr/department-management|/ko/hr/department-management",
"menuHints": ["부서관리", "부서 관리", "인사관리"]
},
"menuNavigation": {
"level1": "인사관리",
"level2": "부서관리",
"expectedUrl": "/ko/hr/department-management",
"expectedUrl": "/hr/department-management",
"searchWithinParent": true,
"closeOtherMenus": true
},
"menuNavigationEnhanced": {
"strategy": "scroll-and-search",
"scrollContainer": ".sidebar-scroll, [data-sidebar], nav, aside",
"scrollStep": 200,
"maxScrollAttempts": 10,
"waitAfterScroll": 300,
"level1": {
"text": "인사관리",
"fallbackSelectors": [
"text=인사관리",
"[data-menu='hr']",
"a:has-text('인사관리')",
"button:has-text('인사관리')"
]
},
"level2": {
"text": "부서관리",
"fallbackSelectors": [
"text=부서관리",
"[data-menu='department']",
"a:has-text('부서관리')",
"button:has-text('부서관리')"
]
},
"expectedUrl": "/ko/hr/department-management",
"fallbackUrl": "https://dev.codebridge-x.com/ko/hr/department-management"
},
"timeout": 90000,
"tags": ["hr", "department", "crud", "random", "hierarchy"],
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"randomData": {
"parentDepartment": {
"type": "composite",
"pattern": "{prefix}본부_{timestamp}",
"components": {
"prefix": {
"type": "random",
"options": ["신규", "테스트", "개발", "QA", "운영", "전략", "혁신", "글로벌"]
}
}
},
"childDepartment": {
"type": "composite",
"pattern": "{prefix}팀_{timestamp}",
"components": {
"prefix": {
"type": "random",
"options": ["기획", "개발", "디자인", "마케팅", "영업", "지원", "품질", "연구"]
}
}
"testData": {
"create": {
"departmentName": "E2E_TEST_본부_{timestamp}",
"childName": "E2E_TEST_팀_{timestamp}"
}
},
"steps": [
{
"id": "step-0",
"name": "사이드바 메뉴 전체 펼치기",
"description": "모두 펼치기 버튼을 클릭하여 전체 메뉴를 펼친 후 메뉴 탐색 준비",
"actions": [
{
"type": "evaluate",
"script": "document.querySelector('.sidebar-scroll, [data-sidebar], nav, aside')?.scrollTo({top:0,behavior:'instant'})"
},
{ "type": "wait", "duration": 300 },
{
"type": "evaluate",
"script": "Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('모두 펼치기'))?.click()"
},
{ "type": "wait", "duration": 2000 }
"id": 1,
"name": "메뉴 진입: 인사관리 > 부서관리",
"action": "menu_navigate",
"level1": "인사관리",
"level2": "부서관리",
"expected": {
"url_contains": "/hr/department-management",
"visible": ["부서관리"]
}
},
{
"id": 2,
"name": "필수 검증 #5: 목업 페이지 감지",
"action": "verify_not_mockup",
"checks": [
"부서 목록 또는 트리 표시",
"부서 추가 버튼 존재"
],
"expect": {
"sidebarReady": true
}
"expected": "정상 페이지 (목업 아님)"
},
{
"id": "step-1",
"name": "인사관리 메뉴 진입",
"description": "인사관리 > 부서관리 메뉴로 이동 (scrollAndFind 패턴 사용)",
"navigationPattern": "scrollAndFind",
"actions": [
{
"type": "scrollAndFind",
"container": ".sidebar-scroll, [data-sidebar], nav, aside",
"target": "인사관리",
"scrollStep": 200,
"maxAttempts": 10,
"waitAfterScroll": 300
},
{ "type": "click_if_exists", "target": "인사관리" },
{ "type": "wait", "duration": 500 },
{
"type": "scrollAndFind",
"container": ".sidebar-scroll, [data-sidebar], nav, aside",
"target": "부서관리",
"scrollStep": 200,
"maxAttempts": 10,
"waitAfterScroll": 300
},
{ "type": "click_if_exists", "target": "부서관리" }
"id": 3,
"name": "페이지 요소 확인",
"action": "verify_element",
"checks": [
"부서 목록 또는 트리 구조 표시",
"추가 버튼 존재"
],
"fallback": {
"type": "navigate",
"url": "https://dev.codebridge-x.com/ko/hr/department-management"
},
"expect": {
"url": "/hr/department-management",
"visible": ["부서관리", "추가"]
"expected": "부서관리 페이지 정상 표시"
},
{
"id": 4,
"phase": "CREATE",
"name": "[CREATE] 부서 추가 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('추가'), button:has-text('등록'), button:has-text('부서 추가')",
"expected": {
"modal": true
}
},
{
"id": "step-2",
"name": "현재 부서 개수 저장",
"description": "테스트 전 부서 개수 기록",
"capture": {
"variable": "initialCount",
"selector": "총 *건",
"extract": "number"
}
"id": 5,
"phase": "CREATE",
"name": "[CREATE] 부서명 입력",
"action": "fill",
"target": "input[name*='name'], input[placeholder*='부서명'], [role='dialog'] input",
"value": "E2E_TEST_본부_{timestamp}",
"clear": true
},
{
"id": "step-3",
"name": "상위 부서 추가 모달 열기",
"description": "추가 버튼 클릭하여 부서 추가 모달 열기",
"actions": [
{ "type": "click_if_exists", "target": "추가", "description": "부서 추가 모달 열기" }
],
"modalConfig": {
"containerSelector": "[role='dialog'], .modal",
"animationDelay": 300,
"waitForSelector": "[role='dialog']"
},
"expect": {
"modal": "부서 추가",
"visible": ["부서명", "등록", "취소"]
}
},
{
"id": "step-4",
"name": "랜덤 상위 부서명 입력",
"description": "모달 내 부서명 입력",
"actions": [
{
"type": "fillInModal",
"target": "부서명",
"value": "{randomData.parentDepartment}",
"options": { "waitAfter": 100 }
}
],
"expect": {
"buttonEnabled": "등록"
}
},
{
"id": "step-5",
"name": "상위 부서 등록",
"description": "모달 내 등록 버튼 클릭하여 상위 부서 추가 완료",
"actions": [
{ "type": "click_if_exists", "target": "등록", "options": { "waitAfter": 500 } }
],
"waitFor": {
"type": "modalClose",
"timeout": 5000
},
"expect": {
"toast": ["등록", "완료", "성공"],
"visible": ["{randomData.parentDepartment}"]
}
},
{
"id": "step-6",
"name": "상위 부서 등록 확인",
"description": "목록에서 새로 추가된 상위 부서 확인",
"id": 6,
"phase": "CREATE",
"name": "[CREATE] 필수 검증 #2: 부서 등록",
"action": "click_if_exists",
"target": "button:has-text('등록'), button:has-text('저장'), button:has-text('확인')",
"verify": {
"listContains": "{randomData.parentDepartment}",
"countIncreased": "{initialCount} + 1"
}
},
{
"id": "step-7",
"name": "하위 부서 추가 버튼 클릭",
"description": "생성된 상위 부서의 '하위 부서 추가' 버튼 클릭",
"actions": [
{
"type": "click_if_exists",
"target": "하위 부서 추가"
}
],
"expect": {
"modal": "하위 부서 추가",
"visible": ["부서명", "상위 부서", "{randomData.parentDepartment}"]
}
},
{
"id": "step-8",
"name": "랜덤 하위 부서명 입력",
"description": "모달 내 하위 부서명 입력",
"actions": [
{
"type": "fillInModal",
"target": "부서명",
"value": "{randomData.childDepartment}",
"options": { "waitAfter": 100 }
}
],
"expect": {
"buttonEnabled": "등록"
}
},
{
"id": "step-9",
"name": "하위 부서 등록",
"description": "모달 내 등록 버튼 클릭하여 하위 부서 추가 완료",
"actions": [
{ "type": "click_if_exists", "target": "등록", "options": { "waitAfter": 500 } }
],
"waitFor": {
"type": "modalClose",
"timeout": 5000
"toast": "등록|완료|성공"
},
"expect": {
"toast": ["등록", "완료", "성공"],
"visible": ["{randomData.childDepartment}"]
"expected": "부서 등록 완료"
},
{
"id": "6-modal-close",
"phase": "CREATE",
"name": "[CREATE] 모달 닫기 확인",
"action": "close_modal_if_open",
"expected": "모달 닫힘"
},
{
"id": 7,
"phase": "CREATE",
"name": "[CREATE] 등록 결과 확인",
"action": "verify_detail",
"checks": [
"E2E_TEST_본부 목록에 표시"
],
"expected": "부서 등록 확인"
},
{
"id": 8,
"phase": "DELETE",
"name": "[DELETE] 부서 삭제 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('삭제'), [aria-label='삭제']",
"expected": {
"confirm_dialog": true
}
},
{
"id": "step-10",
"name": "계층 구조 확인",
"description": "상위 부서 확장하여 하위 부서가 트리 구조로 표시되는지 확인",
"actions": [
{
"type": "click_if_exists",
"target": "table tbody tr:first-child [class*='expand'], table tbody tr:first-child button"
}
],
"id": 9,
"phase": "DELETE",
"name": "[DELETE] 필수 검증 #6: 삭제 확인",
"action": "click_and_confirm",
"target": "button:has-text('확인'), button:has-text('삭제')",
"verify": {
"hierarchy": {
"parent": "{randomData.parentDepartment}",
"children": ["{randomData.childDepartment}"]
},
"totalCountIncreased": "{initialCount} + 2"
}
},
{
"id": "step-11",
"name": "하위 부서 수정 모달 열기",
"description": "하위 부서의 수정 버튼 클릭",
"actions": [
{
"type": "click_if_exists",
"target": "수정"
}
],
"expect": {
"modal": "부서 수정",
"visible": ["부서명", "상위 부서", "저장", "취소"]
}
},
{
"id": "step-12",
"name": "하위 부서명 수정",
"description": "모달 내 하위 부서명 변경",
"actions": [
{
"type": "fillInModal",
"target": "부서명",
"value": "{randomData.childDepartment}_수정됨",
"options": { "waitAfter": 100 }
}
],
"expect": {
"buttonEnabled": "저장"
}
},
{
"id": "step-13",
"name": "부서 수정 저장",
"description": "모달 내 저장 버튼 클릭하여 부서 수정 완료",
"actions": [
{ "type": "click_if_exists", "target": "저장", "options": { "waitAfter": 500 } }
],
"waitFor": {
"type": "modalClose",
"timeout": 5000
"toast": "삭제|완료|성공"
},
"expect": {
"toast": ["수정", "완료", "성공", "저장"],
"visible": ["{randomData.childDepartment}_수정됨"]
}
"expected": "부서 삭제 완료"
},
{
"id": "step-13-1",
"name": "⚠️ 필수 검증: 수정 데이터 반영 확인",
"note": "토스트 성공 메시지만으로 PASS 판정 불가. 실제 데이터 변경 확인 필수!",
"description": "목록에서 수정된 부서명 확인",
"verify": {
"listContains": "{randomData.childDepartment}_수정됨"
}
},
{
"id": "step-14",
"name": "하위 부서 삭제",
"description": "하위 부서의 삭제 버튼 클릭",
"actions": [
{
"type": "click_if_exists",
"target": "삭제"
}
"id": 10,
"phase": "DELETE",
"name": "[DELETE] 삭제 결과 확인",
"action": "verify_detail",
"checks": [
"E2E_TEST_본부 목록에서 제거"
],
"expect": {
"confirmDialog": true,
"dialogText": ["삭제", "하시겠습니까"]
}
},
{
"id": "step-15",
"name": "하위 부서 삭제 확인",
"description": "삭제 확인 다이얼로그에서 확인 클릭",
"actions": [
{ "type": "click_if_exists", "target": "확인", "description": "삭제 확인" }
],
"waitFor": {
"type": "apiResponse",
"method": "DELETE",
"timeout": 5000
},
"expect": {
"toast": ["삭제", "완료", "성공"]
}
},
{
"id": "step-15-1",
"name": "⚠️ 필수 검증: 하위 부서 삭제 반영 확인",
"description": "목록에서 삭제된 하위 부서가 없어졌는지 확인",
"verify": {
"listNotContains": "{randomData.childDepartment}_수정됨"
}
},
{
"id": "step-16",
"name": "상위 부서 삭제",
"description": "상위 부서의 삭제 버튼 클릭",
"actions": [
{
"type": "click_if_exists",
"target": "삭제"
}
],
"expect": {
"confirmDialog": true,
"dialogText": ["삭제", "하시겠습니까"]
}
},
{
"id": "step-17",
"name": "상위 부서 삭제 확인",
"description": "삭제 확인 다이얼로그에서 확인 클릭",
"actions": [
{ "type": "click_if_exists", "target": "확인", "description": "삭제 확인" }
],
"waitFor": {
"type": "apiResponse",
"method": "DELETE",
"timeout": 5000
},
"expect": {
"toast": ["삭제", "완료", "성공"]
}
},
{
"id": "step-18",
"name": "⚠️ 필수 검증: 상위 부서 삭제 반영 확인",
"note": "토스트 성공 메시지만으로 PASS 판정 불가. 실제 데이터 삭제 확인 필수!",
"description": "목록에서 삭제된 상위 부서가 없어졌는지 확인",
"verify": {
"listNotContains": "{randomData.parentDepartment}",
"countRestored": "{initialCount}"
"expected": {
"row_exists": false
}
}
],
"assertions": [
"expectedAPIs": [
{
"type": "url",
"expected": "/hr/department-management",
"message": "부서관리 페이지에 머물러야 함"
"method": "GET",
"endpoint": "/api/v1/departments",
"description": "부서 목록 조회"
},
{
"type": "elementExists",
"selector": "text={randomData.parentDepartment}",
"message": "상위 부서가 목록에 표시되어야 함"
"method": "POST",
"endpoint": "/api/v1/departments",
"description": "부서 등록"
},
{
"type": "elementExists",
"selector": "text={randomData.childDepartment}",
"message": "하위 부서가 목록에 표시되어야 함"
},
{
"type": "hierarchy",
"parent": "{randomData.parentDepartment}",
"child": "{randomData.childDepartment}",
"message": "하위 부서가 상위 부서 아래에 트리 구조로 표시되어야 함"
"method": "DELETE",
"endpoint": "/api/v1/departments/{id}",
"description": "부서 삭제"
}
],
"cleanup": {
"enabled": false,
"description": "테스트 후 생성된 부서 삭제 (필요시 활성화)",
"order": "childFirst",
"steps": [
{
"action": "delete",
"target": "{randomData.childDepartment}",
"description": "하위 부서 먼저 삭제"
},
{
"action": "delete",
"target": "{randomData.parentDepartment}",
"description": "상위 부서 삭제"
}
]
},
"notes": {
"testScope": "상위 부서 생성 → 하위 부서 추가 → 계층 구조 검증까지 전체 플로우 테스트",
"randomGeneration": {
"parent": "prefix(신규,테스트,개발 등) + '본부' + timestamp",
"child": "prefix(기획,개발,디자인 등) + '팀' + timestamp"
"requiredVerifications": [
{
"id": 2,
"name": "등록/저장 버튼",
"steps": [6],
"criteria": "API 호출 + 성공 토스트 + 데이터 반영"
},
"duplicateHandling": "timestamp 포함으로 중복 방지",
"prerequisites": "로그인된 사용자에게 부서 추가 권한 필요",
"uiElements": {
"addButton": "상단 '추가' 버튼 - 최상위 부서 추가",
"subAddButton": "각 행의 '하위 부서 추가' 아이콘 버튼",
"expandButton": "트리 구조 확장/축소 버튼"
{
"id": 5,
"name": "목업 페이지 감지",
"steps": [2],
"criteria": "부서 목록, 추가 버튼 존재"
},
{
"id": 6,
"name": "삭제 기능",
"steps": [8, 9, 10],
"criteria": "DELETE API + 목록에서 제거"
}
],
"rollbackPlan": {
"onCreateFail": "모달 닫기",
"onDeleteFail": "E2E_TEST_ 접두사 부서 수동 삭제 필요",
"cleanupRequired": "E2E_TEST_ 접두사 부서는 테스트 데이터"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,17 @@
{
"enabled": true,
"id": "free-board",
"name": "자유게시판 E2E 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": ["error", "fail", "timeout", "404", "500", "blocked"]
},
"description": "자유게시판의 목록, 게시글 작성, 상세, 수정, 삭제, 댓글 CRUD 전체 워크플로우 테스트",
"url": "/ko/boards/free",
"navigation": {
"targetUrl": "/boards/free",
"urlPattern": "/boards/free|/ko/boards/free",
"menuHints": ["자유게시판", "자유 게시판", "게시판"]
},
"description": "자유게시판의 목록, 게시글 작성, 상세, 수정, 삭제 전체 CRUD 워크플로우 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"menuNavigation": {
"level1": "게시판",
"level2": "자유게시판",
"expectedUrl": "/ko/boards/free",
"expectedUrl": "/boards/free",
"searchWithinParent": true,
"closeOtherMenus": true
},
@@ -23,723 +19,274 @@
"username": "TestUser5",
"password": "password123!"
},
"menuNavigationEnhanced": {
"strategy": "scroll-and-search",
"level1": {
"text": "게시판",
"scrollContainer": ".sidebar-scroll, [data-sidebar='content'], nav",
"maxScrollAttempts": 5,
"scrollStep": 200
"testData": {
"create": {
"title": "E2E_TEST_게시글_{timestamp}",
"content": "E2E 자동화 테스트를 위한 게시글입니다."
},
"level2": {
"text": "자유게시판",
"waitAfterLevel1Click": 500
},
"expectedUrl": "/ko/boards/free",
"fallbackUrl": "/ko/boards/free"
"update": {
"title": "E2E_TEST_수정완료_{timestamp}",
"content": "수정된 내용입니다."
}
},
"steps": [
{
"step": 0,
"name": "사이드바 메뉴 전체 펼치기",
"description": "모두 펼치기 버튼을 클릭하여 전체 메뉴를 펼친 후 메뉴 탐색 준비",
"actions": [
{
"type": "evaluate",
"script": "document.querySelector('.sidebar-scroll, [data-sidebar=\"content\"], nav')?.scrollTo({top: 0, behavior: 'instant'})"
},
{ "type": "wait", "duration": 300 },
{
"type": "evaluate",
"script": "Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('모두 펼치기'))?.click()"
},
{
"type": "wait",
"duration": 2000
}
]
"id": 1,
"name": "메뉴 진입: 게시판 > 자유게시판",
"action": "menu_navigate",
"level1": "게시판",
"level2": "자유게시판",
"expected": {
"url_contains": "/boards/free",
"visible": ["자유게시판"]
}
},
{
"step": 1,
"name": "2단계 메뉴 진입: 게시판 > 자유게시판",
"description": "게시판 > 자유게시판 메뉴로 이동하여 페이지 로드 확인",
"actions": [
{
"type": "scrollAndFind",
"target": "게시판",
"container": ".sidebar-scroll, [data-sidebar='content'], nav",
"maxAttempts": 5,
"scrollStep": 200
},
{
"type": "click_if_exists",
"target": "게시판"
},
{
"type": "wait",
"duration": 500
},
{
"type": "click_if_exists",
"target": "자유게시판"
},
{
"type": "wait",
"target": "페이지 로드 완료"
}
"id": 2,
"name": "필수 검증 #5: 목업 페이지 감지",
"action": "verify_not_mockup",
"checks": [
"게시글 목록 표시",
"글쓰기 버튼 존재",
"검색 기능 존재"
],
"verification": {
"title_contains": "자유게시판",
"elements": ["table", "button:has-text('글쓰기')"]
}
"expected": "정상 페이지 (목업 아님)"
},
{
"step": 2,
"name": "초기 게시글 목록 확인",
"action": "verify_table_structure",
"verification": {
"columns": ["No.", "제목", "작성자", "조회수", "상태", "등록일"],
"min_rows": 0
}
"id": 3,
"name": "게시판 테이블 구조 확인",
"action": "verify_table",
"checks": [
"제목 컬럼",
"작성자 컬럼",
"등록일 컬럼"
],
"expected": "게시판 테이블 표시"
},
{
"step": 3,
"name": "게시글 총 건수 확인",
"action": "verify_text",
"verification": {
"text_pattern": "총 \\d+건"
}
},
{
"step": 4,
"name": "검색 기능 확인 (검색창 존재)",
"action": "verify_element",
"target": "input[placeholder*='제목']",
"verification": {
"exists": true
}
},
{
"step": 5,
"name": "필터 드롭다운 확인 (상태)",
"action": "verify_element",
"target": "select, [role='combobox']:has-text('상태')",
"verification": {
"exists": true
}
},
{
"step": 6,
"name": "정렬 드롭다운 확인",
"action": "verify_element",
"target": "select, [role='combobox']:has-text('최신순')",
"verification": {
"exists": true
}
},
{
"step": 7,
"name": "날짜 범위 선택기 확인",
"action": "verify_element",
"target": "input[type='date']",
"verification": {
"count": 2
}
},
{
"step": 8,
"name": "검색 테스트 (제목)",
"action": "fill_and_wait",
"target": "input[placeholder*='제목']",
"id": 4,
"phase": "SEARCH",
"name": "[SEARCH] 검색 기능 테스트",
"action": "fill",
"target": "input[placeholder*='제목'], input[type='search'], input[placeholder*='검색']",
"value": "테스트",
"verification": {
"wait_for_data_change": true
}
"submit": true
},
{
"step": 9,
"name": "검색 결과 확인",
"action": "verify_table_data",
"verification": {
"filtered": true
}
"id": 5,
"phase": "SEARCH",
"name": "[SEARCH] 검색 결과 확인",
"action": "verify_detail",
"checks": [
"검색 결과 표시 또는 결과 없음 메시지"
],
"expected": "검색 기능 동작"
},
{
"step": 10,
"name": "검색어 초기화",
"action": "fill",
"target": "input[placeholder*='제목']",
"value": ""
},
{
"step": 11,
"name": "상태 필터 테스트 (게시됨)",
"action": "select_dropdown",
"target": "[role='combobox']:has-text('전체')",
"value": "게시됨",
"verification": {
"wait_for_data_change": true
}
},
{
"step": 12,
"name": "상태 필터 초기화 (전체)",
"action": "select_dropdown",
"target": "[role='combobox']",
"value": "전체"
},
{
"step": 13,
"name": "정렬 변경 (오래된순)",
"action": "select_dropdown",
"target": "[role='combobox']:has-text('최신순')",
"value": "오래된순",
"verification": {
"wait_for_data_change": true
}
},
{
"step": 14,
"name": "정렬 복원 (최신순)",
"action": "select_dropdown",
"target": "[role='combobox']:has-text('오래된순')",
"value": "최신순"
},
{
"step": 15,
"name": "글쓰기 버튼 클릭",
"id": 6,
"phase": "CREATE",
"name": "[CREATE] 글쓰기 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('글쓰기')"
"target": "button:has-text('글쓰기'), button:has-text('등록'), button:has-text('작성')"
},
{
"step": 16,
"name": "게시글 작성 페이지 진입 확인",
"action": "verify_url",
"verification": {
"url_pattern": "/boards/free\\?mode=new|/boards/free/create",
"title_contains": "자유게시판"
}
"id": 7,
"phase": "CREATE",
"name": "[CREATE] 작성 페이지 대기",
"action": "wait",
"duration": 2000
},
{
"step": 17,
"name": "제목 필드 확인",
"action": "verify_element",
"target": "input#title",
"verification": {
"exists": true,
"required": true
}
},
{
"step": 18,
"name": "내용 필드 확인",
"action": "verify_element",
"target": "textarea#content",
"verification": {
"exists": true,
"required": true
}
},
{
"step": 19,
"name": "게시글 제목 입력",
"id": 8,
"phase": "CREATE",
"name": "[CREATE] 제목 입력",
"action": "fill",
"target": "input#title",
"value": "E2E 테스트 게시글"
"target": "input#title, input[name='title'], input[placeholder*='제목']",
"value": "E2E_TEST_게시글_{timestamp}",
"clear": true
},
{
"step": 20,
"name": "게시글 내용 입력",
"id": 9,
"phase": "CREATE",
"name": "[CREATE] 내용 입력",
"action": "fill",
"target": "textarea#content",
"value": "이것은 E2E 자동화 테스트를 위한 게시글입니다."
"target": "textarea#content, textarea[name='content'], [contenteditable='true']",
"value": "E2E 자동화 테스트를 위한 게시글입니다.",
"clear": true
},
{
"step": 21,
"name": "현재 URL 저장 (등록 전)",
"action": "save_url",
"variable": "url_before_submit"
},
{
"step": 22,
"name": "게시글 등록 버튼 클릭",
"id": 10,
"phase": "CREATE",
"name": "[CREATE] 필수 검증 #2: 게시글 등록",
"action": "click_if_exists",
"target": "button:has-text('등록')"
},
{
"step": 23,
"name": "게시글 등록 완료 (URL 안정성 검증)",
"action": "verify_url_stability",
"verification": {
"expected_url_pattern": "/boards/free/\\d+",
"no_404": true,
"target": "button:has-text('등록'), button[type='submit']",
"verify": {
"url_maintained": false,
"no_error_page": true,
"success_condition": "url_changed_to_detail"
"toast": "등록|완료|성공"
},
"notes": "필수 검증 #2: 등록 후 상세 페이지로 이동해야 하며 404 에러 페이지가 나오면 안 됨"
"expected": "게시글 등록 완료"
},
{
"step": 24,
"name": "게시글 상세 페이지 진입 확인",
"action": "verify_page",
"verification": {
"title": "E2E 테스트 게시글",
"content_contains": "E2E 자동화 테스트"
"id": 11,
"phase": "CREATE",
"name": "[CREATE] 등록 후 대기",
"action": "wait",
"duration": 2000
},
{
"id": 12,
"phase": "READ",
"name": "[READ] 게시글 상세 확인",
"action": "verify_detail",
"checks": [
"E2E_TEST_게시글 제목 표시",
"게시글 내용 표시"
],
"expected": "게시글 상세 페이지 표시"
},
{
"id": 13,
"phase": "UPDATE",
"name": "[UPDATE] 수정 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('수정'), button:has-text('편집')",
"expected": {
"edit_mode": true
}
},
{
"step": 25,
"name": "게시글 ID 저장",
"action": "extract_from_url",
"pattern": "/ko/boards/free/(\\d+)",
"variable": "post_id"
"id": 14,
"phase": "UPDATE",
"name": "[UPDATE] 수정 페이지 대기",
"action": "wait",
"duration": 1500
},
{
"step": 26,
"name": "작성자 정보 표시 확인",
"action": "verify_element",
"target": "text=/\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}/",
"verification": {
"exists": true
}
"id": 15,
"phase": "UPDATE",
"name": "[UPDATE] 제목 수정",
"action": "fill",
"target": "input#title, input[name='title'], input[placeholder*='제목']",
"value": "E2E_TEST_수정완료_{timestamp}",
"clear": true
},
{
"step": 27,
"name": "조회수 표시 확인",
"action": "verify_element",
"target": "svg.lucide-eye",
"verification": {
"exists": true
}
"id": 16,
"phase": "UPDATE",
"name": "[UPDATE] 필수 검증 #2: 수정 저장",
"action": "click_if_exists",
"target": "button:has-text('저장'), button:has-text('수정'), button[type='submit']",
"verify": {
"no_error_page": true,
"toast": "수정|저장|완료|성공"
},
"expected": "수정 완료"
},
{
"step": 28,
"name": "수정 버튼 존재 확인 (작성자)",
"action": "verify_element",
"target": "button:has-text('수정')",
"verification": {
"exists": true
}
"id": 17,
"phase": "UPDATE",
"name": "[UPDATE] 수정 후 대기",
"action": "wait",
"duration": 2000
},
{
"step": 29,
"name": "삭제 버튼 존재 확인 (작성자)",
"action": "verify_element",
"id": 18,
"phase": "UPDATE",
"name": "[UPDATE] 수정 결과 확인",
"action": "verify_detail",
"checks": [
"E2E_TEST_수정완료 제목 표시"
],
"expected": "수정된 데이터 반영"
},
{
"id": 19,
"phase": "DELETE",
"name": "[DELETE] 삭제 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('삭제')",
"verification": {
"exists": true
"expected": {
"confirm_dialog": true
}
},
{
"step": 30,
"name": "댓글 섹션 확인",
"action": "verify_element",
"target": "text=/댓글/",
"verification": {
"exists": true
}
},
{
"step": 31,
"name": "댓글 입력란 확인",
"action": "verify_element",
"target": "textarea[placeholder*='댓글']",
"verification": {
"exists": true
}
},
{
"step": 32,
"name": "첫 번째 댓글 작성",
"action": "fill",
"target": "textarea[placeholder*='댓글']",
"value": "첫 번째 테스트 댓글입니다."
},
{
"step": 33,
"name": "댓글 등록 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('댓글 등록'), button:has-text('등록')"
},
{
"step": 34,
"name": "댓글 등록 확인",
"action": "verify_text",
"verification": {
"text": "첫 번째 테스트 댓글입니다.",
"exists": true
}
},
{
"step": 35,
"name": "댓글 수 업데이트 확인",
"action": "evaluate",
"script": "(function(){ var t = document.body.innerText; var m = t.match(/댓글[\\s(]*\\d+/); return m ? m[0] : 'comment section found: ' + (t.includes('댓글') ? 'yes' : 'no'); })()"
},
{
"step": 36,
"name": "두 번째 댓글 작성",
"action": "fill",
"target": "textarea[placeholder*='댓글']",
"value": "두 번째 테스트 댓글입니다."
},
{
"step": 37,
"name": "두 번째 댓글 등록",
"action": "click_if_exists",
"target": "button:has-text('댓글 등록'), button:has-text('등록')"
},
{
"step": 38,
"name": "두 번째 댓글 등록 확인",
"action": "verify_text",
"verification": {
"text": "두 번째 테스트 댓글입니다.",
"exists": true
}
},
{
"step": 39,
"name": "첫 번째 댓글 수정 버튼 클릭",
"description": "댓글 영역 내의 수정 버튼을 찾아 클릭 (게시글 수정 버튼과 구별)",
"actions": [
{
"type": "evaluate",
"script": "(function(){ var allBtns = Array.from(document.querySelectorAll('button')).filter(function(b){ return b.innerText && b.innerText.trim() === '수정'; }); var commentBtn = allBtns.filter(function(b){ return b.closest('[class*=\"comment\"], [class*=\"Comment\"], [class*=\"reply\"]'); }); if(commentBtn.length > 0){ commentBtn[0].click(); return 'clicked comment edit btn'; } if(allBtns.length >= 2){ allBtns[allBtns.length - 1].click(); return 'clicked last edit btn (assumed comment)'; } return 'no comment edit btn found'; })()"
},
{ "type": "wait", "duration": 1000 }
]
},
{
"step": 40,
"name": "댓글 수정 내용 입력",
"description": "인라인 편집 또는 별도 textarea에 수정 내용 입력",
"actions": [
{
"type": "evaluate",
"script": "(function(){ var newVal = '수정된 첫 번째 댓글입니다.'; var textareas = Array.from(document.querySelectorAll('textarea')); var editTA = textareas.find(function(t){ return t.value && t.value.includes('첫 번째 테스트'); }); if(editTA){ var rk = Object.keys(editTA).find(function(k){ return k.indexOf('__reactProps$')===0; }); if(rk && editTA[rk] && typeof editTA[rk].onChange==='function'){ var setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set; setter.call(editTA, newVal); editTA[rk].onChange({target:editTA,currentTarget:editTA}); return 'filled via reactProps (value='+editTA.value+')'; } editTA.focus(); editTA.select(); document.execCommand('insertText',false,newVal); return 'filled via execCommand (value='+editTA.value+')'; } var inputs = Array.from(document.querySelectorAll('input[type=\"text\"]')); var editInput = inputs.find(function(i){ return i.value && i.value.includes('첫 번째 테스트'); }); if(editInput){ var rk2 = Object.keys(editInput).find(function(k){ return k.indexOf('__reactProps$')===0; }); if(rk2 && editInput[rk2] && typeof editInput[rk2].onChange==='function'){ var setter2 = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set; setter2.call(editInput, newVal); editInput[rk2].onChange({target:editInput,currentTarget:editInput}); return 'filled input via reactProps (value='+editInput.value+')'; } editInput.focus(); editInput.select(); document.execCommand('insertText',false,newVal); return 'filled input via execCommand (value='+editInput.value+')'; } var editables = document.querySelectorAll('[contenteditable=\"true\"]'); for(var i=0; i<editables.length; i++){ if(editables[i].textContent && editables[i].textContent.includes('첫 번째 테스트')){ editables[i].focus(); var sel = window.getSelection(); var range = document.createRange(); range.selectNodeContents(editables[i]); sel.removeAllRanges(); sel.addRange(range); document.execCommand('insertText',false,newVal); return 'filled contenteditable'; }} return 'edit element not found'; })()"
}
]
},
{
"step": 41,
"name": "댓글 수정 저장",
"actions": [
{
"type": "evaluate",
"script": "(function(){ var btn = Array.from(document.querySelectorAll('button')).find(function(b){ return b.innerText && (b.innerText.includes('저장') || b.innerText.includes('수정 완료') || b.innerText.includes('확인')); }); if(btn){ btn.click(); return 'save clicked'; } return 'save btn not found'; })()"
},
{ "type": "wait", "duration": 1500 }
]
},
{
"step": 42,
"name": "댓글 수정 확인",
"action": "evaluate",
"script": "(function(){ var found = document.body.innerText.includes('수정된 첫 번째 댓글'); return found ? 'Text found: 수정된 첫 번째 댓글' : 'Comment edit may not have saved (non-critical)'; })()"
},
{
"step": 43,
"name": "두 번째 댓글 삭제 버튼 클릭",
"description": "댓글 영역 내의 삭제 버튼을 찾아 클릭 (게시글 삭제 버튼과 구별)",
"actions": [
{
"type": "evaluate",
"script": "(function(){ var allBtns = Array.from(document.querySelectorAll('button')).filter(function(b){ return b.innerText && b.innerText.trim() === '삭제'; }); var commentBtns = allBtns.filter(function(b){ return b.closest('[class*=\"comment\"], [class*=\"Comment\"], [class*=\"reply\"]'); }); if(commentBtns.length > 0){ commentBtns[commentBtns.length-1].click(); return 'clicked last comment delete btn'; } if(allBtns.length >= 2){ allBtns[allBtns.length-1].click(); return 'clicked last delete btn (assumed comment)'; } return 'no comment delete btn found'; })()"
},
{ "type": "wait", "duration": 500 }
]
},
{
"step": 44,
"name": "댓글 삭제 확인 다이얼로그 처리",
"actions": [
{
"type": "evaluate",
"script": "(function(){ var dialog = document.querySelector('[role=\"dialog\"], [role=\"alertdialog\"], [class*=\"modal\"]:not([class*=\"tooltip\"])'); if(dialog && dialog.offsetParent !== null){ var confirmBtn = Array.from(dialog.querySelectorAll('button')).find(function(b){ return ['확인','삭제','예','OK','Yes'].some(function(t){ return b.innerText && b.innerText.includes(t); }); }); if(confirmBtn){ confirmBtn.click(); return 'confirmed delete dialog'; } } return 'no dialog or auto-handled'; })()"
},
{ "type": "wait", "duration": 1500 }
]
},
{
"step": 45,
"name": "댓글 삭제 확인",
"action": "verify_text",
"verification": {
"text": "두 번째 테스트 댓글",
"exists": false
}
},
{
"step": 46,
"name": "댓글 수 확인 (삭제 후)",
"action": "evaluate",
"script": "(function(){ var t = document.body.innerText; var m = t.match(/댓글[\\s(]*\\d+/); return m ? m[0] : 'comment section: ' + (t.includes('댓글') ? 'exists' : 'not found'); })()"
},
{
"step": 47,
"name": "게시글 수정 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('수정')"
},
{
"step": 48,
"name": "게시글 수정 페이지 진입 확인",
"action": "verify_url",
"verification": {
"url_pattern": "/(ko/)?boards/free/\\d+\\?mode=edit"
}
},
{
"step": 49,
"name": "제목 필드에 기존 값 확인",
"action": "verify_input_value",
"target": "input#title",
"verification": {
"value": "E2E 테스트 게시글"
}
},
{
"step": 50,
"name": "제목 수정",
"action": "fill",
"target": "input#title",
"value": "E2E 테스트 게시글 (수정됨)"
},
{
"step": 51,
"name": "내용 수정",
"action": "fill",
"target": "textarea#content",
"value": "수정된 내용입니다. E2E 자동화 테스트를 위한 게시글입니다."
},
{
"step": 52,
"name": "현재 URL 저장 (수정 전)",
"action": "save_url",
"variable": "url_before_update"
},
{
"step": 53,
"name": "수정 저장 버튼 클릭",
"action": "click_if_exists",
"target": "button[type='submit']:has-text('저장'), button:has-text('수정')"
},
{
"step": 54,
"name": "게시글 수정 완료 (URL 안정성 검증)",
"action": "verify_url_stability",
"verification": {
"expected_url_pattern": "/boards/free/\\d+",
"no_404": true,
"no_error_page": true,
"success_condition": "url_back_to_detail"
"id": 20,
"phase": "DELETE",
"name": "[DELETE] 필수 검증 #6: 삭제 확인",
"action": "click_and_confirm",
"target": "button:has-text('확인'), button:has-text('삭제')",
"verify": {
"toast": "삭제|완료|성공",
"redirect": "/boards/free"
},
"notes": "필수 검증 #2: 수정 후 상세 페이지로 돌아가야 하며 404 에러 페이지가 나오면 안 됨"
"expected": "삭제 완료 및 목록 복귀"
},
{
"step": 55,
"name": "수정된 제목 확인",
"action": "verify_text",
"verification": {
"text": "E2E 테스트 게시글 (수정됨)",
"exists": true
}
"id": 21,
"phase": "DELETE",
"name": "[DELETE] 삭제 후 대기",
"action": "wait",
"duration": 2000
},
{
"step": 56,
"name": "수정된 내용 확인",
"action": "verify_text",
"verification": {
"text": "수정된 내용입니다",
"exists": true
}
},
{
"step": 57,
"name": "목록으로 이동 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('목록으로')"
},
{
"step": 58,
"name": "목록 페이지 복귀 확인",
"action": "verify_url",
"verification": {
"url": "/boards/free"
}
},
{
"step": 59,
"name": "수정된 게시글 목록 확인",
"action": "verify_text",
"verification": {
"text": "E2E 테스트 게시글 (수정됨)",
"exists": true
}
},
{
"step": 60,
"name": "게시글 클릭하여 상세 진입",
"action": "click_if_exists",
"target": "text=E2E 테스트 게시글 (수정됨)"
},
{
"step": 61,
"name": "상세 페이지 진입 확인",
"action": "verify_url",
"verification": {
"url_pattern": "/(ko/)?boards/free/\\d+"
}
},
{
"step": 62,
"name": "조회수 증가 확인",
"action": "verify_element",
"target": "svg.lucide-eye ~ text",
"verification": {
"text_pattern": "\\d+",
"notes": "조회수가 표시되는지 확인"
}
},
{
"step": 63,
"name": "게시글 삭제 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('삭제')"
},
{
"step": 64,
"name": "삭제 확인 다이얼로그 표시 확인",
"action": "verify_dialog",
"verification": {
"title": "게시글 삭제",
"content_contains": "삭제하시겠습니까"
}
},
{
"step": 65,
"name": "현재 URL 저장 (삭제 전)",
"action": "save_url",
"variable": "url_before_delete"
},
{
"step": 66,
"name": "삭제 확인 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('삭제'):last-of-type"
},
{
"step": 67,
"name": "게시글 삭제 완료 (URL 안정성 검증)",
"action": "verify_url_stability",
"verification": {
"expected_url": "/boards/free",
"no_404": true,
"no_error_page": true,
"success_condition": "url_back_to_list"
},
"notes": "필수 검증 #2: 삭제 후 목록 페이지로 돌아가야 하며 404 에러 페이지가 나오면 안 됨"
},
{
"step": 68,
"name": "목록 페이지 복귀 확인",
"action": "verify_url",
"verification": {
"url": "/boards/free"
}
},
{
"step": 69,
"name": "삭제된 게시글 목록에서 제거 확인",
"action": "verify_text",
"verification": {
"text": "E2E 테스트 게시글 (수정됨)",
"exists": false
}
},
{
"step": 70,
"name": "콘솔 에러 확인",
"action": "verify_element",
"target": "body",
"verification": {
"no_errors": true
"id": 22,
"phase": "DELETE",
"name": "[DELETE] 삭제 결과 확인",
"action": "verify_detail",
"search": "E2E_TEST_수정완료",
"expected": {
"row_exists": false,
"message": "테스트 게시글이 목록에서 제거됨"
}
}
],
"expectedAPIs": [
{
"endpoint": "GET /api/v1/boards/free",
"description": "자유게시판 정보 조회"
"method": "GET",
"endpoint": "/api/v1/boards/free/posts",
"description": "게시글 목록 조회"
},
{
"endpoint": "GET /api/v1/boards/free/posts",
"description": "게시글 목록 조회 (per_page=100)"
"method": "POST",
"endpoint": "/api/v1/boards/free/posts",
"description": "게시글 등록"
},
{
"endpoint": "POST /api/v1/boards/free/posts",
"description": "게시글 등록",
"payload": {
"title": "string",
"content": "string",
"is_secret": "boolean"
}
"method": "PUT",
"endpoint": "/api/v1/boards/free/posts/{id}",
"description": "게시글 수정"
},
{
"endpoint": "GET /api/v1/boards/free/posts/{id}",
"description": "게시글 상세 조회 (조회수 증가)"
},
{
"endpoint": "GET /api/v1/boards/free/posts/{id}/comments",
"description": "댓글 목록 조회"
},
{
"endpoint": "POST /api/v1/boards/free/posts/{id}/comments",
"description": "댓글 등록",
"payload": {
"content": "string"
}
},
{
"endpoint": "PUT /api/v1/boards/free/posts/{id}/comments/{commentId}",
"description": "댓글 수정",
"payload": {
"content": "string"
}
},
{
"endpoint": "DELETE /api/v1/boards/free/posts/{id}/comments/{commentId}",
"description": "댓글 삭제"
},
{
"endpoint": "PUT /api/v1/boards/free/posts/{id}",
"description": "게시글 수정",
"payload": {
"title": "string",
"content": "string",
"is_secret": "boolean"
}
},
{
"endpoint": "DELETE /api/v1/boards/free/posts/{id}",
"method": "DELETE",
"endpoint": "/api/v1/boards/free/posts/{id}",
"description": "게시글 삭제"
}
],
"notes": [
"자유게시판은 boardCode='free'를 사용하는 동적 게시판입니다.",
"게시글 등록/수정/삭제 시 반드시 URL 안정성 검증 수행 (필수 검증 #2)",
"댓글 CRUD 기능 모두 테스트해야 합니다.",
"IntegratedListTemplateV2 템플릿 사용으로 반응형 디자인 (데스크톱/모바일)",
"페이지네이션은 10개 단위로 동작 (10개 미만 시 미표시)",
"검색은 제목, 작성자명으로 필터링됩니다.",
"상태 필터: 전체, 게시됨, 임시저장",
"정렬: 최신순, 오래된순",
"조회수는 게시글 상세 조회 시마다 증가합니다.",
"작성자만 수정/삭제 버튼이 표시됩니다.",
"댓글도 작성자만 수정/삭제 가능합니다.",
"댓글 수정은 인라인 편집 방식 (별도 textarea 삽입이 아닌 기존 요소 내 편집)",
"댓글 삭제 시 확인 다이얼로그가 표시될 수 있음",
"게시글 내용은 HTML로 저장되며 dangerouslySetInnerHTML로 렌더링됩니다."
]
"requiredVerifications": [
{
"id": 2,
"name": "등록/저장 버튼",
"steps": [10, 16],
"criteria": "API 호출 + 성공 토스트 + 데이터 반영"
},
{
"id": 5,
"name": "목업 페이지 감지",
"steps": [2],
"criteria": "게시글 목록, 글쓰기 버튼, 검색 기능 존재"
},
{
"id": 6,
"name": "삭제 기능",
"steps": [19, 20, 22],
"criteria": "DELETE API + 목록에서 제거"
}
],
"rollbackPlan": {
"onCreateFail": "영향 없음",
"onUpdateFail": "테스트 게시글 수동 삭제 필요",
"onDeleteFail": "테스트 게시글 수동 삭제 필요",
"cleanupRequired": "E2E_TEST_ 접두사 게시글은 테스트 데이터"
}
}

View File

@@ -7,16 +7,16 @@
},
"description": "자재관리 > 재고현황 페이지의 재고 조회 및 엑셀 다운로드 기능을 테스트하는 E2E 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"url": "/material/inventory",
"url": "/material/stock-status",
"navigation": {
"targetUrl": "/material/inventory",
"urlPattern": "/material/inventory|/ko/material/inventory",
"targetUrl": "/material/stock-status",
"urlPattern": "/material/stock-status|/ko/material/stock-status",
"menuHints": ["재고현황", "재고 현황", "자재관리"]
},
"menuNavigation": {
"level1": "자재관리",
"level2": "재고현황",
"expectedUrl": "/material/inventory",
"expectedUrl": "/material/stock-status",
"searchWithinParent": true,
"closeOtherMenus": true
},
@@ -37,8 +37,8 @@
"text": "재고현황",
"waitAfterClick": 300
},
"fallbackUrl": "/material/inventory",
"expectedUrl": "/material/inventory"
"fallbackUrl": "/material/stock-status",
"expectedUrl": "/material/stock-status"
},
"timeout": 90000,
"tags": ["material", "inventory", "read-only"],
@@ -83,12 +83,12 @@
{ "type": "click_if_exists", "target": "재고현황" }
],
"expect": {
"url": "/material/inventory",
"url": "/material/stock-status",
"visible": ["재고 목록", "엑셀 다운로드"]
},
"fallback": {
"type": "navigate",
"url": "/material/inventory"
"url": "/material/stock-status"
}
},
{
@@ -165,7 +165,7 @@
"noErrorPage": true
},
"verify": {
"apiCall": "GET /api/material/inventory/export"
"apiCall": "GET /api/material/stock-status/export"
}
},
{
@@ -209,7 +209,7 @@
"assertions": [
{
"type": "url",
"expected": "/material/inventory",
"expected": "/material/stock-status",
"message": "재고현황 페이지에 머물러야 함"
},
{

View File

@@ -1,590 +0,0 @@
{
"id": "permission-management",
"name": "설정 - 권한관리",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": [
"error",
"fail",
"timeout",
"404",
"500",
"blocked"
]
},
"url": "/ko/settings/permissions",
"navigation": {
"targetUrl": "/settings/permissions",
"urlPattern": "/settings/permissions|/ko/settings/permissions",
"menuHints": [
"권한관리",
"권한 관리",
"설정"
]
},
"menuNavigation": {
"level1": "설정",
"level2": "권한관리",
"expectedUrl": "/ko/settings/permissions",
"searchWithinParent": true,
"closeOtherMenus": true
},
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"menuNavigationEnhanced": {
"strategy": "scroll-and-search",
"description": "사이드바를 스크롤하며 메뉴를 찾고 클릭하여 404를 방지",
"level1": "설정",
"level2": "권한관리",
"alternativeLevel1Names": [
"설정",
"Settings",
"환경설정",
"시스템설정",
"관리"
],
"alternativeLevel2Names": [
"권한관리",
"권한 관리",
"Permissions",
"역할관리",
"Role Management"
],
"scrollConfig": {
"sidebarSelector": "nav, aside, [role='navigation'], .sidebar, #sidebar",
"menuItemSelector": "a, button, [role='menuitem'], [role='treeitem']",
"scrollStep": 200,
"maxScrollAttempts": 10,
"scrollDelay": 300
}
},
"expectedAPIs": [
{
"method": "GET",
"path": "/api/roles",
"description": "역할 목록 조회"
},
{
"method": "GET",
"path": "/api/roles/stats",
"description": "역할 통계 조회"
},
{
"method": "POST",
"path": "/api/roles",
"description": "역할 생성"
},
{
"method": "GET",
"path": "/api/roles/{id}",
"description": "역할 상세 조회"
},
{
"method": "PUT",
"path": "/api/roles/{id}",
"description": "역할 수정"
},
{
"method": "DELETE",
"path": "/api/roles/{id}",
"description": "역할 삭제"
},
{
"method": "GET",
"path": "/api/roles/{id}/permissions",
"description": "역할별 권한 매트릭스 조회"
},
{
"method": "PUT",
"path": "/api/roles/{id}/permissions",
"description": "역할별 권한 저장"
}
],
"steps": [
{
"id": "step-00",
"name": "사이드바 메뉴 전체 펼치기",
"description": "모두 펼치기 버튼을 클릭하여 전체 메뉴를 펼친 후 메뉴 탐색 준비",
"actions": [
{
"type": "evaluate",
"script": "document.querySelector('.sidebar-scroll')?.scrollTo({top:0,behavior:'instant'})"
},
{
"type": "wait",
"duration": 300
},
{
"type": "evaluate",
"script": "Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('모두 펼치기'))?.click()"
},
{
"type": "wait",
"duration": 2000
}
]
},
{
"id": "step-01",
"name": "2단계 메뉴 진입: 설정 > 권한관리",
"description": "사이드바를 스크롤하며 설정 > 권한관리 메뉴를 찾아 클릭",
"actions": [
{
"type": "scrollAndFind",
"target": "설정",
"alternativeTexts": [
"설정",
"Settings",
"환경설정",
"시스템설정"
],
"scrollContainer": "sidebar",
"maxAttempts": 10,
"description": "스크롤하며 설정 메뉴 찾기"
},
{
"type": "click_if_exists",
"target": "설정",
"description": "설정 메뉴 클릭"
},
{
"type": "wait",
"duration": 500,
"description": "서브메뉴 펼쳐지기 대기"
},
{
"type": "scrollAndFind",
"target": "권한관리",
"alternativeTexts": [
"권한관리",
"권한 관리",
"Permissions",
"역할관리"
],
"scrollContainer": "submenu",
"maxAttempts": 5,
"description": "서브메뉴에서 권한관리 찾기"
},
{
"type": "click_if_exists",
"target": "권한관리",
"description": "권한관리 메뉴 클릭"
},
{
"type": "wait",
"target": "페이지 로드 완료",
"timeout": 10000
}
],
"expected": {
"url": "/ko/settings/permissions",
"title": "권한관리",
"authenticated": true
},
"verification": [
"설정 메뉴가 펼쳐졌는지 확인",
"권한관리 서브메뉴 클릭 성공",
"페이지 타이틀 '권한관리' 확인",
"설명 '역할 기반 권한을 관리합니다' 확인",
"Shield 아이콘 표시",
"404 에러 없이 페이지 로드 완료"
]
},
{
"id": "step-02",
"name": "통계 카드 확인",
"action": "verify",
"verification": [
"통계 카드 4개 표시: 전체 역할, 공개, 숨김, 사용 중",
"각 카드에 아이콘과 숫자 표시"
]
},
{
"id": "step-03",
"name": "탭 확인",
"action": "verify",
"verification": [
"탭 3개 표시: 전체, 공개, 숨김",
"각 탭에 카운트 표시"
]
},
{
"id": "step-04",
"name": "테이블 구조 확인",
"action": "verify",
"verification": [
"테이블 컬럼 확인: 체크박스, 번호, 역할, 설명, 상태, 등록일",
"역할 등록 버튼 존재",
"검색창 존재 (placeholder: '역할명, 설명 검색...')"
]
},
{
"id": "step-05",
"name": "탭 필터 테스트 - 공개",
"action": "click_if_exists",
"target": "공개 탭",
"verification": [
"공개 탭 활성화",
"공개 상태 역할만 표시",
"데이터 필터링 확인"
]
},
{
"id": "step-06",
"name": "탭 필터 테스트 - 숨김",
"action": "click_if_exists",
"target": "숨김 탭",
"verification": [
"숨김 탭 활성화",
"숨김 상태 역할만 표시",
"데이터 필터링 확인"
]
},
{
"id": "step-07",
"name": "탭 필터 테스트 - 전체",
"action": "click_if_exists",
"target": "전체 탭",
"verification": [
"전체 탭 활성화",
"모든 역할 표시"
]
},
{
"id": "step-08",
"name": "검색 기능 테스트",
"action": "click_if_exists",
"target": "검색 입력 필드",
"value": "관리자",
"verification": [
"'관리자' 포함된 역할만 표시",
"실시간 필터링 동작"
]
},
{
"id": "step-09",
"name": "검색 초기화",
"action": "click_if_exists",
"target": "검색 입력 필드",
"verification": [
"검색어 제거됨",
"전체 목록 복원"
]
},
{
"id": "step-10",
"name": "역할 등록 페이지 이동",
"action": "click_if_exists",
"target": "역할 등록 버튼",
"verification": [
"URL 변경: /settings/permissions?mode=new",
"페이지 타이틀 변경 확인"
]
},
{
"id": "step-11",
"name": "역할명 입력",
"action": "click_if_exists",
"target": "권한명 입력 필드",
"value": "E2E 테스트 역할",
"verification": [
"입력 값 반영 확인"
]
},
{
"id": "step-12",
"name": "설명 입력",
"action": "click_if_exists",
"target": "설명 입력 필드 (있는 경우)",
"value": "E2E 테스트를 위한 역할입니다",
"verification": [
"입력 값 반영 확인"
]
},
{
"id": "step-13",
"name": "상태 선택",
"action": "click_if_exists",
"target": "상태 드롭다운",
"value": "공개",
"verification": [
"상태 선택 확인"
]
},
{
"id": "step-14",
"name": "역할 등록",
"action": "click_if_exists",
"target": "등록 버튼",
"verification": [
"현재 URL 저장",
"등록 버튼 클릭",
"URL 변경 여부 확인 (에러 페이지 이동 방지)",
"에러 텍스트 없음 ('페이지를 찾을 수 없습니다', '404', 'Not Found' 등)",
"API 호출 확인: POST /api/roles",
"API 응답 200 OK 확인",
"목록 페이지로 리다이렉트 또는 상세 페이지 이동",
"성공 토스트 메시지 확인"
]
},
{
"id": "step-15",
"name": "목록에서 신규 역할 확인",
"action": "verify",
"verification": [
"목록 페이지 진입 (URL: /settings/permissions)",
"'E2E 테스트 역할' 목록에 표시",
"통계 카드 숫자 증가 확인"
]
},
{
"id": "step-16",
"name": "역할 상세 페이지 이동",
"action": "click_if_exists",
"target": "E2E 테스트 역할 행",
"verification": [
"URL 변경: /settings/permissions/{id}",
"페이지 타이틀 '권한 상세' 확인",
"기본 정보 섹션 표시",
"메뉴별 권한 테이블 표시"
]
},
{
"id": "step-17",
"name": "기본 정보 확인",
"action": "verify",
"verification": [
"권한명 입력 필드에 'E2E 테스트 역할' 표시",
"상태 드롭다운에 '공개' 선택됨",
"삭제 버튼 존재",
"수정 버튼 존재",
"목록으로 버튼 존재"
]
},
{
"id": "step-18",
"name": "권한 테이블 구조 확인",
"action": "verify",
"verification": [
"테이블 헤더: 메뉴, 조회, 생성, 수정, 삭제, 승인, 내보내기, 관리",
"각 헤더에 전체 선택 체크박스 존재",
"메뉴 목록 표시 (부모 메뉴 접기/펼치기 아이콘 있음)",
"각 메뉴별 권한 체크박스 존재"
]
},
{
"id": "step-19",
"name": "부모 메뉴 펼치기",
"action": "click_if_exists",
"target": "첫 번째 부모 메뉴 펼치기 아이콘",
"verification": [
"자식 메뉴 목록 표시",
"아이콘이 ChevronDown으로 변경",
"자식 메뉴는 들여쓰기되어 표시"
]
},
{
"id": "step-20",
"name": "개별 권한 체크박스 토글",
"action": "click_if_exists",
"target": "첫 번째 메뉴의 '조회' 체크박스",
"verification": [
"체크박스 상태 변경",
"자동 저장 동작 (API 호출 확인)"
]
},
{
"id": "step-21",
"name": "컬럼 전체 선택",
"action": "click_if_exists",
"target": "'조회' 헤더 체크박스",
"verification": [
"모든 메뉴의 '조회' 권한 체크",
"자동 저장 동작 확인"
]
},
{
"id": "step-22",
"name": "권한명 수정",
"action": "click_if_exists",
"target": "권한명 입력 필드",
"value": "E2E 테스트 역할 (수정됨)",
"verification": [
"입력 값 변경 확인"
]
},
{
"id": "step-23",
"name": "권한명 수정 저장 (blur)",
"action": "blur",
"target": "권한명 입력 필드",
"verification": [
"자동 저장 동작 (API 호출)",
"PUT /api/roles/{id} 확인"
]
},
{
"id": "step-24",
"name": "상태 변경",
"action": "click_if_exists",
"target": "상태 드롭다운",
"value": "숨김",
"verification": [
"상태 변경 확인",
"자동 저장 동작"
]
},
{
"id": "step-25",
"name": "목록으로 이동",
"action": "click_if_exists",
"target": "목록으로 버튼",
"verification": [
"URL 변경: /settings/permissions",
"목록 페이지 표시"
]
},
{
"id": "step-26",
"name": "수정된 역할 확인",
"action": "verify",
"verification": [
"'E2E 테스트 역할 (수정됨)' 목록에 표시",
"상태 Badge '숨김'으로 표시"
]
},
{
"id": "step-27",
"name": "숨김 탭으로 이동",
"action": "click_if_exists",
"target": "숨김 탭",
"verification": [
"숨김 상태 역할만 표시",
"'E2E 테스트 역할 (수정됨)' 표시됨"
]
},
{
"id": "step-28",
"name": "전체 탭으로 복귀",
"action": "click_if_exists",
"target": "전체 탭",
"verification": [
"모든 역할 표시"
]
},
{
"id": "step-29",
"name": "체크박스 선택",
"action": "click_if_exists",
"target": "E2E 테스트 역할 체크박스",
"verification": [
"체크박스 선택됨",
"'1개 항목 선택됨' 표시",
"'선택 삭제 (1)' 버튼 표시",
"작업 컬럼 추가 (권한 설정, 수정, 삭제 버튼)"
]
},
{
"id": "step-30",
"name": "단일 삭제 - 작업 컬럼 삭제 버튼",
"action": "click_if_exists",
"target": "작업 컬럼의 삭제 버튼",
"verification": [
"삭제 확인 다이얼로그 표시",
"다이얼로그 제목: '역할 삭제'",
"경고 메시지: '이 역할을 사용 중인 사원이 있으면 해당 사원의 역할이 초기화됩니다.'"
]
},
{
"id": "step-31",
"name": "삭제 취소",
"action": "click_if_exists",
"target": "다이얼로그 취소 버튼",
"verification": [
"다이얼로그 닫힘",
"역할 삭제되지 않음"
]
},
{
"id": "step-32",
"name": "일괄 삭제 버튼 클릭",
"action": "click_if_exists",
"target": "선택 삭제 버튼",
"verification": [
"삭제 확인 다이얼로그 표시",
"'선택한 1개의 역할을 삭제하시겠습니까?' 메시지"
]
},
{
"id": "step-33",
"name": "일괄 삭제 실행",
"action": "click_if_exists",
"target": "다이얼로그 삭제 버튼",
"verification": [
"삭제 중 로딩 표시 ('삭제 중...')",
"API 호출: DELETE /api/roles/{id}",
"API 응답 200 OK 확인",
"다이얼로그 닫힘",
"성공 토스트 메시지 확인",
"목록에서 삭제된 역할 제거됨",
"통계 카드 숫자 감소 확인"
]
},
{
"id": "step-34",
"name": "체크박스 전체 선택",
"action": "click_if_exists",
"target": "테이블 헤더 체크박스",
"verification": [
"현재 페이지의 모든 항목 선택",
"'N개 항목 선택됨' 표시",
"'선택 삭제 (N)' 버튼 표시"
]
},
{
"id": "step-35",
"name": "전체 선택 해제",
"action": "click_if_exists",
"target": "테이블 헤더 체크박스",
"verification": [
"모든 선택 해제",
"'선택 삭제' 버튼 숨김",
"작업 컬럼 제거"
]
},
{
"id": "step-36",
"name": "페이지네이션 테스트 (데이터 충분 시)",
"action": "verify",
"verification": [
"페이지네이션 컨트롤 존재 여부 확인",
"페이지당 20개 항목 표시",
"총 페이지 수 표시"
]
},
{
"id": "step-37",
"name": "반응형 테스트 - 모바일",
"action": "resize",
"width": 375,
"height": 667,
"verification": [
"모바일 카드 레이아웃으로 변경",
"각 카드에 역할명, 상태, 설명, 등록일 표시",
"카드에 권한 설정, 수정 버튼 존재"
]
}
],
"testData": {
"role": {
"name": "E2E 테스트 역할",
"description": "E2E 테스트를 위한 역할입니다",
"status": "active"
}
},
"cleanup": {
"description": "Step-33에서 테스트 데이터 삭제 완료"
}
}

View File

@@ -7,16 +7,16 @@
},
"description": "판매관리 > 단가관리 페이지의 품목별 단가 조회/등록/수정 기능을 테스트하는 E2E 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"url": "/sales/price",
"url": "/sales/pricing-management",
"navigation": {
"targetUrl": "/sales/price",
"urlPattern": "/sales/price|/ko/sales/price",
"targetUrl": "/sales/pricing-management",
"urlPattern": "/sales/pricing-management|/ko/sales/pricing-management",
"menuHints": ["단가관리", "단가 관리", "판매관리"]
},
"menuNavigation": {
"level1": "판매관리",
"level2": "단가관리",
"expectedUrl": "/sales/price",
"expectedUrl": "/sales/pricing-management",
"searchWithinParent": true,
"closeOtherMenus": true
},
@@ -37,8 +37,8 @@
"text": "단가관리",
"waitAfterClick": 300
},
"fallbackUrl": "/sales/price",
"expectedUrl": "/sales/price"
"fallbackUrl": "/sales/pricing-management",
"expectedUrl": "/sales/pricing-management"
},
"timeout": 90000,
"tags": ["sales", "price", "crud"],
@@ -92,12 +92,12 @@
{ "type": "click_if_exists", "target": "단가관리" }
],
"expect": {
"url": "/sales/price",
"url": "/sales/pricing-management",
"visible": ["단가 목록", "품목 마스터 동기화"]
},
"fallback": {
"type": "navigate",
"url": "/sales/price"
"url": "/sales/pricing-management"
}
},
{
@@ -186,7 +186,7 @@
"toast": ["등록", "저장", "완료", "성공"]
},
"verify": {
"apiCall": "POST /api/sales/price"
"apiCall": "POST /api/sales/pricing-management"
}
},
{
@@ -269,7 +269,7 @@
"assertions": [
{
"type": "url",
"expected": "/sales/price",
"expected": "/sales/pricing-management",
"message": "단가관리 페이지에 머물러야 함"
},
{

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "production-dashboard",
"name": "생산 현황판 테스트",
"screenshotPolicy": {

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "production-item",
"name": "생산품목관리 테스트",
"screenshotPolicy": {

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "production-work-order",
"name": "작업지시 관리 테스트",
"screenshotPolicy": {

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "production-work-result",
"name": "작업실적 테스트",
"screenshotPolicy": {

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "production-worker",
"name": "작업자 화면 테스트",
"screenshotPolicy": {

View File

@@ -1,264 +0,0 @@
{
"enabled": false,
"id": "purchase-client",
"name": "구매거래처관리 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": [
"error",
"fail",
"timeout",
"404",
"500",
"blocked"
]
},
"description": "구매관리 > 거래처관리 메뉴의 구매 거래처 CRUD 기능 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"menuNavigation": {
"level1": "구매관리",
"level2": "거래처관리",
"expectedUrl": "/purchase/supplier-management",
"searchWithinParent": true,
"closeOtherMenus": true
},
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"testData": {
"create": {
"supplierName": "E2E_TEST_구매처_{timestamp}",
"businessNumber": "123-45-67890",
"representative": "테스트 대표"
}
},
"steps": [
{
"id": 1,
"name": "메뉴 진입: 구매관리 > 거래처관리",
"action": "menu_navigate",
"level1": "구매관리",
"level2": "거래처관리",
"expected": {
"url_contains": "/purchase",
"visible": [
"거래처관리",
"거래처"
]
}
},
{
"id": 2,
"name": "필수 검증 #5: 목업 페이지 감지",
"action": "verify_not_mockup",
"checks": [
"거래처 목록 표시",
"거래처 등록 버튼 존재",
"검색 기능 존재"
],
"expected": "정상 페이지 (목업 아님)"
},
{
"id": 3,
"name": "거래처 테이블 구조 확인",
"action": "verify_table",
"checks": [
"거래처명 컬럼",
"사업자번호 컬럼",
"대표자 컬럼",
"연락처 컬럼"
],
"expected": "거래처 테이블 표시"
},
{
"id": 4,
"phase": "CREATE",
"name": "[CREATE] 거래처 등록 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('등록'), button:has-text('추가'), button:has-text('신규')",
"expected": {
"modal_open": true
}
},
{
"id": 5,
"phase": "CREATE",
"name": "[CREATE] 거래처명 입력",
"action": "click_if_exists",
"target": "input[name*='name'], input[placeholder*='거래처명']"
},
{
"id": 6,
"phase": "CREATE",
"name": "[CREATE] 사업자번호 입력",
"action": "click_if_exists",
"target": "input[name*='business'], input[placeholder*='사업자']"
},
{
"id": 7,
"phase": "CREATE",
"name": "[CREATE] 대표자명 입력",
"action": "click_if_exists",
"target": "input[name*='representative'], input[placeholder*='대표']"
},
{
"id": 8,
"phase": "CREATE",
"name": "[CREATE] 필수 검증 #2: 거래처 저장",
"action": "click_if_exists",
"target": "button:has-text('저장'), button:has-text('등록'), button:has-text('확인')",
"verify": {
"url_maintained": true,
"no_error_page": true,
"api_call": "POST /api/v1/purchase/suppliers",
"toast": "등록|저장|완료|성공"
},
"expected": "거래처 등록 완료"
},
{
"id": 9,
"phase": "READ",
"name": "[READ] 등록된 거래처 검색",
"action": "click_if_exists",
"target": "input[type='search'], input[placeholder*='검색']"
},
{
"id": 10,
"phase": "READ",
"name": "[READ] 등록된 거래처 확인",
"action": "verify_detail",
"checks": [
"E2E_TEST_구매처 목록에 표시"
],
"expected": "등록된 거래처 확인"
},
{
"id": 11,
"phase": "READ",
"name": "[READ] 거래처 상세 조회",
"action": "click_if_exists",
"target": "table tbody tr:has-text('E2E_TEST')",
"expected": {
"detail_view": true
}
},
{
"id": 12,
"phase": "UPDATE",
"name": "[UPDATE] 거래처 수정 모드 진입",
"action": "click_if_exists",
"target": "button:has-text('수정'), button:has-text('편집')",
"expected": {
"edit_mode": true
}
},
{
"id": 13,
"phase": "UPDATE",
"name": "[UPDATE] 대표자명 수정",
"action": "click_if_exists",
"target": "input[name*='representative'], input[placeholder*='대표']"
},
{
"id": 14,
"phase": "UPDATE",
"name": "[UPDATE] 거래처 저장",
"action": "click_if_exists",
"target": "button:has-text('저장'), button:has-text('확인')",
"verify": {
"api_call": "PUT /api/v1/purchase/suppliers",
"toast": "수정|저장|완료|성공"
},
"expected": "거래처 수정 완료"
},
{
"id": 15,
"phase": "DELETE",
"name": "[DELETE] 거래처 삭제",
"action": "click_if_exists",
"target": "button:has-text('삭제'), button:has-text('제거')",
"expected": {
"confirm_dialog": true
}
},
{
"id": 16,
"phase": "DELETE",
"name": "[DELETE] 삭제 확인",
"action": "click_if_exists",
"target": "[role='alertdialog'] button:has-text('확인'), [role='dialog'] button:has-text('삭제')",
"verify": {
"api_call": "DELETE /api/v1/purchase/suppliers",
"toast": "삭제|제거|완료|성공"
},
"expected": "거래처 삭제 완료"
},
{
"id": 17,
"phase": "DELETE",
"name": "[DELETE] 삭제 확인",
"action": "verify_detail",
"checks": [
"E2E_TEST_구매처 목록에서 제거"
],
"expected": "거래처 삭제 반영"
},
{
"id": 18,
"name": "엑셀 다운로드 확인",
"action": "verify_elements",
"checks": [
"엑셀 다운로드 버튼 존재"
],
"expected": "엑셀 다운로드 기능 표시"
}
],
"expectedAPIs": [
{
"method": "GET",
"endpoint": "/api/v1/purchase/suppliers",
"description": "거래처 목록 조회"
},
{
"method": "POST",
"endpoint": "/api/v1/purchase/suppliers",
"description": "거래처 등록"
},
{
"method": "PUT",
"endpoint": "/api/v1/purchase/suppliers/:id",
"description": "거래처 수정"
},
{
"method": "DELETE",
"endpoint": "/api/v1/purchase/suppliers/:id",
"description": "거래처 삭제"
}
],
"requiredVerifications": [
{
"id": 2,
"name": "저장 버튼",
"steps": [
8,
14
],
"criteria": "API 호출 + 성공 토스트 + 데이터 반영"
},
{
"id": 5,
"name": "목업 페이지 감지",
"steps": [
2
],
"criteria": "거래처 목록, 등록 버튼, 검색 기능 존재"
}
],
"rollbackPlan": {
"onCreateFail": "등록 모달 닫고 재시도",
"onUpdateFail": "페이지 새로고침 후 재시도",
"onDeleteFail": "수동 삭제 필요",
"cleanupRequired": "E2E_TEST_구매처_* 패턴 데이터 삭제"
}
}

View File

@@ -1,331 +0,0 @@
{
"enabled": false,
"id": "purchase-order",
"name": "발주관리 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": [
"error",
"fail",
"timeout",
"404",
"500",
"blocked"
]
},
"description": "구매관리 > 발주관리 메뉴의 발주 조회/등록/수정/삭제 전체 CRUD 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"menuNavigation": {
"level1": "구매관리",
"level2": "발주관리",
"expectedUrl": "/purchase/purchase-order",
"searchWithinParent": true,
"closeOtherMenus": true
},
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"testData": {
"create": {
"vendorName": "E2E_TEST_거래처",
"itemName": "테스트품목",
"quantity": "300",
"unitPrice": "5000",
"deliveryDate": "2026-02-20",
"memo": "E2E 자동화 테스트 발주"
},
"update": {
"quantity": "350",
"memo": "E2E 수정된 발주 메모"
}
},
"steps": [
{
"id": 1,
"name": "메뉴 진입: 구매관리 > 발주관리",
"action": "menu_navigate",
"level1": "구매관리",
"level2": "발주관리",
"expected": {
"url_contains": "/purchase",
"visible": [
"발주관리",
"발주"
]
}
},
{
"id": 2,
"name": "필수 검증 #5: 목업 페이지 감지",
"action": "verify_not_mockup",
"checks": [
"발주 목록 표시",
"발주 등록 버튼 존재",
"검색/필터 기능 존재"
],
"expected": "정상 페이지 (목업 아님)"
},
{
"id": 3,
"name": "발주 테이블 구조 확인",
"action": "verify_table",
"checks": [
"발주번호 컬럼",
"거래처 컬럼",
"품목 컬럼",
"수량 컬럼",
"금액 컬럼",
"상태 컬럼"
],
"expected": "발주 테이블 컬럼 정상 표시"
},
{
"id": 4,
"name": "검색 기능 테스트",
"action": "click_if_exists",
"target": "input[placeholder*='검색']",
"value": "테스트",
"expected": {
"data_filtered": true
}
},
{
"id": 5,
"phase": "CREATE",
"name": "[CREATE] 발주 등록 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('등록'), button:has-text('발주 등록'), button:has-text('추가')",
"expected": {
"modal_or_page": true,
"title": "발주 등록"
}
},
{
"id": 6,
"phase": "CREATE",
"name": "[CREATE] 발주 정보 입력",
"action": "click_if_exists",
"target": "form, [role=\"dialog\"], .modal"
},
{
"id": 7,
"phase": "CREATE",
"name": "[CREATE] 필수 검증 #2: 등록 저장",
"action": "click_if_exists",
"target": "button:has-text('저장'), button:has-text('등록')",
"verify": {
"url_maintained": true,
"no_error_page": true,
"api_call": "POST /api/v1/purchase-orders",
"toast": "등록|완료|성공"
},
"expected": "발주 등록 완료"
},
{
"id": "7-modal-close",
"phase": "CREATE",
"name": "[CREATE] 모달 닫기 확인",
"action": "close_modal_if_open",
"expected": "모달 닫힘"
},
{
"id": 8,
"phase": "CREATE",
"name": "[CREATE] 등록 결과 확인",
"action": "verify_detail",
"search": "E2E 자동화 테스트 발주",
"expected": {
"row_exists": true,
"contains": [
"E2E",
"300",
"1,500,000"
]
}
},
{
"id": 9,
"phase": "READ",
"name": "[READ] 발주 상세 페이지 진입",
"action": "click_if_exists",
"target": "table tbody tr:has-text('E2E')",
"expected": {
"url_contains": "/purchase",
"visible": [
"발주 상세",
"수정",
"삭제"
]
}
},
{
"id": 10,
"phase": "READ",
"name": "[READ] 상세 정보 확인",
"action": "verify_detail",
"checks": [
"거래처: E2E_TEST_거래처",
"수량: 300",
"금액: 1,500,000",
"납기일: 2026-02-20"
],
"expected": "입력한 데이터와 일치"
},
{
"id": 11,
"phase": "UPDATE",
"name": "[UPDATE] 수정 모드 진입",
"action": "click_if_exists",
"target": "button:has-text('수정')",
"expected": {
"url_contains": "mode=edit",
"fields_editable": true
}
},
{
"id": 12,
"phase": "UPDATE",
"name": "[UPDATE] 수량 수정",
"action": "click_if_exists",
"target": "input[name*='quantity'], input[placeholder*='수량']"
},
{
"id": 13,
"phase": "UPDATE",
"name": "[UPDATE] 메모 수정",
"action": "click_if_exists",
"target": "textarea[name*='memo'], input[placeholder*='메모']"
},
{
"id": 14,
"phase": "UPDATE",
"name": "[UPDATE] 필수 검증 #2: 수정 저장",
"action": "click_if_exists",
"target": "button:has-text('저장')",
"verify": {
"url_maintained": true,
"no_error_page": true,
"api_call": "PUT /api/v1/purchase-orders/",
"toast": "수정|완료|성공"
},
"expected": "수정 완료"
},
{
"id": 15,
"phase": "UPDATE",
"name": "[UPDATE] 수정 결과 확인",
"action": "verify_detail",
"checks": [
"수량: 350",
"금액: 1,750,000"
],
"expected": "수정된 데이터 반영"
},
{
"id": 16,
"phase": "DELETE",
"name": "[DELETE] 삭제 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('삭제')",
"expected": {
"confirm_dialog": true,
"dialog_message": "삭제|정말"
}
},
{
"id": 17,
"phase": "DELETE",
"name": "[DELETE] 필수 검증 #6: 삭제 확인",
"action": "click_if_exists",
"target": "button:has-text('확인'), button:has-text('삭제')",
"verify": {
"api_call": "DELETE /api/v1/purchase-orders/",
"toast": "삭제|완료|성공",
"redirect": "/purchase"
},
"expected": "삭제 완료 및 목록 복귀"
},
{
"id": 18,
"phase": "DELETE",
"name": "[DELETE] 삭제 결과 확인",
"action": "verify_detail",
"search": "E2E 수정된 발주",
"expected": {
"row_exists": false,
"message": "테스트 발주가 목록에서 제거됨"
}
}
],
"expectedAPIs": [
{
"method": "GET",
"endpoint": "/api/v1/purchase-orders",
"description": "발주 목록 조회"
},
{
"method": "POST",
"endpoint": "/api/v1/purchase-orders",
"description": "발주 등록"
},
{
"method": "GET",
"endpoint": "/api/v1/purchase-orders/{id}",
"description": "발주 상세 조회"
},
{
"method": "PUT",
"endpoint": "/api/v1/purchase-orders/{id}",
"description": "발주 수정"
},
{
"method": "DELETE",
"endpoint": "/api/v1/purchase-orders/{id}",
"description": "발주 삭제"
}
],
"requiredVerifications": [
{
"id": 2,
"name": "등록/저장 버튼",
"steps": [
7,
14
],
"criteria": "API 호출 + 성공 토스트 + 데이터 반영"
},
{
"id": 3,
"name": "검색/필터",
"steps": [
4
],
"criteria": "검색 기능 동작"
},
{
"id": 5,
"name": "목업 페이지 감지",
"steps": [
2
],
"criteria": "발주 목록, 등록 버튼, 필터 존재"
},
{
"id": 6,
"name": "삭제 기능",
"steps": [
16,
17,
18
],
"criteria": "DELETE API + 목록에서 제거"
}
],
"rollbackPlan": {
"onCreateFail": "모달 닫기",
"onUpdateFail": "테스트 발주 수동 삭제 필요",
"onDeleteFail": "테스트 발주 수동 삭제 필요",
"cleanupRequired": "E2E_TEST_ 접두사 발주는 테스트 데이터"
}
}

View File

@@ -1,270 +0,0 @@
{
"enabled": false,
"id": "purchase-pricing",
"name": "구매 단가관리 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": [
"error",
"fail",
"timeout",
"404",
"500",
"blocked"
]
},
"description": "구매관리 > 단가관리 메뉴의 구매 단가 CRUD 기능 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"menuNavigation": {
"level1": "구매관리",
"level2": "단가관리",
"expectedUrl": "/purchase/pricing-management",
"searchWithinParent": true,
"closeOtherMenus": true
},
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"testData": {
"create": {
"itemName": "E2E_TEST_품목_{timestamp}",
"price": "10000",
"unit": "EA"
}
},
"steps": [
{
"id": 1,
"name": "메뉴 진입: 구매관리 > 단가관리",
"action": "menu_navigate",
"level1": "구매관리",
"level2": "단가관리",
"expected": {
"url_contains": "/purchase",
"visible": [
"단가관리",
"단가"
]
}
},
{
"id": 2,
"name": "필수 검증 #5: 목업 페이지 감지",
"action": "verify_not_mockup",
"checks": [
"단가 목록 표시",
"단가 등록 버튼 존재",
"검색 기능 존재"
],
"expected": "정상 페이지 (목업 아님)"
},
{
"id": 3,
"name": "단가 테이블 구조 확인",
"action": "verify_table",
"checks": [
"품목 컬럼",
"거래처 컬럼",
"단가 컬럼",
"적용기간 컬럼"
],
"expected": "단가 테이블 표시"
},
{
"id": 4,
"phase": "CREATE",
"name": "[CREATE] 단가 등록 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('등록'), button:has-text('추가'), button:has-text('신규')",
"expected": {
"modal_open": true
}
},
{
"id": 5,
"phase": "CREATE",
"name": "[CREATE] 품목 선택",
"action": "click_if_exists",
"target": "select[name*='item'], button:has-text('품목'), input[placeholder*='품목']",
"expected": "품목 선택 가능"
},
{
"id": 6,
"phase": "CREATE",
"name": "[CREATE] 거래처 선택",
"action": "click_if_exists",
"target": "select[name*='supplier'], button:has-text('거래처'), input[placeholder*='거래처']",
"expected": "거래처 선택 가능"
},
{
"id": 7,
"phase": "CREATE",
"name": "[CREATE] 단가 입력",
"action": "click_if_exists",
"target": "input[name*='price'], input[placeholder*='단가']",
"value": "10000",
"clear": true
},
{
"id": 8,
"phase": "CREATE",
"name": "[CREATE] 필수 검증 #2: 단가 저장",
"action": "click_if_exists",
"target": "button:has-text('저장'), button:has-text('등록'), button:has-text('확인')",
"verify": {
"url_maintained": true,
"no_error_page": true,
"api_call": "POST /api/v1/purchase/pricing",
"toast": "등록|저장|완료|성공"
},
"expected": "단가 등록 완료"
},
{
"id": 9,
"phase": "READ",
"name": "[READ] 등록된 단가 확인",
"action": "verify_detail",
"checks": [
"등록한 단가 목록에 표시"
],
"expected": "등록된 단가 확인"
},
{
"id": 10,
"phase": "READ",
"name": "[READ] 단가 상세 조회",
"action": "click_if_exists",
"target": "table tbody tr:first-child",
"expected": {
"detail_view": true
}
},
{
"id": 11,
"name": "단가 상세 정보 확인",
"action": "verify_detail",
"checks": [
"품목 정보",
"거래처 정보",
"단가",
"적용기간"
],
"expected": "단가 상세 정보 표시"
},
{
"id": 12,
"phase": "UPDATE",
"name": "[UPDATE] 단가 수정 모드 진입",
"action": "click_if_exists",
"target": "button:has-text('수정'), button:has-text('편집')",
"expected": {
"edit_mode": true
}
},
{
"id": 13,
"phase": "UPDATE",
"name": "[UPDATE] 단가 수정",
"action": "click_if_exists",
"target": "input[name*='price'], input[placeholder*='단가']",
"value": "12000",
"clear": true
},
{
"id": 14,
"phase": "UPDATE",
"name": "[UPDATE] 단가 저장",
"action": "click_if_exists",
"target": "button:has-text('저장'), button:has-text('확인')",
"verify": {
"api_call": "PUT /api/v1/purchase/pricing",
"toast": "수정|저장|완료|성공"
},
"expected": "단가 수정 완료"
},
{
"id": 15,
"name": "단가 이력 조회",
"action": "verify_elements",
"checks": [
"단가 변동 이력 조회 가능"
],
"expected": "단가 이력 기능 확인"
},
{
"id": 16,
"name": "엑셀 다운로드",
"action": "click_if_exists",
"target": "button:has-text('엑셀'), button:has-text('Excel'), button:has-text('다운로드')",
"verify": {
"file_download": true
},
"expected": "엑셀 파일 다운로드"
},
{
"id": 17,
"name": "단가 비교 기능",
"action": "verify_elements",
"checks": [
"거래처별 단가 비교 기능"
],
"expected": "단가 비교 기능 확인"
},
{
"id": 18,
"name": "일괄 등록 기능",
"action": "verify_elements",
"checks": [
"엑셀 일괄 등록 버튼 존재 여부"
],
"expected": "일괄 등록 기능 확인"
}
],
"expectedAPIs": [
{
"method": "GET",
"endpoint": "/api/v1/purchase/pricing",
"description": "단가 목록 조회"
},
{
"method": "POST",
"endpoint": "/api/v1/purchase/pricing",
"description": "단가 등록"
},
{
"method": "PUT",
"endpoint": "/api/v1/purchase/pricing/:id",
"description": "단가 수정"
},
{
"method": "GET",
"endpoint": "/api/v1/purchase/pricing/history",
"description": "단가 이력 조회"
}
],
"requiredVerifications": [
{
"id": 2,
"name": "저장 버튼",
"steps": [
8,
14
],
"criteria": "API 호출 + 성공 토스트 + 데이터 반영"
},
{
"id": 5,
"name": "목업 페이지 감지",
"steps": [
2
],
"criteria": "단가 목록, 등록 버튼, 검색 기능 존재"
}
],
"rollbackPlan": {
"onCreateFail": "등록 모달 닫고 재시도",
"onUpdateFail": "페이지 새로고침 후 재시도",
"note": "단가 삭제는 일반적으로 비활성화 처리"
}
}

View File

@@ -1,220 +0,0 @@
{
"enabled": false,
"id": "purchase-status",
"name": "구매현황 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": [
"error",
"fail",
"timeout",
"404",
"500",
"blocked"
]
},
"description": "구매관리 > 구매현황 메뉴의 구매 현황 조회/필터/통계 기능 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"menuNavigation": {
"level1": "구매관리",
"level2": "구매현황",
"expectedUrl": "/purchase/status",
"searchWithinParent": true,
"closeOtherMenus": true
},
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"steps": [
{
"id": 1,
"name": "메뉴 진입: 구매관리 > 구매현황",
"action": "menu_navigate",
"level1": "구매관리",
"level2": "구매현황",
"expected": {
"url_contains": "/purchase",
"visible": [
"구매현황",
"구매"
]
}
},
{
"id": 2,
"name": "필수 검증 #5: 목업 페이지 감지",
"action": "verify_not_mockup",
"checks": [
"구매 현황 표시",
"기간 필터 존재",
"통계 또는 차트 존재"
],
"expected": "정상 페이지 (목업 아님)"
},
{
"id": 3,
"name": "구매현황 페이지 구조 확인",
"action": "verify_elements",
"checks": [
"구매 통계 카드",
"기간 선택 필터",
"구매 목록 테이블 또는 차트"
],
"expected": "구매현황 페이지 정상 표시"
},
{
"id": 4,
"phase": "READ",
"name": "[READ] 구매 통계 확인",
"action": "verify_detail",
"checks": [
"총 구매금액",
"구매 건수",
"평균 구매금액"
],
"expected": "구매 통계 표시"
},
{
"id": 5,
"phase": "FILTER",
"name": "[FILTER] 기간 필터 - 시작일",
"action": "click_if_exists",
"target": "input[type='date']:first-of-type, input[name*='start']"
},
{
"id": 6,
"phase": "FILTER",
"name": "[FILTER] 기간 필터 - 종료일",
"action": "click_if_exists",
"target": "input[type='date']:last-of-type, input[name*='end']"
},
{
"id": 7,
"phase": "FILTER",
"name": "[FILTER] 조회 실행",
"action": "click_if_exists",
"target": "button:has-text('조회'), button:has-text('검색')",
"expected": {
"data_loaded": true,
"api_call": "GET /api/v1/purchase/status"
}
},
{
"id": 8,
"name": "구매 현황 테이블 확인",
"action": "verify_table",
"checks": [
"발주일 컬럼",
"거래처 컬럼",
"품목 컬럼",
"금액 컬럼",
"상태 컬럼"
],
"expected": "구매 현황 테이블 표시"
},
{
"id": 9,
"name": "상태별 필터 확인",
"action": "verify_elements",
"checks": [
"진행중/완료/취소 상태 필터"
],
"expected": "상태 필터 표시"
},
{
"id": 10,
"name": "거래처별 통계 확인",
"action": "verify_elements",
"checks": [
"거래처별 구매금액 표시"
],
"expected": "거래처별 통계 표시"
},
{
"id": 11,
"name": "품목별 통계 확인",
"action": "verify_elements",
"checks": [
"품목별 구매금액 표시"
],
"expected": "품목별 통계 표시"
},
{
"id": 12,
"name": "월별 추이 차트 확인",
"action": "verify_elements",
"checks": [
"월별 구매 추이 차트"
],
"expected": "추이 차트 표시"
},
{
"id": 13,
"name": "필수 검증 #1: 엑셀 다운로드",
"action": "click_if_exists",
"target": "button:has-text('엑셀'), button:has-text('Excel'), button:has-text('다운로드')",
"verify": {
"api_call": "GET /api/v1/purchase/status/export",
"file_download": true
},
"expected": "엑셀 파일 다운로드"
},
{
"id": 14,
"name": "인쇄 기능 확인",
"action": "verify_elements",
"checks": [
"인쇄 버튼 존재"
],
"expected": "인쇄 기능 표시"
},
{
"id": 15,
"name": "전년 대비 비교 확인",
"action": "verify_elements",
"checks": [
"전년 동기 대비 증감 표시"
],
"expected": "비교 분석 표시"
}
],
"expectedAPIs": [
{
"method": "GET",
"endpoint": "/api/v1/purchase/status",
"description": "구매현황 조회"
},
{
"method": "GET",
"endpoint": "/api/v1/purchase/statistics",
"description": "구매 통계 조회"
},
{
"method": "GET",
"endpoint": "/api/v1/purchase/status/export",
"description": "구매현황 엑셀 다운로드"
}
],
"requiredVerifications": [
{
"id": 1,
"name": "엑셀 다운로드",
"steps": [
13
],
"criteria": "API 호출 + 파일 다운로드"
},
{
"id": 5,
"name": "목업 페이지 감지",
"steps": [
2
],
"criteria": "구매 현황, 기간 필터, 통계/차트 존재"
}
],
"rollbackPlan": {
"note": "조회 전용 페이지로 데이터 변경 없음"
}
}

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "quality-certification",
"name": "품질인정심사 시스템 테스트",
"screenshotPolicy": {
@@ -11,7 +11,7 @@
"menuNavigation": {
"level1": "품질관리",
"level2": "품질인정심사 시스템",
"expectedUrl": "/quality/certification",
"expectedUrl": "/quality/qms",
"searchWithinParent": true,
"closeOtherMenus": true
},

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "quality-inspection",
"name": "제품검사관리 테스트",
"screenshotPolicy": {

View File

@@ -1,622 +0,0 @@
{
"id": "rank-management",
"name": "설정 - 직급관리",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": [
"error",
"fail",
"timeout",
"404",
"500",
"blocked"
]
},
"url": "/ko/settings/ranks",
"navigation": {
"targetUrl": "/settings/ranks",
"urlPattern": "/settings/ranks|/ko/settings/ranks",
"menuHints": [
"직급관리",
"직급 관리",
"설정"
]
},
"menuNavigation": {
"level1": "설정",
"level2": "직급관리",
"expectedUrl": "/ko/settings/ranks",
"searchWithinParent": true,
"closeOtherMenus": true
},
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"menuNavigationEnhanced": {
"strategy": "scroll-and-search",
"description": "사이드바를 스크롤하며 메뉴를 찾고 클릭하여 404를 방지",
"level1": "설정",
"level2": "직급관리",
"alternativeLevel1Names": [
"설정",
"Settings",
"환경설정",
"시스템설정",
"관리"
],
"alternativeLevel2Names": [
"직급관리",
"직급 관리",
"Ranks",
"직급",
"Position Management"
],
"scrollConfig": {
"sidebarSelector": "nav, aside, [role='navigation'], .sidebar, #sidebar",
"menuItemSelector": "a, button, [role='menuitem'], [role='treeitem']",
"scrollStep": 200,
"maxScrollAttempts": 10,
"scrollDelay": 300
}
},
"expectedAPIs": [
{
"method": "GET",
"path": "/api/v1/positions?type=rank",
"description": "직급 목록 조회"
},
{
"method": "POST",
"path": "/api/v1/positions",
"description": "직급 생성"
},
{
"method": "PUT",
"path": "/api/v1/positions/{id}",
"description": "직급 수정"
},
{
"method": "DELETE",
"path": "/api/v1/positions/{id}",
"description": "직급 삭제"
},
{
"method": "PUT",
"path": "/api/v1/positions/reorder",
"description": "직급 순서 변경"
}
],
"steps": [
{
"id": "step-00",
"name": "사이드바 메뉴 전체 펼치기",
"description": "모두 펼치기 버튼을 클릭하여 전체 메뉴를 펼친 후 메뉴 탐색 준비",
"actions": [
{
"type": "evaluate",
"script": "document.querySelector('.sidebar-scroll')?.scrollTo({top:0,behavior:'instant'})"
},
{
"type": "wait",
"duration": 300
},
{
"type": "evaluate",
"script": "Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('모두 펼치기'))?.click()"
},
{
"type": "wait",
"duration": 2000
}
]
},
{
"id": "step-01",
"name": "2단계 메뉴 진입: 설정 > 직급관리",
"description": "사이드바를 스크롤하며 설정 > 직급관리 메뉴를 찾아 클릭",
"actions": [
{
"type": "scrollAndFind",
"target": "설정",
"alternativeTexts": [
"설정",
"Settings",
"환경설정",
"시스템설정"
],
"scrollContainer": "sidebar",
"maxAttempts": 10,
"description": "스크롤하며 설정 메뉴 찾기"
},
{
"type": "click_if_exists",
"target": "설정",
"description": "설정 메뉴 클릭"
},
{
"type": "wait",
"duration": 500,
"description": "서브메뉴 펼쳐지기 대기"
},
{
"type": "scrollAndFind",
"target": "직급관리",
"alternativeTexts": [
"직급관리",
"직급 관리",
"Ranks",
"직급"
],
"scrollContainer": "submenu",
"maxAttempts": 5,
"description": "서브메뉴에서 직급관리 찾기"
},
{
"type": "click_if_exists",
"target": "직급관리",
"description": "직급관리 메뉴 클릭"
},
{
"type": "wait",
"target": "페이지 로드 완료",
"timeout": 10000
}
],
"expected": {
"url": "/ko/settings/ranks",
"title": "직급관리",
"authenticated": true
},
"verification": [
"설정 메뉴가 펼쳐졌는지 확인",
"직급관리 서브메뉴 클릭 성공",
"페이지 타이틀 '직급관리' 확인",
"설명 '사원의 직급을 관리합니다. 드래그하여 순서를 변경할 수 있습니다.' 확인",
"Award 아이콘 표시",
"404 에러 없이 페이지 로드 완료"
]
},
{
"id": "step-02",
"name": "직급 추가 입력 영역 확인",
"action": "verify",
"verification": [
"입력 필드 존재 (placeholder: '직급명을 입력하세요')",
"추가 버튼 존재 (disabled 상태)",
"Plus 아이콘 표시"
]
},
{
"id": "step-03",
"name": "직급 목록 카드 확인",
"action": "verify",
"verification": [
"직급 목록 카드 표시",
"기존 직급 목록 로드 확인",
"각 직급 항목 구조: 드래그 핸들, 순서 번호, 직급명, 수정 버튼, 삭제 버튼"
]
},
{
"id": "step-04",
"name": "드래그 핸들 아이콘 확인",
"action": "verify",
"verification": [
"각 직급 항목에 GripVertical 아이콘 표시",
"순서 번호 표시 (1, 2, 3...)",
"cursor-move 스타일 적용 확인"
]
},
{
"id": "step-05",
"name": "안내 문구 확인",
"action": "verify",
"verification": [
"하단 안내 문구 표시: '※ 직급 순서는 드래그 앤 드롭으로 변경할 수 있습니다.'"
]
},
{
"id": "step-06",
"name": "직급 추가 - 빈 값 입력 시도",
"action": "click_if_exists",
"target": "직급명 입력 필드",
"value": "",
"verification": [
"추가 버튼 disabled 상태 유지",
"입력 불가 확인"
]
},
{
"id": "step-07",
"name": "직급 추가 - 공백만 입력 시도",
"action": "click_if_exists",
"target": "직급명 입력 필드",
"value": " ",
"verification": [
"추가 버튼 클릭 가능하나 실제 추가 안됨",
"입력 필드 값 유지 또는 초기화"
]
},
{
"id": "step-08",
"name": "직급 추가 - 정상 입력",
"action": "click_if_exists",
"target": "직급명 입력 필드",
"value": "E2E 테스트 직급1",
"verification": [
"입력 값 반영 확인",
"추가 버튼 활성화 (enabled)"
]
},
{
"id": "step-09",
"name": "직급 추가 실행 (버튼 클릭)",
"action": "click_if_exists",
"target": "추가 버튼",
"verification": [
"추가 버튼 클릭",
"로딩 상태 표시 (Loader2 아이콘)",
"API 호출: POST /api/v1/positions",
"API 응답 200 OK 확인",
"성공 토스트 메시지: '직급이 추가되었습니다.'",
"입력 필드 초기화",
"추가 버튼 다시 disabled 상태",
"목록에 신규 직급 표시 (하단에 추가)"
]
},
{
"id": "step-10",
"name": "신규 직급 확인",
"action": "verify",
"verification": [
"'E2E 테스트 직급1' 목록에 표시",
"순서 번호 자동 할당 (마지막 순서)",
"드래그 핸들, 수정/삭제 버튼 존재"
]
},
{
"id": "step-11",
"name": "직급 추가 - Enter 키로 등록",
"action": "click_if_exists",
"target": "직급명 입력 필드",
"value": "E2E 테스트 직급2",
"verification": [
"입력 값 반영 확인"
]
},
{
"id": "step-12",
"name": "Enter 키 입력",
"action": "keypress",
"target": "직급명 입력 필드",
"key": "Enter",
"verification": [
"Enter 키로 직급 추가 실행",
"API 호출: POST /api/v1/positions",
"성공 토스트 메시지 확인",
"입력 필드 초기화",
"'E2E 테스트 직급2' 목록에 추가"
]
},
{
"id": "step-13",
"name": "세 번째 직급 추가 (드래그 테스트용)",
"action": "click_if_exists",
"target": "직급명 입력 필드",
"value": "E2E 테스트 직급3",
"verification": [
"입력 후 추가 버튼 클릭",
"API 호출 및 성공 확인",
"'E2E 테스트 직급3' 목록에 추가"
]
},
{
"id": "step-14",
"name": "직급 목록 상태 확인",
"action": "verify",
"verification": [
"총 N개 직급 표시 (기존 + 신규 3개)",
"각 직급의 순서 번호 연속적 (1, 2, 3...)",
"신규 직급들이 하단에 순서대로 추가됨"
]
},
{
"id": "step-15",
"name": "직급 수정 다이얼로그 열기",
"action": "click_if_exists",
"target": "E2E 테스트 직급1의 수정 버튼",
"verification": [
"수정 다이얼로그 표시",
"다이얼로그 제목: '직급 수정'",
"직급명 입력 필드에 현재 값 표시: 'E2E 테스트 직급1'",
"취소 버튼 존재",
"수정 버튼 존재"
]
},
{
"id": "step-16",
"name": "직급명 수정 입력",
"action": "click_if_exists",
"target": "다이얼로그 직급명 입력 필드",
"value": "E2E 테스트 직급1 (수정됨)",
"verification": [
"입력 값 반영 확인",
"수정 버튼 활성화"
]
},
{
"id": "step-17",
"name": "직급 수정 실행",
"action": "click_if_exists",
"target": "다이얼로그 수정 버튼",
"verification": [
"수정 버튼 클릭",
"로딩 상태 표시",
"API 호출: PUT /api/v1/positions/{id}",
"API 응답 200 OK 확인",
"성공 토스트 메시지: '직급이 수정되었습니다.'",
"다이얼로그 닫힘",
"목록에서 직급명 변경 확인: 'E2E 테스트 직급1 (수정됨)'"
]
},
{
"id": "step-18",
"name": "수정 취소 테스트 - 다이얼로그 열기",
"action": "click_if_exists",
"target": "E2E 테스트 직급2의 수정 버튼",
"verification": [
"다이얼로그 표시",
"현재 값: 'E2E 테스트 직급2'"
]
},
{
"id": "step-19",
"name": "수정 취소",
"action": "click_if_exists",
"target": "다이얼로그 취소 버튼",
"verification": [
"다이얼로그 닫힘",
"직급명 변경 없음 (API 호출 없음)"
]
},
{
"id": "step-20",
"name": "드래그 앤 드롭 - 첫 번째 항목 선택",
"action": "drag_start",
"target": "목록 마지막 직급 (E2E 테스트 직급3)",
"verification": [
"드래그 시작",
"해당 항목 opacity 50% 및 배경색 변경",
"cursor-move 활성화"
]
},
{
"id": "step-21",
"name": "드래그 앤 드롭 - 상단으로 이동",
"action": "drag_over",
"target": "목록 첫 번째 위치",
"verification": [
"드래그 중 위치 변경 시각적 피드백",
"실시간 순서 변경 (로컬 상태)"
]
},
{
"id": "step-22",
"name": "드래그 앤 드롭 - 드롭 실행",
"action": "drag_end",
"verification": [
"드래그 종료",
"API 호출: PUT /api/v1/positions/reorder",
"Request body에 전체 직급의 새로운 순서 포함",
"API 응답 200 OK 확인",
"성공 토스트 메시지: '순서가 변경되었습니다.'",
"순서 번호 업데이트 확인",
"'E2E 테스트 직급3'이 최상단으로 이동"
]
},
{
"id": "step-23",
"name": "순서 변경 확인",
"action": "verify",
"verification": [
"변경된 순서가 화면에 반영됨",
"각 항목의 순서 번호 재할당 (1, 2, 3...)",
"드래그한 항목이 목표 위치에 정확히 배치"
]
},
{
"id": "step-24",
"name": "삭제 확인 다이얼로그 열기",
"action": "click_if_exists",
"target": "E2E 테스트 직급3의 삭제 버튼",
"verification": [
"삭제 확인 다이얼로그 표시",
"다이얼로그 제목: '직급 삭제'",
"메시지: '\"E2E 테스트 직급3\" 직급을 삭제하시겠습니까?'",
"경고 메시지: '이 직급을 사용 중인 사원이 있으면 해당 사원의 직급이 초기화됩니다.' (빨간색)",
"취소 버튼 존재",
"삭제 버튼 존재 (빨간색)"
]
},
{
"id": "step-25",
"name": "삭제 취소",
"action": "click_if_exists",
"target": "다이얼로그 취소 버튼",
"verification": [
"다이얼로그 닫힘",
"직급 삭제되지 않음 (API 호출 없음)",
"목록에 여전히 존재"
]
},
{
"id": "step-26",
"name": "삭제 실행 - 다이얼로그 재열기",
"action": "click_if_exists",
"target": "E2E 테스트 직급3의 삭제 버튼",
"verification": [
"삭제 확인 다이얼로그 표시"
]
},
{
"id": "step-27",
"name": "삭제 확인 실행",
"action": "click_if_exists",
"target": "다이얼로그 삭제 버튼",
"verification": [
"삭제 버튼 클릭",
"로딩 상태 표시 (Loader2 아이콘)",
"API 호출: DELETE /api/v1/positions/{id}",
"API 응답 200 OK 확인",
"성공 토스트 메시지: '직급이 삭제되었습니다.'",
"다이얼로그 닫힘",
"목록에서 'E2E 테스트 직급3' 제거됨",
"순서 번호 재정렬"
]
},
{
"id": "step-28",
"name": "나머지 테스트 직급 삭제 - 직급2",
"action": "click_if_exists",
"target": "E2E 테스트 직급2의 삭제 버튼",
"verification": [
"삭제 다이얼로그 표시 → 삭제 버튼 클릭",
"API 호출 및 성공 확인",
"목록에서 제거"
]
},
{
"id": "step-29",
"name": "나머지 테스트 직급 삭제 - 직급1 (수정됨)",
"action": "click_if_exists",
"target": "E2E 테스트 직급1 (수정됨)의 삭제 버튼",
"verification": [
"삭제 다이얼로그 표시 → 삭제 버튼 클릭",
"API 호출 및 성공 확인",
"목록에서 제거",
"테스트 데이터 완전 정리"
]
},
{
"id": "step-30",
"name": "최종 상태 확인",
"action": "verify",
"verification": [
"기존 직급만 남음 (테스트 데이터 모두 삭제)",
"순서 번호 정상",
"페이지 정상 동작"
]
},
{
"id": "step-31",
"name": "빈 목록 상태 테스트 (선택)",
"action": "verify",
"verification": [
"만약 모든 직급 삭제 시: '등록된 직급이 없습니다.' 메시지 표시",
"입력 필드와 추가 버튼은 정상 표시"
]
},
{
"id": "step-32",
"name": "페이지 새로고침 후 데이터 확인",
"action": "reload",
"verification": [
"페이지 새로고침",
"GET /api/v1/positions?type=rank 호출",
"저장된 순서대로 직급 목록 로드",
"이전 상태 유지 확인"
]
},
{
"id": "step-33",
"name": "한글 IME 입력 테스트",
"action": "click_if_exists",
"target": "직급명 입력 필드",
"value": "부장",
"verification": [
"한글 조합 중 Enter 키 입력 시 조합 완료 대기",
"조합 완료 후 추가 동작",
"isComposing 이벤트 처리 확인"
]
},
{
"id": "step-34",
"name": "특수문자 입력 테스트",
"action": "click_if_exists",
"target": "직급명 입력 필드",
"value": "직급@#$%",
"verification": [
"특수문자 포함 직급명 입력",
"추가 버튼 클릭",
"API 호출 및 저장 여부 확인",
"성공 시 목록에 표시, 실패 시 에러 메시지"
]
},
{
"id": "step-35",
"name": "긴 직급명 입력 테스트",
"action": "click_if_exists",
"target": "직급명 입력 필드",
"value": "매우긴직급명테스트매우긴직급명테스트매우긴직급명테스트매우긴직급명테스트",
"verification": [
"긴 직급명 입력",
"추가 시도",
"API 응답 확인 (길이 제한 있을 경우 에러)",
"UI에서 텍스트 오버플로우 처리 확인"
]
},
{
"id": "step-36",
"name": "중복 직급명 입력 테스트",
"action": "click_if_exists",
"target": "직급명 입력 필드",
"value": "과장",
"verification": [
"기존 직급명과 동일한 이름 입력",
"추가 버튼 클릭",
"API 응답 확인",
"중복 허용 시 성공, 중복 불가 시 에러 메시지"
]
},
{
"id": "step-37",
"name": "로딩 중 상태 테스트",
"action": "verify",
"verification": [
"API 호출 중 버튼 disabled 상태",
"Loader2 아이콘 표시",
"중복 클릭 방지 확인"
]
},
{
"id": "step-38",
"name": "에러 처리 테스트 (네트워크 오류 시뮬레이션)",
"action": "verify",
"verification": [
"API 호출 실패 시 에러 토스트 표시",
"에러 메시지 명확성 확인",
"페이지 상태 유지 (크래시 없음)"
]
}
],
"testData": {
"ranks": [
{
"name": "E2E 테스트 직급1",
"expected_order": "last"
},
{
"name": "E2E 테스트 직급2",
"expected_order": "last"
},
{
"name": "E2E 테스트 직급3",
"expected_order": "last → first (after drag)"
}
]
},
"cleanup": {
"description": "Step-27~29에서 테스트 데이터 삭제 완료",
"method": "각 직급의 삭제 버튼 클릭 → 확인 다이얼로그에서 삭제 확인"
}
}

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "receiving-management",
"name": "입고관리 테스트",
"screenshotPolicy": {
@@ -8,16 +8,16 @@
},
"description": "자재관리 > 입고관리 페이지의 입고 조회 및 상태별 필터링 기능을 테스트하는 E2E 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"url": "/material/receiving",
"url": "/material/receiving-management",
"navigation": {
"targetUrl": "/material/receiving",
"urlPattern": "/material/receiving|/ko/material/receiving",
"targetUrl": "/material/receiving-management",
"urlPattern": "/material/receiving-management|/ko/material/receiving-management",
"menuHints": ["입고관리", "입고 관리", "자재관리"]
},
"menuNavigation": {
"level1": "자재관리",
"level2": "입고관리",
"expectedUrl": "/material/receiving",
"expectedUrl": "/material/receiving-management",
"searchWithinParent": true,
"closeOtherMenus": true
},
@@ -38,8 +38,8 @@
"text": "입고관리",
"waitAfterClick": 300
},
"fallbackUrl": "/material/receiving",
"expectedUrl": "/material/receiving"
"fallbackUrl": "/material/receiving-management",
"expectedUrl": "/material/receiving-management"
},
"timeout": 90000,
"tags": ["material", "receiving", "read-only"],
@@ -84,12 +84,12 @@
{ "type": "click_if_exists", "target": "입고관리" }
],
"expect": {
"url": "/material/receiving",
"url": "/material/receiving-management",
"visible": ["입고 목록"]
},
"fallback": {
"type": "navigate",
"url": "/material/receiving"
"url": "/material/receiving-management"
}
},
{
@@ -162,7 +162,7 @@
"assertions": [
{
"type": "url",
"expected": "/material/receiving",
"expected": "/material/receiving-management",
"message": "입고관리 페이지에 머물러야 함"
},
{

View File

@@ -1,406 +0,0 @@
{
"id": "salary-management",
"name": "급여관리 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": ["error", "fail", "timeout", "404", "500", "blocked"]
},
"description": "급여 현황 조회, 상태 변경, 엑셀 다운로드 기능을 테스트하는 E2E 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"url": "/hr/salary-management",
"navigation": {
"targetUrl": "/hr/salary-management",
"urlPattern": "/hr/salary-management|/ko/hr/salary-management",
"menuHints": ["급여관리", "급여 관리", "인사관리"]
},
"menuNavigation": {
"level1": "인사관리",
"level2": "급여관리",
"expectedUrl": "/hr/salary-management",
"searchWithinParent": true,
"closeOtherMenus": true
},
"menuNavigationEnhanced": {
"strategy": "scroll-and-search",
"sidebar": {
"scrollContainer": ".sidebar-scroll",
"scrollStep": 200,
"maxScrollAttempts": 5,
"waitAfterScroll": 300
},
"level1": {
"text": "인사관리",
"expandable": true,
"waitAfterClick": 500
},
"level2": {
"text": "급여관리",
"waitAfterClick": 300
},
"fallbackUrl": "/hr/salary-management",
"expectedUrl": "/hr/salary-management"
},
"timeout": 90000,
"tags": ["hr", "salary", "payroll", "management"],
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"testData": {
"searchKeyword": "홍",
"dateRange": {
"startDate": "2025-12-01",
"endDate": "2025-12-31"
}
},
"steps": [
{
"id": "step-0",
"name": "사이드바 메뉴 전체 펼치기",
"description": "모두 펼치기 버튼을 클릭하여 전체 메뉴를 펼친 후 메뉴 탐색 준비",
"actions": [
{
"type": "evaluate",
"script": "document.querySelector('.sidebar-scroll')?.scrollTo({top:0,behavior:'instant'})"
},
{ "type": "wait", "duration": 300 },
{
"type": "evaluate",
"script": "Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('모두 펼치기'))?.click()"
},
{ "type": "wait", "duration": 2000 }
],
"expect": {
"sidebarReady": true
}
},
{
"id": "step-1",
"name": "인사관리 메뉴 진입",
"description": "인사관리 > 급여관리 메뉴로 이동 (scrollAndFind 패턴 사용)",
"actions": [
{
"type": "scrollAndFind",
"container": ".sidebar-scroll",
"target": "인사관리",
"scrollStep": 200,
"maxAttempts": 5
},
{ "type": "click_if_exists", "target": "인사관리" },
{ "type": "wait", "duration": 500 },
{
"type": "scrollAndFind",
"container": ".sidebar-scroll",
"target": "급여관리",
"scrollStep": 200,
"maxAttempts": 5
},
{ "type": "click_if_exists", "target": "급여관리" }
],
"expect": {
"url": "/hr/salary-management",
"visible": ["급여관리", "엑셀 다운로드"]
},
"fallback": {
"type": "navigate",
"url": "/ko/hr/salary-management"
}
},
{
"id": "step-2",
"name": "필수 검증 #5: 목업 페이지 감지",
"description": "페이지가 목업인지 실제 동작하는 페이지인지 감지",
"verify": {
"mockupDetection": {
"inputFields": ["검색창", "날짜 필터"],
"functionalButtons": ["엑셀 다운로드", "지급완료", "지급예정", "수정"],
"apiCalls": true,
"dataChangePossible": true
}
}
},
{
"id": "step-3",
"name": "급여 현황 대시보드 확인",
"description": "총 실지급액, 총 기본급, 총 수당, 초과근무, 상여, 총 공제 카드 표시 확인",
"verify": {
"visible": ["총 실지급액", "총 기본급", "총 수당", "초과근무", "상여", "총 공제"],
"statsCards": true
}
},
{
"id": "step-4",
"name": "급여 테이블 구조 확인",
"description": "테이블 컬럼 구조 검증",
"verify": {
"tableColumns": ["부서", "직책", "이름", "직급", "기본급", "수당", "초과근무", "상여", "공제", "실지급액", "일자", "상태", "작업"]
}
},
{
"id": "step-5",
"name": "날짜 필터 확인",
"description": "시작일/종료일 날짜 필터 필드 확인",
"verify": {
"dateInputs": 2,
"dateRange": {
"startDate": "2025-12-01",
"endDate": "2025-12-31"
}
}
},
{
"id": "step-5-1",
"name": "⚠️ 필수 검증: 날짜 필터 검색",
"description": "날짜 범위 필터를 설정하고 데이터가 필터링되는지 확인",
"actions": [
{ "type": "evaluate", "script": "document.querySelectorAll('table tbody tr').length", "description": "필터 전 행 수 저장" },
{ "type": "fill", "target": "input[type='date']:first-of-type, input[placeholder*='시작']", "value": "2025-12-01", "description": "시작일 입력" },
{ "type": "fill", "target": "input[type='date']:last-of-type, input[placeholder*='종료']", "value": "2025-12-31", "description": "종료일 입력" },
{ "type": "wait", "duration": 500, "description": "필터 적용 대기" },
{ "type": "evaluate", "script": "document.querySelectorAll('table tbody tr').length", "description": "필터 후 행 수 확인" }
],
"verify": {
"dateFilterApplied": true,
"dataChanged": "initialRowCount may differ from filteredRowCount"
},
"note": "날짜 필터가 실제로 데이터를 필터링하는지 확인 - 단순 UI 변경만 확인하면 FAIL"
},
{
"id": "step-6",
"name": "⚠️ 필수 검증: 검색 기능 확인",
"description": "검색어 입력 후 테이블 데이터가 필터링되는지 확인",
"actions": [
{ "type": "evaluate", "script": "document.querySelectorAll('table tbody tr').length", "description": "검색 전 행 수 확인" },
{ "type": "fill", "target": "input[placeholder*='검색'], input[type='search']", "value": "홍", "description": "검색어 입력" },
{ "type": "wait", "duration": 500, "description": "검색 결과 대기" },
{ "type": "evaluate", "script": "document.querySelectorAll('table tbody tr').length", "description": "검색 후 행 수 확인" }
],
"verify": {
"searchApplied": true,
"tableContains": "{testData.searchKeyword}",
"dataFiltered": "검색 결과에 검색어가 포함된 행만 표시되어야 함"
},
"expect": {
"searchPlaceholder": "이름, 부서 검색..."
},
"note": "⚠️ 검색어 입력 후 테이블 데이터가 변경되지 않으면 FAIL"
},
{
"id": "step-6-1",
"name": "검색 결과 데이터 검증",
"description": "검색 결과의 각 행에 검색어가 포함되어 있는지 확인",
"verify": {
"allRowsContain": "{testData.searchKeyword}",
"verifyMethod": "테이블의 모든 행이 검색어를 포함하는지 확인"
}
},
{
"id": "step-6-2",
"name": "검색 초기화 확인",
"description": "검색어 삭제 후 전체 목록 복원 확인",
"actions": [
{ "type": "clear", "target": "input[placeholder*='검색'], input[type='search']", "description": "검색어 삭제" },
{ "type": "wait", "duration": 500, "description": "목록 복원 대기" }
],
"verify": {
"dataRestored": true,
"rowCountRestored": "beforeSearchCount와 유사한 행 수로 복원"
}
},
{
"id": "step-7",
"name": "정렬 옵션 확인",
"description": "정렬 드롭다운 옵션 확인",
"actions": [
{ "type": "click_if_exists", "target": "정렬", "role": "combobox" }
],
"verify": {
"options": ["직급순", "이름순", "부서순", "지급일순", "지급액순"]
}
},
{
"id": "step-8",
"name": "급여 항목 선택",
"description": "체크박스로 급여 항목 선택",
"actions": [
{ "type": "evaluate", "script": "(function(){ var cb = document.querySelector('table tbody tr:first-child input[type=\"checkbox\"], table tbody tr:first-child [role=\"checkbox\"]'); if(cb){ cb.click(); return 'checked'; } return 'no checkbox'; })()" }
],
"expect": {
"visible": ["지급완료", "지급예정"],
"buttonsEnabled": true
}
},
{
"id": "step-9",
"name": "필수 검증 #2: 지급완료 버튼 동작 확인",
"description": "지급완료 버튼 클릭 시 실제 상태 변경 확인",
"actions": [
{ "type": "click_if_exists", "target": "지급완료" }
],
"expect": {
"urlMaintained": true,
"noErrorPage": true,
"toast": "지급완료 처리되었습니다",
"apiCall": "bulkUpdateSalaryStatus"
}
},
{
"id": "step-10",
"name": "수정 버튼 클릭 - 상세 다이얼로그 열기",
"description": "급여 항목의 수정 버튼 클릭하여 상세 다이얼로그 열기",
"actions": [
{ "type": "evaluate", "script": "(function(){ var btns = Array.from(document.querySelectorAll('table tbody tr:first-child button, table tbody tr:first-child a')); var editBtn = btns.find(b => b.innerText?.includes('수정') || b.querySelector('svg')); if(editBtn){ editBtn.click(); return 'clicked edit'; } var actionBtn = document.querySelector('table tbody tr:first-child td:last-child button'); if(actionBtn){ actionBtn.click(); return 'clicked action btn'; } return 'no edit btn found'; })()" },
{ "type": "wait", "duration": 500 }
],
"modalConfig": {
"containerSelector": "[role='dialog'], .modal",
"animationDelay": 300,
"waitForSelector": "[role='dialog']"
},
"expect": {
"modal": "급여 상세",
"visible": ["기본급", "수당", "초과근무", "상여", "공제", "실지급액"]
}
},
{
"id": "step-11",
"name": "필수 검증 #4: 상세 다이얼로그 저장",
"description": "모달 내 급여 상세 저장 버튼 동작 확인",
"actions": [
{ "type": "click_if_exists", "target": "저장", "options": { "waitAfter": 500 } }
],
"expect": {
"urlMaintained": true,
"noErrorPage": true,
"modalClosed": true,
"toast": "저장되었습니다"
}
},
{
"id": "step-12",
"name": "상세 다이얼로그 닫기",
"description": "다이얼로그 닫기",
"actions": [
{ "type": "press", "key": "Escape" }
],
"expect": {
"modalClosed": true
}
},
{
"id": "step-13",
"name": "필수 검증 #1: 엑셀 다운로드",
"description": "엑셀 다운로드 버튼 클릭 시 실제 다운로드 발생 확인",
"actions": [
{ "type": "click_if_exists", "target": "엑셀 다운로드" }
],
"verify": {
"networkRequest": {
"type": "click_if_exists",
"apiPattern": "/api/export|/api/download|/api/salary"
},
"downloadEvent": true
},
"note": "⚠️ Console LOG만 출력되면 FAIL - Network API 호출 필수 확인"
}
],
"assertions": [
{
"type": "url",
"expected": "/hr/salary-management",
"message": "급여관리 페이지에 머물러야 함"
},
{
"type": "elementExists",
"selector": "button:has-text('엑셀 다운로드')",
"message": "엑셀 다운로드 버튼이 표시되어야 함"
},
{
"type": "tableExists",
"message": "급여 목록 테이블이 표시되어야 함"
}
],
"mandatoryVerifications": {
"description": "E2E_TEST_CONFIG.md 기준 필수 검증 항목",
"items": [
{
"id": 1,
"name": "파일 다운로드",
"trigger": "엑셀 다운로드 버튼",
"verification": "Network API 호출 + 실제 파일 다운로드 확인",
"failCondition": "Console LOG만 존재, API 미호출"
},
{
"id": 2,
"name": "등록/저장 버튼",
"trigger": "지급완료, 지급예정, 저장 버튼",
"verification": "URL 유지 + 에러 페이지 없음 + 성공 토스트",
"failCondition": "404/500 에러 페이지 이동"
},
{
"id": 3,
"name": "검색/필터",
"trigger": "검색창, 날짜 필터, 정렬",
"verification": "데이터 변화 확인",
"failCondition": "필터 적용 후 데이터 무변화"
},
{
"id": 4,
"name": "모달 등록 완료",
"trigger": "급여 상세 다이얼로그 저장",
"verification": "실제 저장 동작 + 결과 확인",
"failCondition": "열기/닫기만 테스트"
},
{
"id": 5,
"name": "목업/미완성 페이지 감지",
"trigger": "페이지 로드 시",
"verification": "입력 필드 + 동작하는 버튼 + API 호출 확인",
"failCondition": "버튼만 있고 입력 불가, Console LOG만 출력"
}
]
},
"cleanup": {
"enabled": false,
"description": "조회/상태변경 테스트이므로 cleanup 불필요"
},
"notes": {
"testScope": "급여관리 페이지 UI 요소 및 기능 검증",
"features": {
"dashboard": "총 실지급액/기본급/수당/초과근무/상여/공제 현황 카드",
"dateFilter": "시작일~종료일 날짜 범위 필터",
"search": "이름, 부서 검색",
"sort": "직급순/이름순/부서순/지급일순/지급액순 정렬",
"statusChange": "지급완료/지급예정 일괄 상태 변경",
"detailDialog": "급여 상세 조회/수정 다이얼로그",
"export": "엑셀 다운로드"
},
"tableColumns": {
"부서": "소속 부서",
"직책": "직책명",
"이름": "직원 이름",
"직급": "직급명",
"기본급": "기본급 금액",
"수당": "수당 금액",
"초과근무": "초과근무 수당",
"상여": "상여금",
"공제": "공제 금액",
"실지급액": "실제 지급 금액",
"일자": "지급 일자",
"상태": "지급완료/지급예정",
"작업": "수정 버튼"
},
"knownIssues": [
"엑셀 다운로드 기능이 toast.info('준비 중') 상태일 수 있음 - 목업 가능성",
"지급항목 추가 기능이 미구현 상태"
],
"prerequisites": "로그인된 사용자에게 급여 관리 권한 필요"
}
}

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "sales-client",
"name": "판매거래처관리 테스트",
"screenshotPolicy": {
@@ -18,7 +18,7 @@
"menuNavigation": {
"level1": "판매관리",
"level2": "거래처관리",
"expectedUrl": "/sales/client-management",
"expectedUrl": "/sales/client-management-sales-admin",
"searchWithinParent": true,
"closeOtherMenus": true
},

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "sales-management",
"name": "매출관리 테스트",
"screenshotPolicy": {

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "sales-order",
"name": "수주관리 테스트",
"screenshotPolicy": {

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "sales-pricing",
"name": "단가관리 테스트",
"screenshotPolicy": {

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "sales-quotation",
"name": "견적관리 테스트",
"screenshotPolicy": {

View File

@@ -1,264 +0,0 @@
{
"enabled": false,
"id": "sales-site",
"name": "현장관리 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": [
"error",
"fail",
"timeout",
"404",
"500",
"blocked"
]
},
"description": "판매관리 > 현장관리 메뉴의 현장 CRUD 기능 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"menuNavigation": {
"level1": "판매관리",
"level2": "현장관리",
"expectedUrl": "/sales/site-management",
"searchWithinParent": true,
"closeOtherMenus": true
},
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"testData": {
"create": {
"siteName": "E2E_TEST_현장_{timestamp}",
"address": "테스트 주소",
"manager": "테스트 담당자"
}
},
"steps": [
{
"id": 1,
"name": "메뉴 진입: 판매관리 > 현장관리",
"action": "menu_navigate",
"level1": "판매관리",
"level2": "현장관리",
"expected": {
"url_contains": "/sales",
"visible": [
"현장관리",
"현장"
]
}
},
{
"id": 2,
"name": "필수 검증 #5: 목업 페이지 감지",
"action": "verify_not_mockup",
"checks": [
"현장 목록 표시",
"현장 등록 버튼 존재",
"검색 기능 존재"
],
"expected": "정상 페이지 (목업 아님)"
},
{
"id": 3,
"name": "현장 테이블 구조 확인",
"action": "verify_table",
"checks": [
"현장명 컬럼",
"주소 컬럼",
"담당자 컬럼",
"상태 컬럼"
],
"expected": "현장 테이블 표시"
},
{
"id": 4,
"phase": "CREATE",
"name": "[CREATE] 현장 등록 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('등록'), button:has-text('추가'), button:has-text('신규')",
"expected": {
"modal_open": true
}
},
{
"id": 5,
"phase": "CREATE",
"name": "[CREATE] 현장명 입력",
"action": "click_if_exists",
"target": "input[name*='name'], input[placeholder*='현장명']"
},
{
"id": 6,
"phase": "CREATE",
"name": "[CREATE] 주소 입력",
"action": "click_if_exists",
"target": "input[name*='address'], input[placeholder*='주소']"
},
{
"id": 7,
"phase": "CREATE",
"name": "[CREATE] 담당자 입력",
"action": "click_if_exists",
"target": "input[name*='manager'], input[placeholder*='담당']"
},
{
"id": 8,
"phase": "CREATE",
"name": "[CREATE] 필수 검증 #2: 현장 저장",
"action": "click_if_exists",
"target": "button:has-text('저장'), button:has-text('등록'), button:has-text('확인')",
"verify": {
"url_maintained": true,
"no_error_page": true,
"api_call": "POST /api/v1/sales/sites",
"toast": "등록|저장|완료|성공"
},
"expected": "현장 등록 완료"
},
{
"id": 9,
"phase": "READ",
"name": "[READ] 등록된 현장 검색",
"action": "click_if_exists",
"target": "input[type='search'], input[placeholder*='검색']"
},
{
"id": 10,
"phase": "READ",
"name": "[READ] 등록된 현장 확인",
"action": "verify_detail",
"checks": [
"E2E_TEST_현장 목록에 표시"
],
"expected": "등록된 현장 확인"
},
{
"id": 11,
"phase": "READ",
"name": "[READ] 현장 상세 조회",
"action": "click_if_exists",
"target": "table tbody tr:has-text('E2E_TEST')",
"expected": {
"detail_view": true
}
},
{
"id": 12,
"phase": "UPDATE",
"name": "[UPDATE] 현장 수정 모드 진입",
"action": "click_if_exists",
"target": "button:has-text('수정'), button:has-text('편집')",
"expected": {
"edit_mode": true
}
},
{
"id": 13,
"phase": "UPDATE",
"name": "[UPDATE] 담당자 수정",
"action": "click_if_exists",
"target": "input[name*='manager'], input[placeholder*='담당']"
},
{
"id": 14,
"phase": "UPDATE",
"name": "[UPDATE] 현장 저장",
"action": "click_if_exists",
"target": "button:has-text('저장'), button:has-text('확인')",
"verify": {
"api_call": "PUT /api/v1/sales/sites",
"toast": "수정|저장|완료|성공"
},
"expected": "현장 수정 완료"
},
{
"id": 15,
"phase": "DELETE",
"name": "[DELETE] 현장 삭제",
"action": "click_if_exists",
"target": "button:has-text('삭제'), button:has-text('제거')",
"expected": {
"confirm_dialog": true
}
},
{
"id": 16,
"phase": "DELETE",
"name": "[DELETE] 삭제 확인",
"action": "click_if_exists",
"target": "[role='alertdialog'] button:has-text('확인'), [role='dialog'] button:has-text('삭제')",
"verify": {
"api_call": "DELETE /api/v1/sales/sites",
"toast": "삭제|제거|완료|성공"
},
"expected": "현장 삭제 완료"
},
{
"id": 17,
"phase": "DELETE",
"name": "[DELETE] 삭제 확인",
"action": "verify_detail",
"checks": [
"E2E_TEST_현장 목록에서 제거"
],
"expected": "현장 삭제 반영"
},
{
"id": 18,
"name": "엑셀 다운로드 확인",
"action": "verify_elements",
"checks": [
"엑셀 다운로드 버튼 존재"
],
"expected": "엑셀 다운로드 기능 표시"
}
],
"expectedAPIs": [
{
"method": "GET",
"endpoint": "/api/v1/sales/sites",
"description": "현장 목록 조회"
},
{
"method": "POST",
"endpoint": "/api/v1/sales/sites",
"description": "현장 등록"
},
{
"method": "PUT",
"endpoint": "/api/v1/sales/sites/:id",
"description": "현장 수정"
},
{
"method": "DELETE",
"endpoint": "/api/v1/sales/sites/:id",
"description": "현장 삭제"
}
],
"requiredVerifications": [
{
"id": 2,
"name": "저장 버튼",
"steps": [
8,
14
],
"criteria": "API 호출 + 성공 토스트 + 데이터 반영"
},
{
"id": 5,
"name": "목업 페이지 감지",
"steps": [
2
],
"criteria": "현장 목록, 등록 버튼, 검색 기능 존재"
}
],
"rollbackPlan": {
"onCreateFail": "등록 모달 닫고 재시도",
"onUpdateFail": "페이지 새로고침 후 재시도",
"onDeleteFail": "수동 삭제 필요",
"cleanupRequired": "E2E_TEST_현장_* 패턴 데이터 삭제"
}
}

View File

@@ -10,7 +10,7 @@
"menuNavigation": {
"level1": "설정",
"level2": "계정정보",
"expectedUrl": "/settings/account",
"expectedUrl": "/settings/account-info",
"searchWithinParent": true,
"closeOtherMenus": true
},
@@ -32,7 +32,7 @@
"level1": "설정",
"level2": "계정정보",
"expected": {
"url_contains": "/settings/account",
"url_contains": "/settings/account-info",
"visible": ["계정정보", "프로필"]
}
},

View File

@@ -10,7 +10,7 @@
"menuNavigation": {
"level1": "설정",
"level2": "근태설정",
"expectedUrl": "/settings/attendance",
"expectedUrl": "/settings/attendance-settings",
"searchWithinParent": true,
"closeOtherMenus": true
},
@@ -34,7 +34,7 @@
"level1": "설정",
"level2": "근태설정",
"expected": {
"url_contains": "/settings/attendance",
"url_contains": "/settings/attendance-settings",
"visible": ["근태설정", "근태"]
}
},

View File

@@ -17,7 +17,7 @@
"menuNavigation": {
"level1": "설정",
"level2": "계좌관리",
"expectedUrl": "/settings/bank-accounts",
"expectedUrl": "/settings/accounts",
"searchWithinParent": true,
"closeOtherMenus": true
},
@@ -46,7 +46,7 @@
"level1": "설정",
"level2": "계좌관리",
"expected": {
"url_contains": "/settings/bank",
"url_contains": "/settings/accounts",
"visible": [
"계좌관리",
"계좌"
@@ -173,7 +173,7 @@
"action": "click_if_exists",
"target": "table tbody tr:has-text('E2E_TEST')",
"expected": {
"url_contains": "/settings/bank",
"url_contains": "/settings/accounts",
"visible": [
"계좌 상세",
"수정",
@@ -267,7 +267,7 @@
"verify": {
"api_call": "DELETE /api/v1/bank-accounts/",
"toast": "삭제|완료|성공",
"redirect": "/settings/bank-accounts"
"redirect": "/settings/accounts"
},
"expected": "삭제 완료 및 목록 복귀"
},

View File

@@ -10,7 +10,7 @@
"menuNavigation": {
"level1": "설정",
"level2": "알림설정",
"expectedUrl": "/settings/notifications",
"expectedUrl": "/settings/notification-settings",
"searchWithinParent": true,
"closeOtherMenus": true
},
@@ -34,7 +34,7 @@
"level1": "설정",
"level2": "알림설정",
"expected": {
"url_contains": "/settings/notification",
"url_contains": "/settings/notification-settings",
"visible": ["알림설정", "알림"]
}
},

View File

@@ -10,7 +10,7 @@
"menuNavigation": {
"level1": "설정",
"level2": "직책관리",
"expectedUrl": "/settings/positions",
"expectedUrl": "/settings/titles",
"searchWithinParent": true,
"closeOtherMenus": true
},
@@ -38,7 +38,7 @@
"level1": "설정",
"level2": "직책관리",
"expected": {
"url_contains": "/settings/positions",
"url_contains": "/settings/titles",
"visible": ["직책관리", "직책"]
}
},
@@ -137,7 +137,7 @@
"action": "click_if_exists",
"target": "table tbody tr:has-text('E2E_TEST')",
"expected": {
"url_contains": "/settings/positions",
"url_contains": "/settings/titles",
"visible": ["직책 상세", "수정", "삭제"]
}
},
@@ -227,7 +227,7 @@
"verify": {
"api_call": "DELETE /api/v1/positions/",
"toast": "삭제|완료|성공",
"redirect": "/settings/positions"
"redirect": "/settings/titles"
},
"expected": "삭제 완료 및 목록 복귀"
},

View File

@@ -10,7 +10,7 @@
"menuNavigation": {
"level1": "설정",
"level2": "휴가정책",
"expectedUrl": "/settings/vacation-policy",
"expectedUrl": "/settings/leave-policy",
"searchWithinParent": true,
"closeOtherMenus": true
},
@@ -39,7 +39,7 @@
"level1": "설정",
"level2": "휴가정책",
"expected": {
"url_contains": "/settings/vacation",
"url_contains": "/settings/leave-policy",
"visible": ["휴가정책", "휴가"]
}
},

View File

@@ -1,265 +0,0 @@
{
"enabled": false,
"id": "shipment-dispatch",
"name": "배차차량관리 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": ["error", "fail", "timeout", "404", "500", "blocked"]
},
"description": "출고관리 > 배차차량관리 메뉴의 배차 CRUD 기능 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"menuNavigation": {
"level1": "출고관리",
"level2": "배차차량관리",
"expectedUrl": "/outbound/vehicle-dispatches",
"searchWithinParent": true,
"closeOtherMenus": true
},
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"testData": {
"create": {
"vehicleNumber": "E2E_99가9999",
"dispatchDate": "{today}",
"driver": "E2E_TEST_기사"
}
},
"steps": [
{
"id": 1,
"name": "메뉴 진입: 출고관리 > 배차차량관리",
"action": "menu_navigate",
"level1": "출고관리",
"level2": "배차차량관리",
"expected": {
"url_contains": "/outbound",
"visible": ["배차", "차량"]
}
},
{
"id": 2,
"name": "필수 검증 #5: 목업 페이지 감지",
"action": "verify_not_mockup",
"checks": [
"배차 목록 표시",
"배차 등록 버튼 존재",
"날짜 필터 존재"
],
"expected": "정상 페이지 (목업 아님)"
},
{
"id": 3,
"name": "배차 테이블 구조 확인",
"action": "verify_table",
"checks": [
"차량번호 컬럼",
"배차일 컬럼",
"기사명 컬럼",
"상태 컬럼",
"출고지/도착지 컬럼"
],
"expected": "배차 테이블 표시"
},
{
"id": 4,
"name": "배차 통계 확인",
"action": "verify_elements",
"checks": [
"금일 배차 현황",
"출고 대기 건수",
"배차 완료 건수"
],
"expected": "배차 통계 표시"
},
{
"id": 5,
"phase": "CREATE",
"name": "[CREATE] 배차 등록 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('등록'), button:has-text('추가'), button:has-text('배차')",
"expected": {
"modal_open": true
}
},
{
"id": 6,
"phase": "CREATE",
"name": "[CREATE] 차량 선택",
"action": "click_if_exists",
"target": "select[name*='vehicle'], input[placeholder*='차량']",
"expected": "차량 선택 가능"
},
{
"id": 7,
"phase": "CREATE",
"name": "[CREATE] 배차일 입력",
"action": "click_if_exists",
"target": "input[type='date'], input[name*='date']",
"expected": "배차일 선택"
},
{
"id": 8,
"phase": "CREATE",
"name": "[CREATE] 기사 선택",
"action": "click_if_exists",
"target": "select[name*='driver'], input[placeholder*='기사']",
"expected": "기사 선택 가능"
},
{
"id": 9,
"phase": "CREATE",
"name": "[CREATE] 필수 검증 #2: 배차 저장",
"action": "click_if_exists",
"target": "button:has-text('저장'), button:has-text('등록'), button:has-text('확인')",
"verify": {
"url_maintained": true,
"no_error_page": true,
"api_call": "POST /api/v1/outbound/dispatches",
"toast": "등록|저장|완료|성공"
},
"expected": "배차 등록 완료"
},
{
"id": 10,
"phase": "READ",
"name": "[READ] 등록된 배차 검색",
"action": "click_if_exists",
"target": "input[type='search'], input[placeholder*='검색']",
"value": "E2E_99가",
"submit": true
},
{
"id": 11,
"phase": "READ",
"name": "[READ] 등록된 배차 확인",
"action": "verify_detail",
"checks": [
"E2E_99가 목록에 표시"
],
"expected": "등록된 배차 확인"
},
{
"id": 12,
"phase": "READ",
"name": "[READ] 배차 상세 조회",
"action": "click_if_exists",
"target": "table tbody tr:has-text('E2E_99가')",
"expected": {
"detail_view": true
}
},
{
"id": 13,
"name": "상세 정보 확인",
"action": "verify_elements",
"checks": [
"차량 정보 표시",
"기사 정보 표시",
"출고 품목 정보"
],
"expected": "상세 정보 표시"
},
{
"id": 14,
"phase": "UPDATE",
"name": "[UPDATE] 배차 수정",
"action": "click_if_exists",
"target": "button:has-text('수정'), button:has-text('편집')",
"expected": {
"edit_mode": true
}
},
{
"id": 15,
"phase": "UPDATE",
"name": "[UPDATE] 정보 변경",
"action": "click_if_exists",
"target": "textarea[name*='memo'], textarea[placeholder*='비고']",
"value": "E2E 테스트 배차 메모",
"clear": true
},
{
"id": 16,
"phase": "UPDATE",
"name": "[UPDATE] 변경 저장",
"action": "click_if_exists",
"target": "button:has-text('저장'), button:has-text('확인')",
"verify": {
"api_call": "PUT /api/v1/outbound/dispatches",
"toast": "수정|저장|완료|성공"
},
"expected": "배차 수정 완료"
},
{
"id": 17,
"phase": "DELETE",
"name": "[DELETE] 배차 취소/삭제",
"action": "click_if_exists",
"target": "button:has-text('삭제'), button:has-text('취소'), button:has-text('제거')",
"expected": {
"confirm_dialog": true
}
},
{
"id": 18,
"phase": "DELETE",
"name": "[DELETE] 삭제 확인",
"action": "click_if_exists",
"target": "[role='alertdialog'] button:has-text('확인'), [role='dialog'] button:has-text('삭제')",
"verify": {
"api_call": "DELETE /api/v1/outbound/dispatches",
"toast": "삭제|취소|완료|성공"
},
"expected": "배차 삭제 완료"
}
],
"expectedAPIs": [
{
"method": "GET",
"endpoint": "/api/v1/outbound/dispatches",
"description": "배차 목록 조회"
},
{
"method": "POST",
"endpoint": "/api/v1/outbound/dispatches",
"description": "배차 등록"
},
{
"method": "GET",
"endpoint": "/api/v1/outbound/dispatches/:id",
"description": "배차 상세 조회"
},
{
"method": "PUT",
"endpoint": "/api/v1/outbound/dispatches/:id",
"description": "배차 수정"
},
{
"method": "DELETE",
"endpoint": "/api/v1/outbound/dispatches/:id",
"description": "배차 삭제"
}
],
"requiredVerifications": [
{
"id": 2,
"name": "저장 버튼",
"steps": [9, 16],
"criteria": "API 호출 + 성공 토스트 + 데이터 반영"
},
{
"id": 5,
"name": "목업 페이지 감지",
"steps": [2],
"criteria": "배차 목록, 등록 버튼, 날짜 필터 존재"
}
],
"rollbackPlan": {
"onCreateFail": "등록 모달 닫고 재시도",
"onUpdateFail": "페이지 새로고침 후 재시도",
"onDeleteFail": "수동 삭제 필요",
"cleanupRequired": "E2E_99가* 패턴 배차 데이터 삭제"
}
}

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "shipment-management",
"name": "출고관리 테스트",
"screenshotPolicy": {
@@ -18,7 +18,7 @@
"menuNavigation": {
"level1": "출고관리",
"level2": "출고관리",
"expectedUrl": "/outbound/outbound-management",
"expectedUrl": "/outbound/shipments",
"searchWithinParent": true,
"closeOtherMenus": true
},

View File

@@ -1,197 +0,0 @@
{
"enabled": false,
"id": "subscription-management",
"name": "구독관리 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": ["error", "fail", "timeout", "404", "500", "blocked"]
},
"description": "설정 > 구독관리 페이지의 구독 정보 조회 및 자료 내보내기 기능을 테스트하는 E2E 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"url": "/settings/subscription",
"navigation": {
"targetUrl": "/subscription",
"urlPattern": "/subscription|/ko/subscription|/settings/subscription",
"menuHints": ["구독관리", "구독 관리", "설정"]
},
"menuNavigation": {
"level1": "설정",
"level2": "구독관리",
"expectedUrl": "/settings/subscription",
"searchWithinParent": true,
"closeOtherMenus": true
},
"menuNavigationEnhanced": {
"strategy": "scroll-and-search",
"sidebar": {
"scrollContainer": ".sidebar-scroll",
"scrollStep": 200,
"maxScrollAttempts": 5,
"waitAfterScroll": 300
},
"level1": {
"text": "설정",
"expandable": true,
"waitAfterClick": 500
},
"level2": {
"text": "구독관리",
"waitAfterClick": 300
},
"fallbackUrl": "/settings/subscription",
"expectedUrl": "/settings/subscription"
},
"timeout": 90000,
"tags": ["settings", "subscription", "read-only"],
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"steps": [
{
"id": "step-0",
"name": "사이드바 메뉴 전체 펼치기",
"description": "모두 펼치기 버튼을 클릭하여 전체 메뉴를 펼친 후 메뉴 탐색 준비",
"actions": [
{
"type": "evaluate",
"script": "document.querySelector('.sidebar-scroll')?.scrollTo({top:0,behavior:'instant'})"
},
{ "type": "wait", "duration": 300 },
{
"type": "evaluate",
"script": "Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('모두 펼치기'))?.click()"
},
{ "type": "wait", "duration": 2000 }
]
},
{
"id": "step-1",
"name": "설정 메뉴 진입",
"description": "설정 > 구독관리 메뉴로 이동",
"actions": [
{
"type": "scrollAndFind",
"container": ".sidebar-scroll",
"target": "설정",
"scrollStep": 200,
"maxAttempts": 5
},
{ "type": "click_if_exists", "target": "설정" },
{ "type": "wait", "duration": 500 },
{ "type": "click_if_exists", "target": "구독관리" }
],
"expect": {
"url": "/settings/subscription",
"visible": ["구독관리", "구독 정보"]
},
"fallback": {
"type": "navigate",
"url": "/settings/subscription"
}
},
{
"id": "step-2",
"name": "페이지 구조 확인",
"description": "구독 정보 필드 및 버튼 확인",
"verify": {
"visible": ["구독관리", "최근 결제일시", "다음 결제일시", "구독금액", "구독 정보"],
"buttons": ["자료 내보내기", "서비스 해지"]
}
},
{
"id": "step-3",
"name": "결제 정보 확인",
"description": "결제 관련 정보 표시 확인",
"verify": {
"visible": ["최근 결제일시", "다음 결제일시", "구독금액"]
}
},
{
"id": "step-4",
"name": "사용량 정보 확인",
"description": "사용자 수, 저장 공간, API 호출 정보 확인",
"verify": {
"visible": ["사용자 수", "저장 공간", "AI API 호출"]
}
},
{
"id": "step-5",
"name": "필수 검증 #1: 자료 내보내기 버튼 동작",
"description": "자료 내보내기 버튼 클릭하여 다운로드 확인",
"actions": [
{ "type": "click_if_exists", "target": "자료 내보내기" },
{ "type": "wait", "duration": 1000 }
],
"expect": {
"downloadTriggered": true,
"noErrorPage": true
},
"verify": {
"apiCall": "GET /api/settings/subscription/export"
}
},
{
"id": "step-6",
"name": "서비스 해지 버튼 확인",
"description": "서비스 해지 버튼 존재 확인 (클릭하지 않음)",
"verify": {
"buttonExists": "서비스 해지"
}
},
{
"id": "step-7",
"name": "구독 플랜 정보 확인",
"description": "현재 구독 플랜 정보 표시 확인",
"verify": {
"visible": ["무료", "무제한"]
}
}
],
"assertions": [
{
"type": "url",
"expected": "/settings/subscription",
"message": "구독관리 페이지에 머물러야 함"
},
{
"type": "elementExists",
"selector": "button:has-text('자료 내보내기')",
"message": "자료 내보내기 버튼이 표시되어야 함"
}
],
"mandatoryVerifications": {
"description": "E2E_TEST_CONFIG.md 기준 필수 검증 항목",
"items": [
{
"id": 1,
"name": "파일 다운로드",
"trigger": "자료 내보내기 버튼",
"verification": "Network API + 실제 다운로드 확인",
"failCondition": "Console LOG만으로 PASS 금지"
},
{
"id": 5,
"name": "목업/미완성 페이지 감지",
"trigger": "페이지 로드 시",
"verification": "실제 구독 정보 표시 + 동작하는 버튼 확인",
"failCondition": "더미 데이터만 표시"
}
]
},
"notes": {
"testScope": "구독 정보 조회 및 자료 내보내기 테스트",
"pageFeatures": {
"paymentInfo": ["최근 결제일시", "다음 결제일시", "구독금액"],
"usageInfo": ["사용자 수", "저장 공간", "AI API 호출"],
"buttons": ["자료 내보내기", "서비스 해지"]
},
"caution": "서비스 해지 버튼은 테스트 계정 보호를 위해 클릭하지 않음",
"prerequisites": "로그인된 사용자"
}
}

View File

@@ -1,879 +0,0 @@
{
"id": "vacation-management",
"name": "휴가관리 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": [
"error",
"fail",
"timeout",
"404",
"500",
"blocked"
]
},
"description": "휴가 사용현황, 부여현황, 신청현황 탭 기능과 부여등록/휴가신청 다이얼로그를 테스트하는 E2E 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"url": "/ko/hr/vacation-management",
"navigation": {
"targetUrl": "/hr/vacation-management",
"urlPattern": "/hr/vacation-management|/ko/hr/vacation-management",
"menuHints": [
"휴가관리",
"휴가 관리",
"인사관리"
]
},
"menuNavigation": {
"level1": "인사관리",
"level2": "휴가관리",
"expectedUrl": "/ko/hr/vacation-management",
"searchWithinParent": true,
"closeOtherMenus": true
},
"menuNavigationEnhanced": {
"strategy": "scroll-and-search",
"sidebarSelector": ".sidebar-scroll, [data-sidebar], nav[class*='sidebar']",
"scrollConfig": {
"scrollStep": 200,
"maxScrollAttempts": 10,
"scrollDelay": 300
},
"level1": {
"text": "인사관리",
"selectors": [
"button:has-text('인사관리')",
"[data-menu='hr']",
"a:has-text('인사관리')"
]
},
"level2": {
"text": "휴가관리",
"selectors": [
"a:has-text('휴가관리')",
"[href*='vacation-management']",
"button:has-text('휴가관리')"
]
},
"fallbackUrl": "/ko/hr/vacation-management"
},
"timeout": 120000,
"tags": [
"hr",
"vacation",
"leave",
"management"
],
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"testData": {
"searchKeyword": "홍",
"grantData": {
"vacationType": "annual",
"grantDays": 3,
"reason": "E2E 테스트 부여"
},
"requestData": {
"leaveType": "annual",
"daysRange": 2
},
"dateRange": {
"startDate": "2025-12-01",
"endDate": "2025-12-31"
}
},
"steps": [
{
"id": "step-0",
"name": "사이드바 메뉴 전체 펼치기",
"description": "모두 펼치기 버튼을 클릭하여 전체 메뉴를 펼친 후 메뉴 탐색 준비",
"actions": [
{
"type": "evaluate",
"script": "document.querySelector('.sidebar-scroll')?.scrollTo({top:0,behavior:'instant'})"
},
{
"type": "wait",
"duration": 300
},
{
"type": "evaluate",
"script": "Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('모두 펼치기'))?.click()"
},
{
"type": "wait",
"duration": 2000
}
],
"expect": {
"sidebarReady": true
}
},
{
"id": "step-1",
"name": "인사관리 메뉴 진입",
"description": "인사관리 > 휴가관리 메뉴로 이동 (scrollAndFind 패턴 사용)",
"navigationPattern": "scrollAndFind",
"actions": [
{
"type": "scrollAndFind",
"target": "인사관리",
"container": ".sidebar-scroll, [data-sidebar], nav[class*='sidebar']",
"scrollStep": 200,
"maxAttempts": 10
},
{
"type": "click_if_exists",
"target": "인사관리"
},
{
"type": "wait",
"duration": 300
},
{
"type": "scrollAndFind",
"target": "휴가관리",
"container": ".sidebar-scroll, [data-sidebar], nav[class*='sidebar']",
"scrollStep": 200,
"maxAttempts": 10
},
{
"type": "click_if_exists",
"target": "휴가관리"
}
],
"fallback": {
"type": "navigate",
"url": "/ko/hr/vacation-management"
},
"expect": {
"url": "/hr/vacation-management",
"visible": [
"휴가관리",
"휴가 사용현황"
]
}
},
{
"id": "step-2",
"name": "필수 검증 #5: 목업 페이지 감지",
"description": "페이지가 목업인지 실제 동작하는 페이지인지 감지",
"verify": {
"mockupDetection": {
"inputFields": [
"검색창",
"날짜 필터"
],
"functionalButtons": [
"부여등록",
"휴가신청",
"승인",
"거절"
],
"apiCalls": true,
"dataChangePossible": true
}
}
},
{
"id": "step-3",
"name": "통계 카드 대시보드 확인",
"description": "휴가 승인 대기, 연차, 경조사, 연간 연차 사용률 카드 표시 확인",
"verify": {
"visible": [
"휴가 승인 대기",
"연차",
"경조사",
"연간 연차 사용률"
],
"statsCards": true
}
},
{
"id": "step-4",
"name": "휴가 사용현황 탭 확인 (기본 탭)",
"description": "휴가 사용현황 탭의 테이블 컬럼 구조 검증",
"verify": {
"activeTab": "휴가 사용현황",
"tableColumns": [
"번호",
"부서",
"직책",
"이름",
"직급",
"입사일",
"기본",
"부여",
"사용",
"잔여"
]
}
},
{
"id": "step-4-1",
"name": "⚠️ 필수 검증: 날짜 필터 검색",
"description": "날짜 범위 필터를 설정하고 데이터가 필터링되는지 확인",
"actions": [
{
"type": "evaluate",
"script": "document.querySelectorAll('table tbody tr').length",
"description": "필터 전 행 수 확인"
},
{
"type": "fill",
"target": "input[type='date']:first-of-type, input[placeholder*='시작']",
"value": "2025-12-01",
"description": "시작일 입력"
},
{
"type": "fill",
"target": "input[type='date']:last-of-type, input[placeholder*='종료']",
"value": "2025-12-31",
"description": "종료일 입력"
},
{
"type": "wait",
"duration": 500,
"description": "필터 적용 대기"
},
{
"type": "evaluate",
"script": "document.querySelectorAll('table tbody tr').length",
"description": "필터 후 행 수 확인"
}
],
"verify": {
"dateFilterApplied": true,
"dataChanged": "필터 적용 후 데이터가 변경되어야 함"
},
"note": "날짜 필터가 실제로 데이터를 필터링하는지 확인"
},
{
"id": "step-5",
"name": "⚠️ 필수 검증: 검색 기능 확인 (사용현황)",
"description": "검색어 입력 후 테이블 데이터가 필터링되는지 확인",
"actions": [
{
"type": "evaluate",
"script": "document.querySelectorAll('table tbody tr').length",
"description": "검색 전 행 수 확인"
},
{
"type": "fill",
"target": "input[placeholder*='검색'], input[type='search']",
"value": "홍",
"description": "검색어 입력"
},
{
"type": "wait",
"duration": 500,
"description": "검색 결과 대기"
},
{
"type": "evaluate",
"script": "document.querySelectorAll('table tbody tr').length",
"description": "검색 후 행 수 확인"
}
],
"verify": {
"searchApplied": true,
"tableContains": "{testData.searchKeyword}",
"dataFiltered": "검색 결과에 검색어가 포함된 행만 표시"
},
"expect": {
"searchPlaceholder": "이름, 부서 검색..."
},
"note": "⚠️ 검색어 입력 후 테이블에 검색어가 포함된 행만 표시되어야 함"
},
{
"id": "step-5-1",
"name": "검색 결과 데이터 검증",
"description": "검색 결과의 각 행에 검색어가 포함되어 있는지 확인",
"verify": {
"allRowsContain": "{testData.searchKeyword}",
"verifyMethod": "테이블의 모든 행이 검색어를 포함하는지 확인"
}
},
{
"id": "step-5-2",
"name": "검색 초기화 확인",
"description": "검색어 삭제 후 전체 목록 복원 확인",
"actions": [
{
"type": "clear",
"target": "input[placeholder*='검색'], input[type='search']",
"description": "검색어 삭제"
},
{
"type": "wait",
"duration": 500,
"description": "목록 복원 대기"
}
],
"verify": {
"dataRestored": true
}
},
{
"id": "step-6",
"name": "휴가 부여현황 탭 전환",
"description": "휴가 부여현황 탭 클릭 및 테이블 구조 확인",
"actions": [
{
"type": "click_if_exists",
"target": "휴가 부여현황"
}
],
"verify": {
"activeTab": "휴가 부여현황",
"tableColumns": [
"번호",
"부서",
"직책",
"이름",
"직급",
"유형",
"부여일",
"부여휴가일수",
"사유"
],
"visible": [
"부여등록"
]
}
},
{
"id": "step-7",
"name": "부여등록 다이얼로그 열기",
"description": "부여등록 버튼 클릭하여 다이얼로그 열기",
"actions": [
{
"type": "click_if_exists",
"target": "부여등록",
"description": "부여등록 버튼 클릭"
}
],
"expect": {
"modal": "휴가 부여 등록",
"visible": [
"사원 선택",
"휴가 유형",
"부여일",
"부여 일수",
"사유"
]
}
},
{
"id": "step-8",
"name": "부여등록 다이얼로그 입력 필드 확인",
"description": "다이얼로그 내 입력 필드들이 정상 동작하는지 확인",
"actions": [
{
"type": "click_if_exists",
"target": "사원 선택",
"options": {
"waitAfter": 200
}
}
],
"verify": {
"comboboxOptions": true,
"inputFields": [
"사원 선택",
"휴가 유형",
"부여일",
"부여 일수",
"사유"
]
}
},
{
"id": "step-9",
"name": "필수 검증 #4: 부여등록 저장",
"description": "모달 내 부여등록 실제 등록 수행",
"actions": [
{
"type": "click_if_exists",
"target": "사원 선택",
"options": {
"waitAfter": 200
},
"description": "사원 선택 요소 존재 확인"
},
{
"type": "click_if_exists",
"target": "휴가 유형",
"options": {
"waitAfter": 200
},
"description": "휴가 유형 요소 존재 확인"
},
{
"type": "click_if_exists",
"target": "부여 일수",
"options": {
"waitAfter": 100
},
"description": "부여 일수 요소 존재 확인"
},
{
"type": "click_if_exists",
"target": "사유",
"options": {
"waitAfter": 100
},
"description": "사유 요소 존재 확인"
},
{
"type": "click_if_exists",
"target": "등록",
"options": {
"waitAfter": 500
}
}
],
"expect": {
"urlMaintained": true,
"noErrorPage": true,
"modalClosed": true,
"apiCall": "createLeaveGrant"
}
},
{
"id": "step-10",
"name": "부여등록 다이얼로그 취소 테스트",
"description": "다이얼로그 취소 버튼 동작 확인",
"actions": [
{
"type": "click_if_exists",
"target": "부여등록"
},
{
"type": "click_if_exists",
"target": "취소"
}
],
"expect": {
"modalClosed": true
}
},
{
"id": "step-11",
"name": "휴가 신청현황 탭 전환",
"description": "휴가 신청현황 탭 클릭 및 테이블 구조 확인",
"actions": [
{
"type": "click_if_exists",
"target": "휴가 신청현황"
}
],
"verify": {
"activeTab": "휴가 신청현황",
"tableColumns": [
"번호",
"부서",
"직책",
"이름",
"직급",
"휴가기간",
"휴가일수",
"상태",
"신청일"
],
"visible": [
"휴가신청"
]
}
},
{
"id": "step-12",
"name": "휴가신청 다이얼로그 열기",
"description": "휴가신청 버튼 클릭하여 다이얼로그 열기",
"actions": [
{
"type": "click_if_exists",
"target": "휴가신청",
"description": "휴가신청 버튼 클릭"
}
],
"expect": {
"modal": "휴가 신청",
"visible": [
"사원 선택",
"휴가 유형",
"시작일",
"종료일"
]
}
},
{
"id": "step-13",
"name": "휴가신청 다이얼로그 입력 필드 확인",
"description": "다이얼로그 내 입력 필드들이 정상 동작하는지 확인 (캘린더 포함)",
"actions": [
{
"type": "click_if_exists",
"target": "사원 선택",
"options": {
"waitAfter": 200
}
}
],
"verify": {
"comboboxOptions": true,
"inputFields": [
"사원 선택",
"휴가 유형",
"시작일",
"종료일"
]
}
},
{
"id": "step-14",
"name": "필수 검증 #4: 휴가신청 등록",
"description": "모달 내 휴가신청 실제 등록 수행",
"actions": [
{
"type": "click_if_exists",
"target": "사원 선택",
"options": {
"waitAfter": 200
},
"description": "사원 선택 요소 존재 확인"
},
{
"type": "click_if_exists",
"target": "휴가 유형",
"options": {
"waitAfter": 200
},
"description": "휴가 유형 요소 존재 확인"
},
{
"type": "click_if_exists",
"target": "시작일 선택",
"options": {
"waitAfter": 200
}
},
{
"type": "click_if_exists",
"target": "캘린더 날짜 선택",
"options": {
"waitAfter": 200
}
},
{
"type": "click_if_exists",
"target": "종료일 선택",
"options": {
"waitAfter": 200
}
},
{
"type": "click_if_exists",
"target": "캘린더 날짜 선택",
"options": {
"waitAfter": 200
}
},
{
"type": "click_if_exists",
"target": "신청",
"options": {
"waitAfter": 500
}
}
],
"expect": {
"urlMaintained": true,
"noErrorPage": true,
"modalClosed": true,
"apiCall": "createLeave"
}
},
{
"id": "step-15",
"name": "휴가신청 다이얼로그 취소 테스트",
"description": "다이얼로그 취소 버튼 동작 확인",
"actions": [
{
"type": "click_if_exists",
"target": "휴가신청"
},
{
"type": "click_if_exists",
"target": "취소"
}
],
"expect": {
"modalClosed": true
}
},
{
"id": "step-16",
"name": "필수 검증 #2: 휴가 승인 버튼 동작",
"description": "신청현황에서 체크박스 선택 후 승인 버튼 동작 확인",
"actions": [
{
"type": "click_if_exists",
"target": "첫번째 행 체크박스"
},
{
"type": "click_if_exists",
"target": "승인"
}
],
"expect": {
"modal": "휴가 승인",
"visible": [
"승인하시겠습니까"
]
}
},
{
"id": "step-17",
"name": "승인 확인 다이얼로그 동작",
"description": "승인 확인 다이얼로그에서 승인 버튼 클릭",
"actions": [
{
"type": "click_if_exists",
"target": "승인",
"context": "dialog"
}
],
"expect": {
"urlMaintained": true,
"noErrorPage": true,
"modalClosed": true,
"apiCall": "approveLeavesMany"
}
},
{
"id": "step-18",
"name": "필수 검증 #2: 휴가 거절 버튼 동작",
"description": "신청현황에서 체크박스 선택 후 거절 버튼 동작 확인",
"actions": [
{
"type": "click_if_exists",
"target": "첫번째 행 체크박스"
},
{
"type": "click_if_exists",
"target": "거절"
}
],
"expect": {
"modal": "휴가 거절",
"visible": [
"거절하시겠습니까"
]
}
},
{
"id": "step-19",
"name": "거절 확인 다이얼로그 취소",
"description": "거절 확인 다이얼로그에서 취소 버튼 클릭",
"actions": [
{
"type": "click_if_exists",
"target": "취소",
"context": "dialog"
}
],
"expect": {
"modalClosed": true
}
},
{
"id": "step-20",
"name": "필터 및 정렬 셀렉트 동작 확인",
"description": "필터 및 정렬 셀렉트박스가 정상 동작하는지 확인",
"actions": [
{
"type": "click_if_exists",
"target": "필터 선택 콤보박스"
}
],
"verify": {
"comboboxOptions": [
"전체",
"대기중",
"승인됨",
"거절됨"
]
}
},
{
"id": "step-21",
"name": "날짜 범위 필터 확인",
"description": "시작일/종료일 날짜 필터 필드 동작 확인",
"verify": {
"dateInputs": 2,
"dateRange": {
"startDate": "2025-12-01",
"endDate": "2025-12-31"
}
}
}
],
"assertions": [
{
"type": "url",
"expected": "/hr/vacation-management",
"message": "휴가관리 페이지에 머물러야 함"
},
{
"type": "tabsExist",
"expected": [
"휴가 사용현황",
"휴가 부여현황",
"휴가 신청현황"
],
"message": "3개의 탭이 모두 표시되어야 함"
},
{
"type": "tableExists",
"message": "휴가 목록 테이블이 표시되어야 함"
}
],
"mandatoryVerifications": {
"description": "E2E_TEST_CONFIG.md 기준 필수 검증 항목",
"items": [
{
"id": 2,
"name": "등록/저장 버튼",
"trigger": "부여등록, 휴가신청, 승인, 거절 버튼",
"verification": "URL 유지 + 에러 페이지 없음 + 모달 닫힘",
"failCondition": "404/500 에러 페이지 이동"
},
{
"id": 3,
"name": "검색/필터",
"trigger": "검색창, 날짜 필터, 탭 전환",
"verification": "데이터 변화 확인",
"failCondition": "필터 적용 후 데이터 무변화"
},
{
"id": 4,
"name": "모달 등록 완료",
"trigger": "부여등록/휴가신청 다이얼로그",
"verification": "실제 등록 동작 + API 호출 확인",
"failCondition": "열기/닫기만 테스트"
},
{
"id": 5,
"name": "목업/미완성 페이지 감지",
"trigger": "페이지 로드 시",
"verification": "입력 필드 + 동작하는 버튼 + API 호출 확인",
"failCondition": "버튼만 있고 입력 불가, Console LOG만 출력"
}
]
},
"cleanup": {
"description": "테스트 데이터는 수동 정리 필요"
},
"notes": {
"testScope": "휴가관리 페이지 UI 요소 및 기능 검증",
"features": {
"tabs": "휴가 사용현황 / 휴가 부여현황 / 휴가 신청현황",
"statsCards": "휴가 승인 대기 / 연차 / 경조사 / 연간 연차 사용률",
"grantDialog": "부여등록 다이얼로그 (사원 선택, 휴가 유형, 부여일, 부여 일수, 사유)",
"requestDialog": "휴가신청 다이얼로그 (사원 선택, 휴가 유형, 시작일, 종료일, 캘린더)",
"approveReject": "신청현황 탭에서 승인/거절 버튼",
"dateFilter": "시작일~종료일 날짜 범위 필터",
"search": "이름, 부서 검색",
"filterSort": "필터 및 정렬 셀렉트박스"
},
"tableColumns": {
"usage": [
"번호",
"부서",
"직책",
"이름",
"직급",
"입사일",
"기본",
"부여",
"사용",
"잔여"
],
"grant": [
"번호",
"부서",
"직책",
"이름",
"직급",
"유형",
"부여일",
"부여휴가일수",
"사유"
],
"request": [
"번호",
"부서",
"직책",
"이름",
"직급",
"휴가기간",
"휴가일수",
"상태",
"신청일"
]
},
"dialogs": {
"grantDialog": {
"title": "휴가 부여 등록",
"fields": [
"사원 선택",
"휴가 유형",
"부여일",
"부여 일수",
"사유"
],
"buttons": [
"취소",
"등록"
]
},
"requestDialog": {
"title": "휴가 신청",
"fields": [
"사원 선택",
"휴가 유형",
"시작일",
"종료일"
],
"buttons": [
"취소",
"신청"
],
"note": "캘린더 컴포넌트 사용"
},
"approveDialog": {
"title": "휴가 승인",
"buttons": [
"취소",
"승인"
]
},
"rejectDialog": {
"title": "휴가 거절",
"buttons": [
"취소",
"거절"
]
}
},
"knownIssues": [
"엑셀 다운로드 버튼이 주석처리 되어 있음 (미구현)",
"휴가 유형 옵션: annual(연차), sick(병가), personal(개인사유), condolence(경조사), maternity(출산휴가), reward(포상휴가)"
],
"prerequisites": "로그인된 사용자에게 휴가 관리 권한 필요"
}
}

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "vendor-ledger",
"name": "거래처원장 테스트",
"screenshotPolicy": {

View File

@@ -1,5 +1,5 @@
{
"enabled": false,
"enabled": true,
"id": "withdrawal-management",
"name": "출금관리 테스트",
"screenshotPolicy": {

View File

@@ -1,214 +0,0 @@
{
"enabled": false,
"id": "work-performance",
"name": "작업실적 테스트",
"screenshotPolicy": {
"onErrorOnly": true,
"captureOn": ["error", "fail", "timeout", "404", "500", "blocked"]
},
"description": "생산관리 > 작업실적 메뉴의 작업실적 조회/필터/검색/엑셀다운로드 기능 테스트",
"baseUrl": "https://dev.codebridge-x.com",
"menuNavigation": {
"level1": "생산관리",
"level2": "작업실적",
"expectedUrl": "/production/performance",
"searchWithinParent": true,
"closeOtherMenus": true
},
"auth": {
"username": "TestUser5",
"password": "password123!"
},
"steps": [
{
"id": 1,
"name": "메뉴 진입: 생산관리 > 작업실적",
"action": "menu_navigate",
"level1": "생산관리",
"level2": "작업실적",
"expected": {
"url_contains": "/production/performance",
"visible": ["작업실적", "조회"]
}
},
{
"id": 2,
"name": "필수 검증 #5: 목업 페이지 감지",
"action": "verify_not_mockup",
"checks": [
"작업실적 테이블 표시",
"검색/필터 영역 존재",
"엑셀 다운로드 버튼 존재"
],
"expected": "정상 페이지 (목업 아님)"
},
{
"id": 3,
"name": "통계 카드 확인",
"action": "verify_elements",
"checks": [
"총 생산수량 카드",
"양품수량 카드",
"불량수량 카드",
"불량률 카드"
],
"expected": "통계 카드 표시"
},
{
"id": 4,
"name": "작업실적 테이블 구조 확인",
"action": "verify_table",
"checks": [
"로트번호 컬럼",
"작업일 컬럼",
"작업지시번호 컬럼",
"공정 컬럼",
"품목명 컬럼",
"생산수량 컬럼",
"양품수량 컬럼",
"불량수량 컬럼"
],
"expected": "작업실적 테이블 표시"
},
{
"id": 5,
"phase": "FILTER",
"name": "[FILTER] 기간 필터 - 시작일",
"action": "click_if_exists",
"target": "input[type='date']:first-of-type, input[name*='start'], input[placeholder*='시작']",
"expected": "시작일 선택 열림"
},
{
"id": 6,
"phase": "FILTER",
"name": "[FILTER] 기간 필터 - 종료일",
"action": "click_if_exists",
"target": "input[type='date']:last-of-type, input[name*='end'], input[placeholder*='종료']",
"expected": "종료일 선택 열림"
},
{
"id": 7,
"phase": "FILTER",
"name": "[FILTER] 조회 버튼 클릭",
"action": "click_if_exists",
"target": "button:has-text('조회'), button:has-text('검색'), button:has-text('적용')",
"expected": "필터 적용됨"
},
{
"id": 8,
"phase": "FILTER",
"name": "[FILTER] 필터 결과 확인",
"action": "verify_detail",
"checks": [
"선택한 기간의 작업실적 데이터 표시 또는 검색 결과 없음"
],
"expected": "필터 동작 확인"
},
{
"id": 9,
"name": "공정 필터 확인",
"action": "verify_elements",
"checks": [
"공정 선택 드롭다운 또는 필터"
],
"expected": "공정 필터 존재"
},
{
"id": 10,
"phase": "READ",
"name": "[READ] 작업실적 행 상세 조회",
"action": "click_if_exists",
"target": "table tbody tr:first-child",
"expected": {
"detail_view": true
}
},
{
"id": 11,
"name": "상세 정보 확인",
"action": "verify_detail",
"checks": [
"로트번호 표시",
"작업일 표시",
"생산수량 표시",
"양품수량 표시",
"불량수량 표시"
],
"expected": "상세 정보 표시"
},
{
"id": 12,
"name": "필수 검증 #1: 엑셀 다운로드",
"action": "click_if_exists",
"target": "button:has-text('엑셀'), button:has-text('Excel'), button:has-text('다운로드')",
"verify": {
"api_call": "GET /api/v1/production/performance/export",
"download_triggered": true
},
"expected": "엑셀 파일 다운로드"
},
{
"id": 13,
"name": "페이지네이션 확인",
"action": "verify_elements",
"checks": [
"페이지 번호 또는 더보기 버튼"
],
"expected": "페이지네이션 표시"
},
{
"id": 14,
"name": "인쇄 버튼 확인",
"action": "verify_elements",
"checks": [
"인쇄 버튼 존재"
],
"expected": "인쇄 기능 표시"
}
],
"expectedAPIs": [
{
"method": "GET",
"endpoint": "/api/v1/production/performance",
"description": "작업실적 목록 조회"
},
{
"method": "GET",
"endpoint": "/api/v1/production/performance/:id",
"description": "작업실적 상세 조회"
},
{
"method": "GET",
"endpoint": "/api/v1/production/performance/export",
"description": "작업실적 엑셀 다운로드"
},
{
"method": "GET",
"endpoint": "/api/v1/production/performance/stats",
"description": "작업실적 통계 조회"
}
],
"requiredVerifications": [
{
"id": 1,
"name": "파일 다운로드",
"steps": [12],
"criteria": "Network API 호출 + 실제 다운로드 확인"
},
{
"id": 3,
"name": "검색/필터",
"steps": [5, 6, 7, 8],
"criteria": "기간 필터 동작"
},
{
"id": 5,
"name": "목업 페이지 감지",
"steps": [2],
"criteria": "테이블, 필터, 다운로드 버튼 존재"
}
],
"rollbackPlan": {
"note": "조회 전용 페이지로 데이터 변경 없음"
}
}