Files
sam-scenarios/quality-inspection.json

153 lines
7.8 KiB
JSON
Raw Normal View History

{
"enabled": true,
"id": "quality-inspection",
"name": "제품검사관리 테스트",
"version": "2.0.0",
"screenshotPolicy": {
"captureOnFail": true,
"captureOnPass": false
},
"description": "품질관리 > 제품검사관리 메뉴의 페이지 로드, 테이블 구조, 검색/필터, 상세 조회 검증 (READ-only, CRUD 제외 - API 필수 필드 매핑 미완)",
"baseUrl": "https://dev.codebridge-x.com",
"menuNavigation": {
"level1": "품질관리",
"level2": "제품검사관리",
"expectedUrl": "/quality/inspections",
"searchWithinParent": true,
"closeOtherMenus": true
},
"auth": { "username": "TestUser5", "password": "password123!" },
"steps": [
{
"id": 1,
"name": "메뉴 진입: 품질관리 > 제품검사관리",
"action": "menu_navigate",
"level1": "품질관리",
"level2": "제품검사관리",
"expected": { "url_contains": "/quality/inspections" }
},
{
"id": 2,
"name": "페이지 로드 대기",
"action": "wait",
"timeout": 3000
},
{
"id": 3,
"name": "URL 검증",
"action": "verify_url",
"expected": { "url_contains": "/quality/inspections" }
},
{
"id": 4,
"name": "목업 감지",
"action": "verify_not_mockup",
"checks": ["제품검사 목록 표시", "등록 버튼 존재"]
},
{
"id": 5,
"name": "테이블 로드 대기",
"action": "wait_for_table",
"timeout": 10000
},
{
"id": 6,
"name": "통계 카드 확인",
"action": "evaluate",
"script": "(() => { const cards = document.querySelectorAll('[class*=\"card\"], [class*=\"Card\"], [class*=\"stat\"], [class*=\"Stat\"]'); const texts = Array.from(cards).map(c => c.innerText?.substring(0, 40)).filter(Boolean); return texts.length > 0 ? 'pass: Stats cards=' + texts.length : 'warn: No stat cards'; })()"
},
{
"id": 7,
"name": "테이블 컬럼 구조 확인",
"action": "evaluate",
"script": "(() => { const ths = Array.from(document.querySelectorAll('table thead th, table th, [role=\"columnheader\"]')); const cols = ths.map(t => t.innerText?.trim()).filter(Boolean); return cols.length > 0 ? 'pass: columns=' + cols.length + ' [' + cols.join(', ') + ']' : 'warn: No table headers'; })()"
},
{
"id": 8,
"name": "테이블 데이터 행 확인",
"action": "evaluate",
"script": "(() => { const rows = document.querySelectorAll('table tbody tr'); const visRows = Array.from(rows).filter(r => r.offsetParent !== null); return visRows.length > 0 ? 'pass: ' + visRows.length + ' rows in table' : 'warn: No data rows (empty table)'; })()"
},
{
"id": 9,
"name": "필터/검색 UI 확인",
"action": "evaluate",
"script": "(() => { const R = {}; const searchInputs = document.querySelectorAll('input[type=\"search\"], input[placeholder*=\"검색\"], input[placeholder*=\"조회\"]'); R.searchInputs = searchInputs.length; const selects = document.querySelectorAll('select, [role=\"combobox\"], button[class*=\"Select\"]'); R.filters = selects.length; const tabs = document.querySelectorAll('button[role=\"tab\"]'); R.tabs = tabs.length; return 'pass: search=' + R.searchInputs + ' filters=' + R.filters + ' tabs=' + R.tabs; })()"
},
{
"id": 10,
"name": "[READ] 첫 번째 행 클릭 (상세 보기)",
"phase": "READ",
"action": "click_first_row"
},
{
"id": 11,
"name": "[READ] 상세 대기",
"phase": "READ",
"action": "wait",
"timeout": 2000
},
{
"id": 12,
"name": "[READ] 상세 다이얼로그/페이지 확인",
"phase": "READ",
"action": "evaluate",
"script": "(async () => { const R = { phase: 'DETAIL_CHECK' }; const dlg = document.querySelector('[role=\"dialog\"]'); const isVis = el => !!el && el.getBoundingClientRect().width > 0; if (isVis(dlg)) { R.type = 'dialog'; R.text = dlg.innerText?.substring(0, 200); R.hasFields = dlg.querySelectorAll('input, textarea, select, label, dt, [class*=\"field\"]').length; R.ok = true; R.info = 'pass: 상세 다이얼로그 열림 (fields=' + R.hasFields + ')'; } else if (window.location.href.includes('/inspections/')) { R.type = 'page'; R.text = document.body.innerText?.substring(0, 200); R.ok = true; R.info = 'pass: 상세 페이지 이동'; } else { R.ok = true; R.info = 'warn: 상세 화면 미확인 (행 클릭 반응 없음)'; } return JSON.stringify(R); })()",
"timeout": 5000
},
{
"id": 13,
"name": "[READ] 상세 필드 확인",
"phase": "READ",
"action": "evaluate",
"script": "(() => { const R = { phase: 'FIELD_CHECK' }; const dlg = document.querySelector('[role=\"dialog\"]'); const scope = (dlg && dlg.getBoundingClientRect().width > 0) ? dlg : document; const labels = Array.from(scope.querySelectorAll('label, dt, [class*=\"label\"], [class*=\"Label\"]')); const fields = labels.map(l => l.innerText?.trim()).filter(t => t && t.length < 30); R.fieldCount = fields.length; R.fields = fields.slice(0, 15); R.ok = true; R.info = fields.length > 0 ? 'pass: ' + fields.length + ' fields found' : 'warn: no labeled fields'; return JSON.stringify(R); })()"
},
{
"id": 14,
"name": "[READ] 등록 버튼 존재 확인 (클릭하지 않음)",
"phase": "READ",
"action": "evaluate",
"script": "(() => { const btns = Array.from(document.querySelectorAll('button')).filter(b => b.offsetParent !== null); const createBtn = btns.find(b => /등록|추가|신규/.test(b.innerText?.trim())); const editBtn = btns.find(b => /수정|편집/.test(b.innerText?.trim())); const delBtn = btns.find(b => /삭제/.test(b.innerText?.trim())); return 'pass: 등록=' + (createBtn ? '있음' : '없음') + ' 수정=' + (editBtn ? '있음' : '없음') + ' 삭제=' + (delBtn ? '있음' : '없음'); })()"
},
{
"id": 15,
"name": "[READ] 모달/다이얼로그 닫기",
"phase": "READ",
"action": "close_modal_if_open"
},
{
"id": 16,
"name": "페이지네이션 확인",
"action": "evaluate",
"script": "(() => { const p = document.querySelector('[class*=\"pagination\"], [class*=\"Pagination\"], nav[aria-label*=\"page\"]'); const pageButtons = Array.from(document.querySelectorAll('button')).filter(b => /^\\d+$/.test(b.innerText?.trim())); return p ? 'pass: Pagination found' : pageButtons.length > 0 ? 'pass: Page buttons=' + pageButtons.length : 'No pagination (ok)'; })()"
},
{
"id": 17,
"name": "[SUMMARY] API 호출 통계",
"action": "evaluate",
"script": "(() => { const logs = window.__E2E__ ? window.__E2E__.getApiLogs().logs : ((window.__E2E__?window.__E2E__.getApiLogs().logs:[])); const inspApi = logs.filter(l => l.url?.includes('inspection')); const failedApis = logs.filter(l => l.status >= 400); return 'pass: API total=' + logs.length + ' inspection=' + inspApi.length + ' failed=' + failedApis.length; })()"
}
],
"expectedAPIs": [
{ "method": "GET", "endpoint": "/api/v1/inspections", "description": "제품검사 목록 조회" },
{ "method": "GET", "endpoint": "/api/v1/inspections/stats", "description": "통계 조회" }
],
"knownIssues": [
{
"issue": "GET /api/v1/inspections/calendar → 404",
"reason": "백엔드 라우트 미구현. 프론트엔드가 페이지 로드 시 자동 호출하지만 USE_MOCK_FALLBACK으로 동작",
"severity": "low",
"action": "백엔드에 calendar 엔드포인트 구현 필요"
},
{
"issue": "CRUD 테스트 미포함",
"reason": "API 필수 필드(품목코드, 검사유형 등)와 프론트엔드 폼 매핑이 복잡하여 READ-only로 제한",
"severity": "info",
"action": "폼 필드 분석 후 CRUD 시나리오 별도 작성 가능"
}
],
"rollbackPlan": {
"note": "READ-only 테스트. 데이터 변경 없음."
}
}