{ "id": "attendance-management", "name": "근태관리 테스트", "screenshotPolicy": { "onErrorOnly": true, "captureOn": ["error", "fail", "timeout", "404", "500", "blocked"] }, "description": "근태 등록 및 사유 등록 기능을 테스트하는 E2E 테스트", "baseUrl": "https://dev.codebridge-x.com", "menuNavigation": { "level1": "인사관리", "level2": "근태관리", "expectedUrl": "/hr/attendance-management", "searchWithinParent": true, "closeOtherMenus": true }, "auth": { "username": "TestUser5", "password": "password123!" }, "timeout": 90000, "tags": ["hr", "attendance", "management", "crud"], "testData": { "attendance": { "checkInHour": "9", "checkInMinute": "0", "checkOutHour": "18", "checkOutMinute": "0" }, "reason": { "type": { "options": ["출장신청서", "휴가신청서", "외근신청서", "연장근무신청서"] } } }, "steps": [ { "id": 1, "name": "메뉴 진입: 인사관리 > 근태관리", "action": "menu_navigate", "level1": "인사관리", "level2": "근태관리", "expected": { "url_contains": "/hr/attendance-management" } }, { "id": 2, "name": "페이지 로드 대기", "action": "wait", "timeout": 3000 }, { "id": 3, "name": "URL 검증", "action": "verify_url", "expected": { "url_contains": "/hr/attendance-management" } }, { "id": 4, "name": "GPS 위치 정보 모킹", "action": "evaluate", "script": "(() => { const mockPosition = { coords: { latitude: 37.5665, longitude: 126.9780, accuracy: 100, altitude: null, altitudeAccuracy: null, heading: null, speed: null }, timestamp: Date.now() }; const mockGeolocation = { getCurrentPosition: (success) => { setTimeout(() => success(mockPosition), 100); }, watchPosition: (success) => { setTimeout(() => success(mockPosition), 100); return 1; }, clearWatch: () => {} }; Object.defineProperty(navigator, 'geolocation', { value: mockGeolocation, writable: false, configurable: true }); return 'GPS mocking complete'; })()" }, { "id": 5, "name": "근태 현황 카드 확인", "action": "verify_element", "target": "body", "description": "미출근, 정시출근, 지각, 휴가 카드 확인" }, { "id": 6, "name": "테이블 로드 대기", "action": "wait_for_table", "timeout": 5000 }, { "id": 7, "name": "근태 등록 버튼 클릭", "action": "click_button", "target": "근태 등록", "expected": { "modal": true } }, { "id": 8, "name": "모달 열림 대기", "action": "wait", "timeout": 1000 }, { "id": 9, "name": "대상 사원 선택", "action": "evaluate", "script": "(async () => { const triggers = Array.from(document.querySelectorAll('button[role=\"combobox\"], [class*=\"select-trigger\"], [class*=\"SelectTrigger\"]')); const target = triggers.find(t => { const label = t.closest('[class*=\"field\"], [class*=\"form\"], .grid, tr')?.querySelector('label, span'); return label?.innerText?.includes('대상'); }) || triggers[0]; if (!target) return 'No combobox found'; target.click(); await new Promise(r => setTimeout(r, 500)); const opt = document.querySelector('[role=\"option\"]'); if (opt) { opt.click(); return 'Selected: ' + opt.innerText?.trim(); } return 'No options found'; })()" }, { "id": 10, "name": "기준일 기본값 확인", "action": "verify_element", "target": "[role='dialog']", "description": "기준일 기본값 확인" }, { "id": 11, "name": "근태 등록 저장", "action": "click_button", "target": "저장", "expected": { "toast": true }, "critical": true }, { "id": 12, "name": "근태 등록 모달 닫기 확인", "action": "close_modal_if_open" }, { "id": 13, "name": "근태 등록 결과 확인", "action": "verify_element", "target": "table tbody tr", "critical": true }, { "id": 14, "name": "사유 등록 버튼 클릭", "action": "click_button", "target": "사유 등록", "expected": { "modal": true } }, { "id": 15, "name": "사유 모달 열림 대기", "action": "wait", "timeout": 1000 }, { "id": 16, "name": "사유 유형 선택", "action": "evaluate", "script": "(async () => { const triggers = Array.from(document.querySelectorAll('button[role=\"combobox\"], [class*=\"select-trigger\"], [class*=\"SelectTrigger\"]')); const target = triggers.find(t => { const label = t.closest('[class*=\"field\"], [class*=\"form\"], .grid, tr')?.querySelector('label, span'); return label?.innerText?.includes('유형'); }) || triggers[0]; if (!target) return 'No combobox found'; target.click(); await new Promise(r => setTimeout(r, 500)); const opt = document.querySelector('[role=\"option\"]'); if (opt) { opt.click(); return 'Selected: ' + opt.innerText?.trim(); } return 'No options found'; })()" }, { "id": 17, "name": "사유 대상 사원 선택", "action": "evaluate", "script": "(async () => { const triggers = Array.from(document.querySelectorAll('button[role=\"combobox\"], [class*=\"select-trigger\"], [class*=\"SelectTrigger\"]')); const target = triggers.find(t => { const label = t.closest('[class*=\"field\"], [class*=\"form\"], .grid, tr')?.querySelector('label, span'); return label?.innerText?.includes('대상'); }) || triggers[0]; if (!target) return 'No combobox found'; target.click(); await new Promise(r => setTimeout(r, 500)); const opt = document.querySelector('[role=\"option\"]'); if (opt) { opt.click(); return 'Selected: ' + opt.innerText?.trim(); } return 'No options found'; })()" }, { "id": 18, "name": "사유 등록 저장", "action": "click_button", "target": "등록", "alternatives": ["저장"], "expected": { "toast": true }, "critical": true }, { "id": 19, "name": "사유 등록 모달 닫기 확인", "action": "close_modal_if_open" }, { "id": 20, "name": "기간 필터 - 당월 클릭", "action": "click", "target": "당월", "critical": true }, { "id": 21, "name": "필터 적용 대기", "action": "wait", "timeout": 1000 }, { "id": 22, "name": "검색 기능 테스트", "action": "fill", "target": "input[type='search'], input[placeholder*='검색']", "value": "홍" }, { "id": 23, "name": "검색 결과 대기", "action": "wait", "timeout": 1000 }, { "id": 24, "name": "검색 초기화", "action": "clear", "target": "input[type='search'], input[placeholder*='검색']" }, { "id": 25, "name": "엑셀 다운로드 버튼 확인", "action": "verify_element", "target": "엑셀 다운로드" } ], "expectedAPIs": [ { "method": "GET", "endpoint": "/api/v1/attendances", "description": "근태 목록 조회" }, { "method": "POST", "endpoint": "/api/v1/attendances", "description": "근태 등록" }, { "method": "POST", "endpoint": "/api/v1/attendance-reasons", "description": "사유 등록" }, { "method": "GET", "endpoint": "/api/v1/attendances/export", "description": "엑셀 다운로드" } ], "cleanup": { "enabled": true, "description": "테스트 후 등록한 근태/사유 데이터 삭제 (가능한 경우)" } }