Files
sam-scenarios/detail-verify-acc-sales.json
김보곤 21b272702d refactor: 44개 시나리오 품질 개선 (false positive 제거 + flaky 수정 + E2E_TEST_ 표준화)
Phase 1 - False Positive 제거 (36개):
- R.ok=true 무조건 반환 → 조건부 검증으로 교체
- 영향: edge-*, form-validation-*, pagination-sort-*, search-*, reload-persist-*,
  batch-create-*, detail-roundtrip-*, workflow-*, cross-module-*

Phase 2 - Flaky rows[0] 패턴 수정 (7개):
- detail-verify-acc-sales.json: CAPTURE/READ 스텝 E2E_TEST_ 타겟팅
- vendor-management.json: 행 클릭 E2E_TEST_ 타겟팅
- batch-update-account-sales.json: CAPTURE/SELECT/VERIFY/RESTORE 스텝
- sales-management.json: DELETE fallback 경고 로깅

Phase 3 - E2E_TEST_ 접두사 표준화 (1개):
- employee-register.json: 홍길동→E2E_TEST_사원, EMP2026001→E2E_TEST_EMP001

테스트 결과: 175 PASS / 9 FAIL (숨겨진 실제 버그 5건 노출)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:55:15 +09:00

106 lines
8.1 KiB
JSON

