{ "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 firstRow=rows[0];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);}rows[0].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" } ] }