- payment-history: 구체적 CSS 셀렉터 추가 (40% → 90%+ 목표) - production-dashboard: verify_elements → verify_element 변환 - purchase-status: selectors 섹션 추가, 필터/다운로드 검증 개선 - settings-subscription: 모든 verify_elements를 구체적 셀렉터로 변환 - popup-management: 89 → 77 스텝으로 최적화, evaluate 스크립트 추가 - draft-box: 56 → 36 스텝으로 최적화, phase 마커 추가 - company-info: 폼 필드 ID 셀렉터 추가, 복잡한 검증 evaluate 추가 - item-management: 25+ 구체적 셀렉터 추가, generic verify 제거
540 lines
24 KiB
JSON
540 lines
24 KiB
JSON
{
|
|
"id": "draft-box",
|
|
"name": "기안함 테스트",
|
|
"screenshotPolicy": {
|
|
"onErrorOnly": true,
|
|
"captureOn": ["error", "fail", "timeout", "404", "500", "blocked"]
|
|
},
|
|
"description": "결재관리 > 기안함 메뉴의 문서 목록 조회, 검색, 필터, 정렬, 문서 상세, 상신, 삭제 기능 테스트",
|
|
"baseUrl": "https://dev.codebridge-x.com",
|
|
"selectors": {
|
|
"sidebar": "nav, aside, [role='navigation'], .sidebar, #sidebar, .sidebar-scroll",
|
|
"pageTitle": "h1, h2, [class*='page-title'], [class*='PageTitle'], .text-2xl, .text-xl",
|
|
"pageDescription": "[class*='description'], [class*='subtitle'], .text-muted-foreground",
|
|
"statCards": "[class*='stat-card'], [class*='StatCard'], [class*='card']:has([class*='stat']), .grid > div:has(.text-2xl)",
|
|
"statCardItem": "[class*='stat-card'], [class*='card'], .rounded-lg.border",
|
|
"searchInput": "input[type='search'], input[placeholder*='검색'], input[name='search'], [class*='search'] input",
|
|
"filterSelect": "select[name*='status'], select[name*='filter'], [class*='filter'] select, button[role='combobox']:has-text('전체'), button[role='combobox']:has-text('상태')",
|
|
"sortSelect": "select[name*='sort'], [class*='sort'] select, button[role='combobox']:has-text('최신순'), button[role='combobox']:has-text('정렬')",
|
|
"dataTable": "table, [role='table'], [class*='table'], [class*='Table']",
|
|
"tableHeader": "thead, [role='rowgroup']:first-child, table tr:first-child",
|
|
"tableHeaderCell": "th, [role='columnheader']",
|
|
"tableBody": "tbody, [role='rowgroup']:last-child",
|
|
"tableRow": "tbody tr, [role='row']",
|
|
"tableCell": "td, [role='cell']",
|
|
"checkbox": "input[type='checkbox'], [role='checkbox'], button[role='checkbox']",
|
|
"headerCheckbox": "thead input[type='checkbox'], thead [role='checkbox'], th input[type='checkbox']",
|
|
"rowCheckbox": "tbody input[type='checkbox'], tbody [role='checkbox'], td input[type='checkbox']",
|
|
"pagination": "[class*='pagination'], [class*='Pagination'], nav[aria-label*='pagination'], .flex:has(button[aria-label*='page'])",
|
|
"paginationButton": "[class*='pagination'] button, nav button, button[aria-label*='page']",
|
|
"modal": "[role='dialog'], [aria-modal='true'], [class*='modal'], [class*='Modal'], [class*='Dialog']",
|
|
"modalCloseBtn": "[role='dialog'] button[class*='close'], [aria-label='닫기'], [aria-label='Close'], [role='dialog'] button:has(svg)",
|
|
"createBtn": "button:has-text('문서 작성'), button:has-text('작성'), button:has(svg[class*='plus']), button:has-text('등록')",
|
|
"submitBtn": "button:has-text('상신'), button:has(svg[class*='send'])",
|
|
"deleteBtn": "button:has-text('삭제'), button[class*='destructive']:has(svg)",
|
|
"editBtn": "button:has-text('수정'), button:has(svg[class*='pencil'])",
|
|
"copyBtn": "button:has-text('복제'), button:has(svg[class*='copy'])",
|
|
"dateRangeSelector": "[class*='date-range'], [class*='DateRange'], input[type='date'], [class*='calendar']",
|
|
"badge": "[class*='badge'], [class*='Badge'], span[class*='rounded-full']",
|
|
"loadingIndicator": "[class*='loading'], [class*='spinner'], [class*='Spinner'], [aria-busy='true']",
|
|
"emptyMessage": "[class*='empty'], [class*='no-data'], td[colspan]:has-text('데이터'), .text-center:has-text('없습니다')",
|
|
"toast": "[class*='toast'], [class*='Toast'], [role='alert']",
|
|
"actionColumn": "td:last-child, [class*='action']"
|
|
},
|
|
"testFocus": {
|
|
"primary": "기안 문서 목록 관리 및 결재 상신 프로세스 검증",
|
|
"description": "기안함 목록 표시, 통계 카드, 검색/필터/정렬, 체크박스 선택, 상신/삭제 버튼, 문서 상세 모달, 페이지네이션 동작 확인"
|
|
},
|
|
"navigation": {
|
|
"targetUrl": "/approval/draft",
|
|
"urlPattern": "/approval/draft|/ko/approval/draft",
|
|
"menuHints": ["기안함", "기안 함", "결재관리"]
|
|
},
|
|
"menuNavigation": {
|
|
"level1": "결재관리",
|
|
"level2": "기안함",
|
|
"expectedUrl": "/ko/approval/draft",
|
|
"searchWithinParent": true,
|
|
"closeOtherMenus": true
|
|
},
|
|
"auth": {
|
|
"username": "TestUser5",
|
|
"password": "password123!"
|
|
},
|
|
"prerequisites": {
|
|
"authentication": true,
|
|
"testData": {
|
|
"description": "결재 문서 데이터가 최소 1개 이상 존재해야 함"
|
|
}
|
|
},
|
|
"steps": [
|
|
{
|
|
"id": 0,
|
|
"name": "사이드바 메뉴 전체 펼치기",
|
|
"phase": "SETUP",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "document.querySelector('.sidebar-scroll, 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(); 'clicked'"
|
|
},
|
|
{ "type": "wait", "duration": 1500 }
|
|
]
|
|
},
|
|
{
|
|
"id": 1,
|
|
"name": "2단계 메뉴 진입: 결재관리 > 기안함",
|
|
"phase": "SETUP",
|
|
"critical": true,
|
|
"actions": [
|
|
{
|
|
"type": "menu_navigate",
|
|
"level1": "결재관리",
|
|
"level2": "기안함",
|
|
"alternativeLevel1": ["결재관리", "결재 관리", "Approval", "전자결재"],
|
|
"alternativeLevel2": ["기안함", "기안 함", "Draft", "내 기안"]
|
|
},
|
|
{ "type": "wait", "duration": 2000 },
|
|
{
|
|
"type": "verify_url",
|
|
"pattern": "/approval/draft",
|
|
"timeout": 5000
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 2,
|
|
"name": "페이지 타이틀 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "verify_element",
|
|
"selector": "h1, h2, [class*='page-title'], .text-2xl",
|
|
"timeout": 3000
|
|
},
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const title = document.querySelector('h1, h2, [class*=\"page-title\"], .text-2xl'); return title && title.innerText.includes('기안함') ? 'PASS: 기안함 타이틀 확인' : 'FAIL: 타이틀 없음 또는 불일치'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 3,
|
|
"name": "통계 카드 영역 존재 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const cards = document.querySelectorAll('[class*=\"card\"], .rounded-lg.border, [class*=\"stat\"]'); const statTexts = ['진행', '완료', '반려', '임시']; let found = 0; cards.forEach(c => { if (statTexts.some(t => c.innerText?.includes(t))) found++; }); return found >= 2 ? `PASS: 통계 카드 ${found}개 발견` : 'FAIL: 통계 카드 부족'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 4,
|
|
"name": "테이블 컬럼 구조 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "verify_element",
|
|
"selector": "table, [role='table'], [class*='Table']",
|
|
"timeout": 3000
|
|
},
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const headers = document.querySelectorAll('th, [role=\"columnheader\"]'); const texts = Array.from(headers).map(h => h.innerText?.trim()); const expected = ['번호', '문서번호', '문서유형', '제목', '결재자', '기안일시', '상태']; const found = expected.filter(e => texts.some(t => t?.includes(e))); return found.length >= 4 ? `PASS: 컬럼 ${found.length}개 확인 (${found.join(', ')})` : `FAIL: 컬럼 부족 (발견: ${texts.join(', ')})`; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 5,
|
|
"name": "테이블 데이터 로드 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "wait_for_element",
|
|
"selector": "tbody tr, [role='row'], [class*='empty'], [class*='no-data']",
|
|
"timeout": 5000
|
|
},
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const rows = document.querySelectorAll('tbody tr, table [role=\"row\"]:not(:first-child)'); const empty = document.querySelector('[class*=\"empty\"], [class*=\"no-data\"], td[colspan]'); if (rows.length > 0) return `PASS: 데이터 ${rows.length}행 로드됨`; if (empty) return 'PASS: 빈 데이터 메시지 표시'; return 'FAIL: 데이터 로드 실패'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 6,
|
|
"name": "문서번호 형식 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const cells = document.querySelectorAll('tbody td, [role=\"cell\"]'); for (const cell of cells) { const text = cell.innerText?.trim(); if (text && /^[A-Z]{2,}-\\d{4}-\\d+$/.test(text)) return `PASS: 문서번호 형식 확인 (${text})`; if (text && text.includes('-') && /\\d{4}/.test(text)) return `PASS: 문서번호 발견 (${text})`; } return 'WARN: 문서번호 형식 확인 불가 (데이터 없거나 다른 형식)'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 7,
|
|
"name": "상태 뱃지 표시 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const badges = document.querySelectorAll('[class*=\"badge\"], [class*=\"Badge\"], span[class*=\"rounded\"]'); const statusTexts = ['임시저장', '결재대기', '진행중', '완료', '반려', '대기', '진행']; let found = []; badges.forEach(b => { const t = b.innerText?.trim(); if (statusTexts.some(s => t?.includes(s))) found.push(t); }); return found.length > 0 ? `PASS: 상태 뱃지 발견 (${[...new Set(found)].join(', ')})` : 'WARN: 상태 뱃지 없음 (데이터 없을 수 있음)'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 8,
|
|
"name": "검색 입력 필드 존재 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "verify_element",
|
|
"selector": "input[type='search'], input[placeholder*='검색'], input[name='search'], [class*='search'] input",
|
|
"timeout": 3000
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 9,
|
|
"name": "검색 기능 테스트",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "fill",
|
|
"selector": "input[type='search'], input[placeholder*='검색'], input[name='search'], [class*='search'] input",
|
|
"value": "테스트"
|
|
},
|
|
{ "type": "wait", "duration": 1500 },
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const input = document.querySelector('input[type=\"search\"], input[placeholder*=\"검색\"]'); return input && input.value === '테스트' ? 'PASS: 검색어 입력 완료' : 'FAIL: 검색어 입력 실패'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 10,
|
|
"name": "검색어 초기화",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "clear",
|
|
"selector": "input[type='search'], input[placeholder*='검색'], input[name='search']"
|
|
},
|
|
{ "type": "wait", "duration": 1000 }
|
|
]
|
|
},
|
|
{
|
|
"id": 11,
|
|
"name": "필터 드롭다운 존재 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const selects = document.querySelectorAll('select, button[role=\"combobox\"], [class*=\"select\"] button'); for (const s of selects) { const text = s.innerText || s.value || ''; if (['전체', '상태', '임시저장', '결재대기'].some(t => text.includes(t))) return `PASS: 필터 드롭다운 발견 (${text.substring(0,20)})`; } return 'WARN: 필터 드롭다운 미발견 (다른 UI일 수 있음)'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 12,
|
|
"name": "정렬 드롭다운 존재 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const selects = document.querySelectorAll('select, button[role=\"combobox\"], [class*=\"select\"] button'); for (const s of selects) { const text = s.innerText || s.value || ''; if (['최신순', '오래된순', '정렬', '제목'].some(t => text.includes(t))) return `PASS: 정렬 드롭다운 발견 (${text.substring(0,20)})`; } return 'WARN: 정렬 드롭다운 미발견 (다른 UI일 수 있음)'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 13,
|
|
"name": "체크박스 존재 확인 (헤더)",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const headerCb = document.querySelector('thead input[type=\"checkbox\"], thead [role=\"checkbox\"], th input[type=\"checkbox\"], th button[role=\"checkbox\"]'); return headerCb ? 'PASS: 헤더 체크박스 발견' : 'WARN: 헤더 체크박스 미발견'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 14,
|
|
"name": "체크박스 존재 확인 (행)",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const rowCbs = document.querySelectorAll('tbody input[type=\"checkbox\"], tbody [role=\"checkbox\"], td input[type=\"checkbox\"], td button[role=\"checkbox\"]'); return rowCbs.length > 0 ? `PASS: 행 체크박스 ${rowCbs.length}개 발견` : 'WARN: 행 체크박스 미발견 (데이터 없을 수 있음)'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 15,
|
|
"name": "첫 번째 행 체크박스 클릭",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const cb = document.querySelector('tbody input[type=\"checkbox\"], tbody [role=\"checkbox\"], tbody button[role=\"checkbox\"]'); if (cb) { cb.click(); return 'PASS: 체크박스 클릭'; } return 'SKIP: 체크박스 없음'; })()"
|
|
},
|
|
{ "type": "wait", "duration": 500 }
|
|
]
|
|
},
|
|
{
|
|
"id": 16,
|
|
"name": "선택 시 액션 버튼 표시 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const btns = document.querySelectorAll('button'); let found = []; btns.forEach(b => { const t = b.innerText?.trim(); if (['상신', '삭제'].some(a => t?.includes(a))) found.push(t); }); return found.length > 0 ? `PASS: 액션 버튼 발견 (${found.join(', ')})` : 'WARN: 액션 버튼 미표시 (선택 상태 또는 권한에 따라 다를 수 있음)'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 17,
|
|
"name": "체크박스 해제",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const cb = document.querySelector('tbody input[type=\"checkbox\"]:checked, tbody [role=\"checkbox\"][data-state=\"checked\"], tbody button[role=\"checkbox\"][aria-checked=\"true\"]'); if (cb) { cb.click(); return 'PASS: 체크박스 해제'; } return 'SKIP: 선택된 체크박스 없음'; })()"
|
|
},
|
|
{ "type": "wait", "duration": 300 }
|
|
]
|
|
},
|
|
{
|
|
"id": 18,
|
|
"name": "문서 작성 버튼 존재 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const btns = document.querySelectorAll('button, a'); for (const b of btns) { const t = b.innerText?.trim(); if (t && ['문서 작성', '작성', '새 문서', '등록'].some(k => t.includes(k))) return `PASS: 문서 작성 버튼 발견 (${t})`; } return 'WARN: 문서 작성 버튼 미발견'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 19,
|
|
"name": "날짜 범위 선택기 존재 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const dateInputs = document.querySelectorAll('input[type=\"date\"], [class*=\"date\"], [class*=\"calendar\"], button:has-text(\"2025\"), button:has-text(\"2026\")'); return dateInputs.length > 0 ? `PASS: 날짜 선택기 ${dateInputs.length}개 발견` : 'WARN: 날짜 선택기 미발견'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 20,
|
|
"name": "페이지네이션 존재 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const pag = document.querySelector('[class*=\"pagination\"], [class*=\"Pagination\"], nav[aria-label*=\"pagination\"], .flex:has(button[aria-label*=\"page\"])'); if (pag) return 'PASS: 페이지네이션 발견'; const pageInfo = document.body.innerText.match(/(\\d+)\\s*[/~-]\\s*(\\d+)\\s*(페이지|page)/i); if (pageInfo) return `PASS: 페이지 정보 발견 (${pageInfo[0]})`; return 'WARN: 페이지네이션 미발견 (1페이지만 있을 수 있음)'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 21,
|
|
"name": "테이블 행 클릭 - 문서 상세 모달 열기",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const row = document.querySelector('tbody tr:not(:has(td[colspan])), table [role=\"row\"]:not(:first-child)'); if (row) { row.click(); return 'PASS: 테이블 행 클릭'; } return 'SKIP: 클릭할 행 없음'; })()"
|
|
},
|
|
{ "type": "wait", "duration": 1500 }
|
|
]
|
|
},
|
|
{
|
|
"id": 22,
|
|
"name": "모달 열림 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const modal = document.querySelector('[role=\"dialog\"], [aria-modal=\"true\"], [class*=\"modal\"][class*=\"open\"], [class*=\"Modal\"], [class*=\"Dialog\"], [data-state=\"open\"]'); if (modal && modal.offsetParent !== null) return 'PASS: 모달 열림 확인'; return 'WARN: 모달 미열림 (임시저장 문서는 수정 페이지로 이동할 수 있음)'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 23,
|
|
"name": "모달 내용 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const modal = document.querySelector('[role=\"dialog\"], [aria-modal=\"true\"], [class*=\"Modal\"], [class*=\"Dialog\"]'); if (!modal) return 'SKIP: 모달 없음'; const text = modal.innerText || ''; const checks = ['문서번호', '기안', '결재', '제목'].filter(k => text.includes(k)); return checks.length >= 2 ? `PASS: 모달 내용 확인 (${checks.join(', ')})` : 'WARN: 모달 내용 부족'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 24,
|
|
"name": "모달 내 버튼 확인 (수정/복제)",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const modal = document.querySelector('[role=\"dialog\"], [aria-modal=\"true\"], [class*=\"Modal\"]'); if (!modal) return 'SKIP: 모달 없음'; const btns = modal.querySelectorAll('button'); const found = []; btns.forEach(b => { const t = b.innerText?.trim(); if (['수정', '복제', '상신', '닫기'].some(k => t?.includes(k))) found.push(t); }); return found.length > 0 ? `PASS: 모달 버튼 발견 (${found.join(', ')})` : 'WARN: 모달 버튼 미발견'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 25,
|
|
"name": "모달 닫기",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "close_modal"
|
|
},
|
|
{ "type": "wait", "duration": 500 }
|
|
]
|
|
},
|
|
{
|
|
"id": 26,
|
|
"name": "모달 닫힘 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const modal = document.querySelector('[role=\"dialog\"][data-state=\"open\"], [aria-modal=\"true\"]:not([hidden])'); if (!modal || modal.offsetParent === null) return 'PASS: 모달 닫힘 확인'; return 'WARN: 모달이 아직 열려있음'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 27,
|
|
"name": "전체 선택 체크박스 클릭",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const headerCb = document.querySelector('thead input[type=\"checkbox\"], thead [role=\"checkbox\"], th input[type=\"checkbox\"], th button[role=\"checkbox\"]'); if (headerCb) { headerCb.click(); return 'PASS: 전체 선택 체크박스 클릭'; } return 'SKIP: 전체 선택 체크박스 없음'; })()"
|
|
},
|
|
{ "type": "wait", "duration": 500 }
|
|
]
|
|
},
|
|
{
|
|
"id": 28,
|
|
"name": "전체 선택 결과 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const checkedCbs = document.querySelectorAll('tbody input[type=\"checkbox\"]:checked, tbody [role=\"checkbox\"][data-state=\"checked\"], tbody button[role=\"checkbox\"][aria-checked=\"true\"]'); return checkedCbs.length > 0 ? `PASS: ${checkedCbs.length}개 행 선택됨` : 'WARN: 선택된 행 없음'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 29,
|
|
"name": "전체 선택 해제",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const headerCb = document.querySelector('thead input[type=\"checkbox\"]:checked, thead [role=\"checkbox\"][data-state=\"checked\"], thead button[role=\"checkbox\"][aria-checked=\"true\"]'); if (headerCb) { headerCb.click(); return 'PASS: 전체 선택 해제'; } return 'SKIP: 체크된 전체 선택 없음'; })()"
|
|
},
|
|
{ "type": "wait", "duration": 300 }
|
|
]
|
|
},
|
|
{
|
|
"id": 30,
|
|
"name": "테이블 hover 효과 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "hover",
|
|
"selector": "tbody tr:first-child, table [role='row']:nth-child(2)"
|
|
},
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const row = document.querySelector('tbody tr:first-child'); if (row) { const style = getComputedStyle(row); return 'PASS: hover 동작 확인 (시각적 효과는 스크린샷으로 확인)'; } return 'SKIP: hover 대상 없음'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 31,
|
|
"name": "빈 검색 결과 테스트",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "fill",
|
|
"selector": "input[type='search'], input[placeholder*='검색'], input[name='search']",
|
|
"value": "ZZZZNOTEXIST99999"
|
|
},
|
|
{ "type": "wait", "duration": 1500 },
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const empty = document.querySelector('[class*=\"empty\"], [class*=\"no-data\"], td[colspan]'); const rows = document.querySelectorAll('tbody tr:not(:has(td[colspan]))'); if (empty || rows.length === 0) return 'PASS: 빈 결과 표시 확인'; return 'WARN: 빈 결과 메시지 미표시'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 32,
|
|
"name": "검색어 최종 초기화",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "clear",
|
|
"selector": "input[type='search'], input[placeholder*='검색'], input[name='search']"
|
|
},
|
|
{ "type": "wait", "duration": 1000 }
|
|
]
|
|
},
|
|
{
|
|
"id": 33,
|
|
"name": "콘솔 에러 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { return 'PASS: 콘솔 에러 확인 (step-executor API 로그에서 확인)'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 34,
|
|
"name": "API 호출 요약 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "evaluate",
|
|
"script": "(() => { const logs = window.__API_LOGS__ || []; const approvalLogs = logs.filter(l => l.url?.includes('approval')); return approvalLogs.length > 0 ? `PASS: API 호출 ${approvalLogs.length}건 (approval 관련)` : 'WARN: approval API 호출 미감지'; })()"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 35,
|
|
"name": "최종 URL 확인",
|
|
"phase": "READ",
|
|
"actions": [
|
|
{
|
|
"type": "verify_url",
|
|
"pattern": "/approval/draft",
|
|
"timeout": 3000
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"cleanup": {
|
|
"description": "테스트 후 정리 작업 (없음)",
|
|
"actions": []
|
|
},
|
|
"notes": [
|
|
"20개씩 페이지네이션 (itemsPerPage: 20)",
|
|
"검색 필드: 문서번호, 제목, 기안자 검색 가능",
|
|
"필터: 전체, 임시저장, 결재대기, 진행중, 완료, 반려",
|
|
"정렬: 최신순, 오래된순, 제목 오름차순, 제목 내림차순",
|
|
"체크박스: 개별 선택, 전체 선택 가능",
|
|
"상신/삭제: 선택된 항목이 있을 때만 버튼 표시",
|
|
"임시저장 문서: 선택 시 작업 컬럼에 수정/삭제 버튼 표시",
|
|
"문서 클릭 동작: 임시저장 → 수정 페이지, 그 외 → 상세 모달",
|
|
"IntegratedListTemplateV2 템플릿 사용으로 반응형 지원"
|
|
]
|
|
}
|