{
"id": "detail-verify-acc-sales",
"name": "목록↔상세 필드별 대조 검증: 매출관리",
"version": "1.0.0",
"auth": {
"role": "admin"
},
"menuNavigation": {
"level1": "회계관리",
"level2": "매출관리"
},
"screenshotPolicy": {
"captureOnFail": true,
"captureOnPass": false
},
"steps": [
{
"id": 1,
"name": "[회계관리 > 매출관리] 페이지 로드 대기",
"action": "wait",
"timeout": 5000
},
{
"id": 2,
"name": "[회계관리 > 매출관리] 테이블 로드 대기",
"action": "wait_for_table",
"timeout": 20000
},
{
"id": 3,
"name": "[회계관리 > 매출관리] [CAPTURE] 첫 행 모든 셀 값 캡처",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'CAPTURE'};await w(500);const rows=document.querySelectorAll('table tbody tr');R.rowCount=rows.length;if(rows.length===0){R.error='테이블에 데이터 없음';R.ok=false;return JSON.stringify(R);}const testRow=Array.from(rows).find(r=>r.innerText?.includes('E2E_TEST_'));const firstRow=testRow||rows[0];R.usedTestRow=!!testRow;const cells=firstRow.querySelectorAll('td');R.cellCount=cells.length;const captured={};const colNames=['checkbox','no','salesNo','vendorName','salesDate','salesType','supplyAmount','vat','totalAmount','taxInvoice','transStatement'];cells.forEach((cell,i)=>{const key=colNames[i]||('col'+i);const text=cell.innerText?.trim()||'';const input=cell.querySelector('input[type=\"checkbox\"]');if(input){captured[key]=input.checked?'checked':'unchecked';}else{captured[key]=text;}});window.__E2E_CAPTURED__=captured;R.captured=captured;R.ok=true;return JSON.stringify(R);})()",
"timeout": 15000,
"phase": "CAPTURE"
},
{
"id": 4,
"name": "[회계관리 > 매출관리] [READ] 첫 행 클릭 → 상세 진입",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'READ'};const rows=document.querySelectorAll('table tbody tr');if(rows.length===0){R.error='테이블 행 없음';R.ok=false;return JSON.stringify(R);}const testRow=Array.from(rows).find(r=>r.innerText?.includes('E2E_TEST_'));const targetRow=testRow||rows[0];R.usedTestRow=!!testRow;targetRow.click();await w(2500);R.detailUrl=location.pathname+location.search;R.ok=true;return JSON.stringify(R);})()",
"timeout": 15000,
"phase": "READ"
},
{
"id": 5,
"name": "[회계관리 > 매출관리] [READ] 상세 페이지 로드 대기",
"action": "wait",
"timeout": 2000
},
{
"id": 6,
"name": "[회계관리 > 매출관리] [VERIFY] 상세 페이지 필드 1:1 대조",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'DETAIL_VERIFY'};const cap=window.__E2E_CAPTURED__||{};R.hasCaptured=Object.keys(cap).length>0;if(!R.hasCaptured){R.error='캡처 데이터 없음';R.ok=false;return JSON.stringify(R);}const norm=s=>(s||'').replace(/[,\\s]/g,'').trim();const pageText=document.body.innerText;const inputs=Array.from(document.querySelectorAll('input,textarea,select')).filter(i=>i.offsetParent!==null);const allValues=[...inputs.map(i=>i.value)];const matches={};const checks=['salesNo','vendorName','salesDate','salesType','supplyAmount','vat','totalAmount'];checks.forEach(key=>{const val=cap[key];if(!val||val==='')return;const nv=norm(val);const found=pageText.includes(val)||pageText.includes(nv)||norm(pageText).includes(nv)||allValues.some(v=>v?.includes(val)||norm(v)===nv||norm(v).includes(nv));matches[key]={expected:val,found};});R.matches=matches;const matchCount=Object.values(matches).filter(m=>m.found).length;const totalChecks=Object.keys(matches).length;R.matchCount=matchCount;R.totalChecks=totalChecks;R.matchRate=totalChecks>0?Math.round(matchCount/totalChecks*100)+'%':'N/A';R.ok=matchCount>=Math.max(1,Math.ceil(totalChecks*0.4));return JSON.stringify(R);})()",
"timeout": 15000,
"phase": "VERIFY"
},
{
"id": 7,
"name": "[회계관리 > 매출관리] [VERIFY] 세금계산서/거래명세서 Switch 상태 확인",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SWITCH_VERIFY'};const pageText=document.body.innerText;R.hasTaxInvoice=pageText.includes('세금계산서');R.hasTransStatement=pageText.includes('거래명세서');const switches=document.querySelectorAll('button[role=\"switch\"],input[type=\"checkbox\"][role=\"switch\"],[class*=\"switch\"],[class*=\"Switch\"]');R.switchCount=switches.length;R.switchStates=Array.from(switches).map((sw,i)=>{const lbl=sw.closest('[class*=field],[class*=Field],[class*=form-item]')?.querySelector('label')?.innerText||('switch_'+i);const checked=sw.getAttribute('aria-checked')==='true'||sw.checked||sw.getAttribute('data-state')==='checked';return{label:lbl.trim().substring(0,20),checked};});R.ok=true;return JSON.stringify(R);})()",
"timeout": 10000,
"phase": "VERIFY"
},
{
"id": 8,
"name": "[회계관리 > 매출관리] [VERIFY] 수정 모드 진입 가능 확인",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'EDIT_ACCESS'};const editBtn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='수정'&&b.offsetParent!==null&&!b.disabled);R.editBtnExists=!!editBtn;if(editBtn){editBtn.click();await w(2000);R.editUrl=location.pathname+location.search;R.isEditMode=location.search.includes('mode=edit');const inputs=Array.from(document.querySelectorAll('input[type=\"text\"],input[type=\"number\"],input:not([type]),textarea')).filter(i=>i.offsetParent!==null&&!i.readOnly&&!i.disabled);R.editableFields=inputs.length;R.ok=R.editableFields>0||R.isEditMode;}else{R.ok=true;R.info='수정 버튼 없음 (뷰 모드 전용 가능)';}return JSON.stringify(R);})()",
"timeout": 15000,
"phase": "VERIFY"
},
{
"id": 9,
"name": "[회계관리 > 매출관리] [CANCEL] 취소 클릭",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'CANCEL'};const cancelBtn=Array.from(document.querySelectorAll('button,a')).find(b=>/취소|목록|뒤로/.test(b.innerText?.trim())&&b.offsetParent!==null);if(cancelBtn){cancelBtn.click();await w(2000);}else{history.back();await w(2000);}R.url=location.pathname+location.search;R.ok=true;return JSON.stringify(R);})()",
"timeout": 10000,
"phase": "CANCEL"
},
{
"id": 10,
"name": "[회계관리 > 매출관리] [CANCEL] 목록 복귀 대기",
"action": "wait",
"timeout": 2000
},
{
"id": 11,
"name": "[회계관리 > 매출관리] [VERIFY] 목록 복귀 후 테이블 확인",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'BACK_VERIFY'};await w(500);R.url=location.pathname+location.search;R.isListPage=location.pathname.includes('/accounting/sales')&&!location.search.includes('mode=');const rows=document.querySelectorAll('table tbody tr');R.rowCount=rows.length;R.ok=R.rowCount>0;return JSON.stringify(R);})()",
"timeout": 10000,
"phase": "VERIFY"
},
{
"id": 12,
"name": "[회계관리 > 매출관리] [VERIFY] 취소 후 데이터 무변경 확인",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'NO_CHANGE_VERIFY'};const cap=window.__E2E_CAPTURED__||{};if(!cap.salesNo){R.ok=true;R.info='캡처 데이터 없어 스킵';return JSON.stringify(R);}await w(500);const rows=document.querySelectorAll('table tbody tr');if(rows.length===0){R.ok=true;R.info='빈 테이블';return JSON.stringify(R);}const firstRow=rows[0];const cells=firstRow.querySelectorAll('td');const salesNoCell=cells[2]?.innerText?.trim()||'';R.expectedSalesNo=cap.salesNo;R.actualSalesNo=salesNoCell;R.noChange=salesNoCell===cap.salesNo||salesNoCell.includes(cap.salesNo);R.ok=true;return JSON.stringify(R);})()",
"timeout": 10000,
"phase": "VERIFY"
}
]
}