{ "id": "batch-update-account-sales", "name": "계정과목 일괄변경 버그 회귀 테스트 (BUG-SALES-20260115-001): 매출관리", "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_BEFORE'};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 typeColIdx=[5,4,3].find(idx=>cells[idx]&&cells[idx].innerText?.trim()!=='');const typeValue=typeColIdx!==undefined?cells[typeColIdx]?.innerText?.trim():'';R.beforeType=typeValue;window.__E2E_BEFORE_TYPE__=typeValue;R.firstRowText=firstRow.innerText?.substring(0,100);R.ok=true;return JSON.stringify(R);})()", "timeout": 15000, "phase": "CAPTURE" }, { "id": 4, "name": "[회계관리 > 매출관리] [SELECT] 첫 행 체크박스 선택", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SELECT_ROW'};const rows=document.querySelectorAll('table tbody tr');if(rows.length===0){R.error='행 없음';R.ok=false;return JSON.stringify(R);}const firstRow=rows[0];const checkbox=firstRow.querySelector('input[type=\"checkbox\"],button[role=\"checkbox\"]');if(checkbox){checkbox.click();await w(500);R.checked=true;}else{firstRow.querySelector('td')?.click();await w(500);R.clickedFirstCell=true;}R.ok=true;return JSON.stringify(R);})()", "timeout": 10000, "phase": "SELECT" }, { "id": 5, "name": "[회계관리 > 매출관리] [SELECT] 계정과목 드롭다운에서 다른 값 선택", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'CHANGE_ACCOUNT'};const beforeType=window.__E2E_BEFORE_TYPE__||'';const combos=Array.from(document.querySelectorAll('button[role=\"combobox\"],select')).filter(b=>b.offsetParent!==null);const labels=Array.from(document.querySelectorAll('label')).filter(l=>/계정과목|매출유형/.test(l.innerText));R.comboCount=combos.length;R.labelCount=labels.length;let targetCombo=null;for(const cb of combos){const lbl=cb.closest('[class*=field],[class*=Field],[class*=form-item]')?.querySelector('label')?.innerText||'';if(lbl.includes('계정과목')||lbl.includes('매출유형')){targetCombo=cb;break;}}if(!targetCombo&&combos.length>0){targetCombo=combos[0];}if(!targetCombo){const selectEls=document.querySelectorAll('select');for(const sel of selectEls){const lbl=sel.closest('[class*=field],[class*=Field],[class*=form-item]')?.querySelector('label')?.innerText||'';if(lbl.includes('계정과목')){targetCombo=sel;break;}}if(!targetCombo&&selectEls.length>0)targetCombo=selectEls[0];}if(!targetCombo){R.error='계정과목 콤보박스 없음';R.ok=false;return JSON.stringify(R);}if(targetCombo.tagName==='SELECT'){const opts=targetCombo.options;for(let i=0;io.innerText?.trim()!==beforeType&&o.innerText?.trim()!==''&&o.innerText?.trim()!=='미설정');if(diff){diff.click();R.selectedValue=diff.innerText?.trim();await w(400);}else if(opts[0]){opts[0].click();R.selectedValue=opts[0].innerText?.trim();await w(400);}}else{document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(200);}R.ok=true;}window.__E2E_NEW_TYPE__=R.selectedValue||'';return JSON.stringify(R);})()", "timeout": 15000, "phase": "SELECT" }, { "id": 6, "name": "[회계관리 > 매출관리] [SAVE] 저장 버튼 클릭", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SAVE'};const saveBtn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='저장'&&b.offsetParent!==null&&!b.disabled);if(!saveBtn){R.error='저장 버튼 없음';R.ok=false;return JSON.stringify(R);}saveBtn.click();await w(1500);R.ok=true;return JSON.stringify(R);})()", "timeout": 10000, "phase": "SAVE" }, { "id": 7, "name": "[회계관리 > 매출관리] [SAVE] 확인 다이얼로그 → 확인 클릭", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const toastInfo=()=>{const t=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"],[class*=\"Toaster\"] [data-content]');return{count:t.length,text:t.length>0?Array.from(t).pop()?.innerText?.trim().substring(0,100):''};};const R={phase:'CONFIRM'};const dlg=document.querySelector('[role=\"alertdialog\"],[role=\"dialog\"]');if(dlg&&dlg.offsetParent!==null){R.dialogText=dlg.innerText?.substring(0,100);const cfmBtn=Array.from(dlg.querySelectorAll('button')).find(b=>/확인|저장|예|Yes/.test(b.innerText?.trim()));if(cfmBtn){cfmBtn.click();await w(3000);}R.dialogConfirmed=true;}else{R.dialogFound=false;await w(2000);}R.toast=toastInfo();R.ok=true;return JSON.stringify(R);})()", "timeout": 15000, "phase": "SAVE" }, { "id": 8, "name": "[회계관리 > 매출관리] [VERIFY-1] 토스트 메시지 확인", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VERIFY_TOAST'};await w(1000);const toasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"],[class*=\"Toaster\"] [data-content]');R.toastCount=toasts.length;R.toastTexts=Array.from(toasts).map(t=>t.innerText?.trim().substring(0,80)).filter(Boolean);const successToast=R.toastTexts.some(t=>/성공|완료|저장|변경/.test(t));R.successToast=successToast;R.ok=true;R.info=successToast?'✅ 성공 토스트 확인':'⚠️ 성공 토스트 미감지';return JSON.stringify(R);})()", "timeout": 10000, "phase": "VERIFY" }, { "id": 9, "name": "[회계관리 > 매출관리] [VERIFY-2] ★핵심★ 첫 행 매출유형 실제 변경 확인", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VERIFY_DATA_CHANGED'};await w(1000);const beforeType=window.__E2E_BEFORE_TYPE__||'';const newType=window.__E2E_NEW_TYPE__||'';const rows=document.querySelectorAll('table tbody tr');if(rows.length===0){R.error='테이블 행 없음';R.ok=false;return JSON.stringify(R);}const firstRow=rows[0];const cells=firstRow.querySelectorAll('td');const currentTypes=Array.from(cells).map(c=>c.innerText?.trim()).filter(Boolean);R.beforeType=beforeType;R.expectedNewType=newType;R.currentCellValues=currentTypes.slice(0,8);const changed=newType&¤tTypes.some(v=>v.includes(newType));const unchanged=beforeType&¤tTypes.some(v=>v===beforeType);R.dataActuallyChanged=changed;R.dataStillOld=unchanged&&!changed;R.ok=true;if(R.dataStillOld&&beforeType!==newType){R.bugDetected=true;R.info='🐛 BUG-SALES-20260115-001 재현: 토스트 성공 but 데이터 미변경 (beforeType='+beforeType+', expected='+newType+')';}else if(R.dataActuallyChanged){R.info='✅ 데이터 실제 변경 확인 ('+beforeType+' → '+newType+')';}else{R.info='⚠️ 변경 확인 불가 (beforeType='+beforeType+', newType='+newType+')';}return JSON.stringify(R);})()", "timeout": 15000, "phase": "VERIFY" }, { "id": 10, "name": "[회계관리 > 매출관리] [RELOAD] 새로고침", "action": "reload", "timeout": 10000 }, { "id": 11, "name": "[회계관리 > 매출관리] [RELOAD] 새로고침 후 대기", "action": "wait", "timeout": 5000 }, { "id": 12, "name": "[회계관리 > 매출관리] [RELOAD] 테이블 재로드 대기", "action": "wait_for_table", "timeout": 8000 }, { "id": 13, "name": "[회계관리 > 매출관리] [VERIFY-3] ★핵심★ 새로고침 후에도 변경값 유지 확인", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VERIFY_PERSIST'};await w(1000);const beforeType=window.__E2E_BEFORE_TYPE__||'';const newType=window.__E2E_NEW_TYPE__||'';const rows=document.querySelectorAll('table tbody tr');if(rows.length===0){R.error='테이블 행 없음 after reload';R.ok=false;return JSON.stringify(R);}const firstRow=rows[0];const cells=firstRow.querySelectorAll('td');const currentTypes=Array.from(cells).map(c=>c.innerText?.trim()).filter(Boolean);R.beforeType=beforeType;R.expectedNewType=newType;R.currentCellValues=currentTypes.slice(0,8);const persisted=newType&¤tTypes.some(v=>v.includes(newType));const reverted=beforeType&¤tTypes.some(v=>v===beforeType)&&!persisted;R.persistedAfterReload=persisted;R.revertedAfterReload=reverted;R.ok=true;if(reverted&&beforeType!==newType){R.bugDetected=true;R.info='🐛 BUG-SALES-20260115-001 재현: 새로고침 후 원래값 복귀 ('+beforeType+')';}else if(persisted){R.info='✅ 새로고침 후에도 변경값 유지 ('+newType+')';}else{R.info='⚠️ 새로고침 후 상태 확인 불가';}return JSON.stringify(R);})()", "timeout": 15000, "phase": "VERIFY" }, { "id": 14, "name": "[회계관리 > 매출관리] [RESTORE] 원래 값 복원 (선택적)", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'RESTORE'};const beforeType=window.__E2E_BEFORE_TYPE__||'';if(!beforeType){R.info='원래 값 없음 - 복원 스킵';R.ok=true;return JSON.stringify(R);}const rows=document.querySelectorAll('table tbody tr');if(rows.length===0){R.ok=true;return JSON.stringify(R);}const firstRow=rows[0];const checkbox=firstRow.querySelector('input[type=\"checkbox\"],button[role=\"checkbox\"]');if(checkbox)checkbox.click();await w(500);const combos=Array.from(document.querySelectorAll('button[role=\"combobox\"],select')).filter(b=>b.offsetParent!==null);if(combos.length>0){const cb=combos[0];if(cb.tagName==='SELECT'){for(let i=0;io.innerText?.trim()===beforeType);if(opt){opt.click();await w(400);}else{document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(200);}}}}const saveBtn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='저장'&&b.offsetParent!==null);if(saveBtn){saveBtn.click();await w(1500);const cfm=Array.from(document.querySelectorAll('[role=\"alertdialog\"] button,[role=\"dialog\"] button')).find(b=>/확인/.test(b.innerText?.trim()));if(cfm){cfm.click();await w(2000);}}R.info='원래 값 복원 시도 완료';R.ok=true;return JSON.stringify(R);})()", "timeout": 20000, "phase": "RESTORE" } ] }