refactor: 비표준 포맷 13개 시나리오 Format A 통일
- actions 배열(Format B) → 단일 action(Format A) 변환 - fill_form fields: target 키 → name 키 수정 - verify_detail checks: 객체 배열 → 문자열 배열 수정 - 전체 13개 시나리오 E2E 테스트 PASS 확인
This commit is contained in:
@@ -124,45 +124,18 @@
|
||||
"steps": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "🔐 Geolocation API 모킹 (권한 팝업 방지)",
|
||||
"name": "Geolocation API 모킹 (권한 팝업 방지)",
|
||||
"description": "페이지 로드 직후 Geolocation API를 모킹하여 브라우저 권한 팝업이 나타나지 않도록 함",
|
||||
"executeBeforeNavigation": false,
|
||||
"executeImmediately": true,
|
||||
"actions": [
|
||||
{
|
||||
"type": "evaluate",
|
||||
"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, error, options) => { console.log('[E2E] Geolocation.getCurrentPosition - 모킹된 위치 반환'); setTimeout(() => success(mockPosition), 50); }, watchPosition: (success, error, options) => { console.log('[E2E] Geolocation.watchPosition - 모킹된 위치 반환'); setTimeout(() => success(mockPosition), 50); return 1; }, clearWatch: (id) => {} }; Object.defineProperty(navigator, 'geolocation', { value: mockGeolocation, writable: false, configurable: true }); console.log('[E2E] Geolocation API 모킹 완료 - 서울 영등포구 좌표'); return { success: true, coords: mockPosition.coords }; })()",
|
||||
"description": "Geolocation API 모킹 (서울 영등포구 좌표: 37.557358, 126.864414)"
|
||||
},
|
||||
{
|
||||
"type": "wait",
|
||||
"duration": 300,
|
||||
"description": "모킹 적용 대기"
|
||||
}
|
||||
],
|
||||
"action": "evaluate",
|
||||
"script": "(async () => { 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, error, options) => { console.log('[E2E] Geolocation.getCurrentPosition - 모킹된 위치 반환'); setTimeout(() => success(mockPosition), 50); }, watchPosition: (success, error, options) => { console.log('[E2E] Geolocation.watchPosition - 모킹된 위치 반환'); setTimeout(() => success(mockPosition), 50); return 1; }, clearWatch: (id) => {} }; Object.defineProperty(navigator, 'geolocation', { value: mockGeolocation, writable: false, configurable: true }); console.log('[E2E] Geolocation API 모킹 완료 - 서울 영등포구 좌표'); await new Promise(r => setTimeout(r, 300)); return JSON.stringify({ success: true, coords: mockPosition.coords }); })()",
|
||||
"note": "Geolocation API를 모킹하면 브라우저가 위치 권한을 요청하지 않음"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "🗺️ 브라우저 위치 권한 팝업 클릭 (좌측 상단)",
|
||||
"name": "브라우저 위치 권한 팝업 클릭 (좌측 상단)",
|
||||
"description": "Chrome 브라우저 좌측 상단에 나타나는 '사이트에 있는 동안 허용' 팝업 클릭",
|
||||
"actions": [
|
||||
{
|
||||
"type": "wait",
|
||||
"duration": 1500,
|
||||
"description": "위치 권한 팝업 표시 대기"
|
||||
},
|
||||
{
|
||||
"type": "evaluate",
|
||||
"script": "(async function() { const permissionSelectors = [ '[class*=\"permission\"][class*=\"allow\"]', '[class*=\"infobar\"] button', '[aria-label*=\"허용\"]', '[aria-label*=\"Allow\"]', 'button:has-text(\"사이트에 있는 동안 허용\")', 'button:has-text(\"허용\")', 'button:has-text(\"Allow\")', '[data-testid*=\"permission\"]', '.permission-prompt button', '[class*=\"PermissionPrompt\"] button' ]; for (const sel of permissionSelectors) { try { const btn = document.querySelector(sel); if (btn && btn.offsetParent !== null) { btn.click(); console.log('[E2E] 위치 권한 팝업 클릭 성공:', sel); await new Promise(r => setTimeout(r, 500)); return { clicked: true, selector: sel }; } } catch(e) {} } const allButtons = Array.from(document.querySelectorAll('button, [role=\"button\"]')); const allowBtn = allButtons.find(b => { const text = b.innerText || b.textContent || ''; return text.includes('사이트에 있는 동안 허용') || text.includes('허용') || text.includes('Allow'); }); if (allowBtn && allowBtn.offsetParent !== null) { allowBtn.click(); console.log('[E2E] 위치 권한 팝업 텍스트 검색으로 클릭'); return { clicked: true, method: 'textSearch' }; } console.log('[E2E] 위치 권한 팝업 없음 (이미 허용되었거나 모킹으로 우회됨)'); return { clicked: false, reason: 'no_popup_found' }; })()",
|
||||
"description": "좌측 상단 권한 팝업 찾아서 클릭"
|
||||
},
|
||||
{
|
||||
"type": "wait",
|
||||
"duration": 500,
|
||||
"description": "권한 설정 적용 대기"
|
||||
}
|
||||
],
|
||||
"action": "evaluate",
|
||||
"script": "(async function() { await new Promise(r => setTimeout(r, 1500)); const permissionSelectors = [ '[class*=\"permission\"][class*=\"allow\"]', '[class*=\"infobar\"] button', '[aria-label*=\"허용\"]', '[aria-label*=\"Allow\"]', 'button:has-text(\"사이트에 있는 동안 허용\")', 'button:has-text(\"허용\")', 'button:has-text(\"Allow\")', '[data-testid*=\"permission\"]', '.permission-prompt button', '[class*=\"PermissionPrompt\"] button' ]; for (const sel of permissionSelectors) { try { const btn = document.querySelector(sel); if (btn && btn.offsetParent !== null) { btn.click(); console.log('[E2E] 위치 권한 팝업 클릭 성공:', sel); await new Promise(r => setTimeout(r, 500)); return JSON.stringify({ clicked: true, selector: sel }); } } catch(e) {} } const allButtons = Array.from(document.querySelectorAll('button, [role=\"button\"]')); const allowBtn = allButtons.find(b => { const text = b.innerText || b.textContent || ''; return text.includes('사이트에 있는 동안 허용') || text.includes('허용') || text.includes('Allow'); }); if (allowBtn && allowBtn.offsetParent !== null) { allowBtn.click(); console.log('[E2E] 위치 권한 팝업 텍스트 검색으로 클릭'); return JSON.stringify({ clicked: true, method: 'textSearch' }); } console.log('[E2E] 위치 권한 팝업 없음 (이미 허용되었거나 모킹으로 우회됨)'); await new Promise(r => setTimeout(r, 500)); return JSON.stringify({ clicked: false, reason: 'no_popup_found' }); })()",
|
||||
"errorHandling": {
|
||||
"onTimeout": "continue",
|
||||
"onNotFound": "continue",
|
||||
@@ -171,253 +144,65 @@
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "📂 사이드바 메뉴 전체 펼치기",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"type": "screenshot",
|
||||
"name": "after_permission_grant_and_menu_expanded"
|
||||
}
|
||||
],
|
||||
"verification": [
|
||||
"사이드바 메뉴가 펼쳐졌는지 확인"
|
||||
]
|
||||
"action": "evaluate",
|
||||
"script": "(async () => { document.querySelector('.sidebar-scroll')?.scrollTo({top:0,behavior:'instant'}); await new Promise(r => setTimeout(r, 300)); Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('모두 펼치기'))?.click(); await new Promise(r => setTimeout(r, 2000)); return 'Menu expanded'; })()"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "1차 메뉴 찾기: 인사관리 (스크롤 포함)",
|
||||
"description": "사이드바를 스크롤하며 '인사관리' 메뉴를 찾아 클릭",
|
||||
"actions": [
|
||||
{
|
||||
"type": "scrollAndFind",
|
||||
"target": "인사관리",
|
||||
"alternativeTexts": [
|
||||
"인사관리",
|
||||
"인사 관리",
|
||||
"HR",
|
||||
"Human Resource"
|
||||
],
|
||||
"scrollContainer": "sidebar",
|
||||
"maxAttempts": 10,
|
||||
"description": "스크롤하며 인사관리 메뉴 찾기"
|
||||
},
|
||||
{
|
||||
"type": "wait",
|
||||
"duration": 300
|
||||
},
|
||||
{
|
||||
"type": "click_if_exists",
|
||||
"target": "인사관리",
|
||||
"description": "인사관리 메뉴 클릭"
|
||||
},
|
||||
{
|
||||
"type": "wait",
|
||||
"duration": 500,
|
||||
"description": "서브메뉴 펼쳐지기 대기"
|
||||
},
|
||||
{
|
||||
"type": "screenshot",
|
||||
"name": "hr_menu_expanded"
|
||||
}
|
||||
],
|
||||
"verification": [
|
||||
"인사관리 메뉴가 클릭되었는지 확인",
|
||||
"서브메뉴가 펼쳐졌는지 확인",
|
||||
"하위 메뉴 항목들이 보이는지 확인"
|
||||
],
|
||||
"fallback": {
|
||||
"if": "메뉴를 찾을 수 없음",
|
||||
"then": "사이드바 전체를 스크롤하며 재탐색"
|
||||
}
|
||||
"action": "menu_navigate",
|
||||
"level1": "인사관리",
|
||||
"level2": "근태현황"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "2차 메뉴 찾기: 근태현황 (스크롤 포함)",
|
||||
"description": "서브메뉴에서 '근태현황'을 찾아 클릭",
|
||||
"actions": [
|
||||
{
|
||||
"type": "scrollAndFind",
|
||||
"target": "근태현황",
|
||||
"alternativeTexts": [
|
||||
"근태현황",
|
||||
"근태 현황",
|
||||
"출퇴근",
|
||||
"Attendance"
|
||||
],
|
||||
"scrollContainer": "submenu",
|
||||
"maxAttempts": 5,
|
||||
"description": "서브메뉴에서 근태현황 찾기"
|
||||
},
|
||||
{
|
||||
"type": "wait",
|
||||
"duration": 200
|
||||
},
|
||||
{
|
||||
"type": "click_if_exists",
|
||||
"target": "근태현황",
|
||||
"description": "근태현황 메뉴 클릭"
|
||||
},
|
||||
{
|
||||
"type": "wait",
|
||||
"target": "페이지 로드 완료",
|
||||
"timeout": 10000
|
||||
},
|
||||
{
|
||||
"type": "screenshot",
|
||||
"name": "attendance_page"
|
||||
}
|
||||
],
|
||||
"verification": [
|
||||
"근태현황 메뉴 클릭 성공",
|
||||
"페이지 이동 또는 컨텐츠 로드"
|
||||
]
|
||||
"name": "2차 메뉴 도착 확인",
|
||||
"description": "근태현황 페이지에 도착했는지 확인",
|
||||
"action": "verify_url",
|
||||
"target": "/hr/attendance"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "404 에러 감지 및 대체 경로 시도",
|
||||
"description": "페이지 로드 후 404 에러 여부 확인, 404시 대체 경로 탐색",
|
||||
"actions": [
|
||||
{
|
||||
"type": "wait",
|
||||
"duration": 1000
|
||||
},
|
||||
{
|
||||
"type": "checkFor404",
|
||||
"indicators": [
|
||||
"페이지를 찾을 수 없습니다",
|
||||
"404",
|
||||
"Not Found",
|
||||
"존재하지 않거나"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "screenshot",
|
||||
"name": "page_load_result"
|
||||
}
|
||||
],
|
||||
"verification": [
|
||||
"현재 페이지가 404인지 확인"
|
||||
],
|
||||
"onError404": {
|
||||
"description": "404 에러 발생 시 대체 URL 시도",
|
||||
"actions": [
|
||||
{
|
||||
"type": "log",
|
||||
"message": "404 감지 - 대체 경로 탐색 시작"
|
||||
},
|
||||
{
|
||||
"type": "tryAlternativeUrls",
|
||||
"urls": [
|
||||
"/ko/hr/attendance",
|
||||
"/ko/hr/attendance-status",
|
||||
"/ko/hr/checkin"
|
||||
],
|
||||
"stopOnSuccess": true
|
||||
},
|
||||
{
|
||||
"type": "ifStillFailed",
|
||||
"action": "navigateViaMenuClick",
|
||||
"description": "URL 직접 접근 실패 시 메뉴 클릭으로 재시도"
|
||||
}
|
||||
]
|
||||
}
|
||||
"name": "404 에러 감지",
|
||||
"description": "페이지 로드 후 404 에러 여부 확인",
|
||||
"action": "evaluate",
|
||||
"script": "(async () => { await new Promise(r => setTimeout(r, 1000)); const indicators = ['페이지를 찾을 수 없습니다', '404', 'Not Found', '존재하지 않거나']; const bodyText = document.body.innerText || ''; const found = indicators.find(i => bodyText.includes(i)); if (found) return 'WARN: 404 detected - ' + found; return 'PASS: No 404 error'; })()"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "페이지 정상 로드 확인",
|
||||
"description": "근태현황 페이지가 정상적으로 로드되었는지 확인",
|
||||
"actions": [
|
||||
{
|
||||
"type": "verify",
|
||||
"target": "pageTitle",
|
||||
"contains": [
|
||||
"근태현황",
|
||||
"출퇴근",
|
||||
"Attendance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "verify",
|
||||
"target": "pageContent",
|
||||
"notContains": [
|
||||
"404",
|
||||
"찾을 수 없습니다",
|
||||
"Not Found"
|
||||
]
|
||||
}
|
||||
],
|
||||
"verification": [
|
||||
"페이지 제목 '근태현황' 또는 관련 텍스트 표시",
|
||||
"404 에러 메시지 미표시",
|
||||
"콘텐츠가 정상 렌더링됨"
|
||||
],
|
||||
"successCriteria": {
|
||||
"urlPattern": "/hr/attendance",
|
||||
"requiredElements": [
|
||||
"출퇴근",
|
||||
"출근",
|
||||
"퇴근",
|
||||
"현재 시간"
|
||||
]
|
||||
}
|
||||
"action": "evaluate",
|
||||
"script": "(() => { const bodyText = document.body.innerText || ''; const titleCheck = ['근태현황', '출퇴근', 'Attendance'].some(t => bodyText.includes(t)); const no404 = !['404', '찾을 수 없습니다', 'Not Found'].some(t => bodyText.includes(t)); if (titleCheck && no404) return 'PASS: Page loaded correctly'; if (!titleCheck) return 'WARN: Page title not found'; return 'FAIL: 404 error detected'; })()"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "브라우저 위치 권한 설정",
|
||||
"description": "Playwright context에서 위치 정보 권한을 허용하고 가상 위치 설정",
|
||||
"playwright": {
|
||||
"code": "await context.grantPermissions(['geolocation']);",
|
||||
"setGeolocation": {
|
||||
"latitude": 37.557358,
|
||||
"longitude": 126.864414
|
||||
}
|
||||
},
|
||||
"expect": {
|
||||
"permissionGranted": "geolocation"
|
||||
}
|
||||
"action": "evaluate",
|
||||
"script": "(() => { console.log('[E2E] Geolocation permission should be granted via Playwright context.grantPermissions'); return 'Geolocation permission note: handled by Playwright context'; })()"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "위치 정보 로딩 대기",
|
||||
"description": "Google Map 로딩 및 현재 위치 표시 대기",
|
||||
"waitFor": {
|
||||
"type": "element",
|
||||
"selector": "region[name='지도']",
|
||||
"timeout": 10000
|
||||
},
|
||||
"expect": {
|
||||
"mapLoaded": true,
|
||||
"locationMarkerVisible": true
|
||||
}
|
||||
"action": "wait_for_element",
|
||||
"target": "region[name='지도'], [class*='map'], canvas, iframe[src*='map']",
|
||||
"timeout": 10000
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "사용자 정보 확인",
|
||||
"description": "출퇴근 패널에서 로그인한 사용자 정보 확인",
|
||||
"action": "verify_element",
|
||||
"target": "body",
|
||||
"verify": {
|
||||
"userInfo": {
|
||||
"name": "홍킬동",
|
||||
"department": "부서명 · 개발중인 메뉴"
|
||||
},
|
||||
"currentTime": {
|
||||
"format": "HH:mm:ss",
|
||||
"updating": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -425,66 +210,29 @@
|
||||
"id": 11,
|
||||
"name": "출근 상태 확인",
|
||||
"description": "현재 출퇴근 상태 확인 (출근 전/출근 후)",
|
||||
"capture": {
|
||||
"variable": "attendanceStatus",
|
||||
"checkElements": [
|
||||
{
|
||||
"selector": "button:has-text('출근하기')",
|
||||
"status": "not_checked_in"
|
||||
},
|
||||
{
|
||||
"selector": "text=출근 완료",
|
||||
"status": "checked_in"
|
||||
},
|
||||
{
|
||||
"selector": "button:has-text('퇴근하기')",
|
||||
"status": "ready_to_checkout"
|
||||
}
|
||||
]
|
||||
}
|
||||
"action": "evaluate",
|
||||
"script": "(() => { const bodyText = document.body.innerText || ''; if (document.querySelector(\"button\") && Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('출근하기'))) return 'not_checked_in'; if (bodyText.includes('출근 완료')) return 'checked_in'; if (Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('퇴근하기'))) return 'ready_to_checkout'; return 'unknown'; })()"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "출근하기 (미출근 상태인 경우)",
|
||||
"description": "출근하기 버튼이 활성화된 경우 클릭하여 출근 기록",
|
||||
"action": "click_if_exists",
|
||||
"target": "출근하기",
|
||||
"condition": {
|
||||
"if": "{attendanceStatus} == 'not_checked_in'"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"type": "click_if_exists",
|
||||
"target": "출근하기"
|
||||
}
|
||||
],
|
||||
"waitFor": {
|
||||
"type": "text",
|
||||
"content": "출근 완료",
|
||||
"timeout": 5000
|
||||
},
|
||||
"expect": {
|
||||
"toast": [
|
||||
"출근",
|
||||
"완료",
|
||||
"성공"
|
||||
],
|
||||
"visible": [
|
||||
"출근 완료",
|
||||
"출근 시간"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "출근 완료 상태 확인",
|
||||
"description": "출근 완료 후 상태 및 출근 시간 표시 확인",
|
||||
"action": "verify_element",
|
||||
"target": "body",
|
||||
"verify": {
|
||||
"visible": [
|
||||
"출근 완료"
|
||||
],
|
||||
"checkInTime": {
|
||||
"format": "HH:mm:ss",
|
||||
"displayed": true
|
||||
},
|
||||
"buttonState": {
|
||||
"출근하기": "hidden_or_disabled",
|
||||
"퇴근하기": "enabled_or_visible"
|
||||
@@ -495,6 +243,8 @@
|
||||
"id": 14,
|
||||
"name": "퇴근하기 버튼 상태 확인",
|
||||
"description": "출근 완료 후 퇴근하기 버튼 활성화 여부 확인",
|
||||
"action": "verify_element",
|
||||
"target": "body",
|
||||
"verify": {
|
||||
"button": {
|
||||
"target": "퇴근하기",
|
||||
@@ -508,39 +258,18 @@
|
||||
"name": "퇴근하기 (선택적)",
|
||||
"description": "퇴근하기 버튼이 활성화된 경우 클릭하여 퇴근 기록",
|
||||
"optional": true,
|
||||
"action": "click_if_exists",
|
||||
"target": "퇴근하기",
|
||||
"condition": {
|
||||
"if": "button[name='퇴근하기']:enabled"
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"type": "click_if_exists",
|
||||
"target": "퇴근하기"
|
||||
}
|
||||
],
|
||||
"waitFor": {
|
||||
"type": "text",
|
||||
"content": [
|
||||
"퇴근 완료",
|
||||
"퇴근 시간"
|
||||
],
|
||||
"timeout": 5000
|
||||
},
|
||||
"expect": {
|
||||
"toast": [
|
||||
"퇴근",
|
||||
"완료",
|
||||
"성공"
|
||||
],
|
||||
"visible": [
|
||||
"퇴근 완료",
|
||||
"퇴근 시간"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "최종 상태 확인",
|
||||
"description": "출퇴근 기록 후 최종 상태 확인",
|
||||
"action": "verify_element",
|
||||
"target": "body",
|
||||
"verify": {
|
||||
"url": "/hr/attendance",
|
||||
"mapDisplayed": true,
|
||||
@@ -615,4 +344,4 @@
|
||||
"note": "서울 영등포구 좌표 (테스트 회사 위치 가정)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user