{ "id": "attendance-checkin", "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", "searchWithinParent": true, "closeOtherMenus": true }, "auth": { "username": "TestUser5", "password": "password123!" }, "timeout": 120000, "tags": ["hr", "attendance", "geolocation", "checkin", "checkout"], "browserConfig": { "permissions": { "geolocation": { "grant": true, "mockLocation": { "enabled": true, "latitude": 37.557358, "longitude": 126.864414, "accuracy": 100 } } } }, "steps": [ { "id": 1, "name": "GPS 위치 정보 모킹", "action": "evaluate", "critical": true, "script": "(() => { const mockPosition = { coords: { latitude: 37.557358, longitude: 126.864414, 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 - coords: 37.557358, 126.864414'; })()" }, { "id": 2, "name": "메뉴 진입: 인사관리 > 근태현황", "action": "menu_navigate", "level1": "인사관리", "level2": "근태현황", "expected": { "url_contains": "/hr/attendance" } }, { "id": 3, "name": "페이지 로드 대기", "action": "wait", "timeout": 3000 }, { "id": 4, "name": "URL 검증", "action": "verify_url", "expected": { "url_contains": "/hr/attendance" } }, { "id": 5, "name": "404 에러 감지", "action": "verify_text", "target": "body", "not_contains": "404" }, { "id": 6, "name": "페이지 콘텐츠 확인", "action": "verify_element", "target": "body", "description": "근태현황 페이지가 정상 로드되었는지 확인" }, { "id": 7, "name": "출퇴근 버튼 확인", "action": "evaluate", "script": "(() => { const btns = Array.from(document.querySelectorAll('button')); const checkin = btns.find(b => b.innerText?.includes('출근')); const checkout = btns.find(b => b.innerText?.includes('퇴근')); return JSON.stringify({ checkinBtn: checkin ? checkin.innerText.trim() : null, checkoutBtn: checkout ? checkout.innerText.trim() : null, status: checkin ? 'not_checked_in' : (checkout ? 'checked_in' : 'unknown') }); })()" }, { "id": 8, "name": "출근하기 버튼 클릭 (있는 경우)", "action": "click_if_exists", "target": "button:has-text('출근')", "description": "출근 버튼이 있으면 클릭 (이미 출근한 경우 스킵)" }, { "id": 9, "name": "출근 결과 대기", "action": "wait", "timeout": 3000 }, { "id": 10, "name": "출퇴근 상태 확인", "action": "evaluate", "script": "(() => { const body = document.body.innerText; const hasCheckin = body.includes('출근 완료') || body.includes('출근 시간'); const hasCheckout = body.includes('퇴근') && !body.includes('퇴근 완료'); return JSON.stringify({ checkedIn: hasCheckin, checkoutAvailable: hasCheckout, pageText: body.substring(0, 200) }); })()" }, { "id": 11, "name": "퇴근하기 버튼 확인", "action": "click_if_exists", "target": "button:has-text('퇴근')", "description": "퇴근 버튼이 있으면 클릭 (선택적)" }, { "id": 12, "name": "최종 상태 확인", "action": "verify_element", "target": "body", "description": "근태현황 페이지 정상 표시 확인" } ], "cleanup": { "enabled": false, "description": "출퇴근 기록은 삭제하지 않음 (업무 데이터)" }, "notes": { "testScope": "위치 권한 허용 -> 근태현황 페이지 이동 -> 출근/퇴근 기록 테스트", "antiPattern404": "직접 URL 접근 금지 - 반드시 메뉴 클릭으로 페이지 진입", "emptyDataHandling": "출근 버튼이 없을 수 있음 (이미 출근한 상태) - click_if_exists로 처리" } }