{ "id": "edge-rapid-click-acc-sales", "name": "엣지 케이스: UI 내구성 연타 테스트 (회계 > 매출관리)", "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": "[회계관리 > 매출관리] [RAPID] 헤더 체크박스 10회 연타 → 최종 상태 일관성", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'RAPID_HEADER_CHECKBOX'};const headerCb=document.querySelector('table thead input[type=\"checkbox\"],table thead button[role=\"checkbox\"]');if(!headerCb){R.err='헤더 체크박스 없음';R.ok=true;return JSON.stringify(R);}let clickCount=0;for(let i=0;i<10;i++){headerCb.click();clickCount++;await w(50);}R.clickCount=clickCount;await w(1000);const isChecked=headerCb.checked||headerCb.getAttribute('aria-checked')==='true'||headerCb.getAttribute('data-state')==='checked';R.finalState=isChecked?'checked':'unchecked';R.expectedState='unchecked';R.consistent=R.finalState===R.expectedState;const bodyCheckboxes=document.querySelectorAll('table tbody input[type=\"checkbox\"],table tbody button[role=\"checkbox\"]');const checkedCount=Array.from(bodyCheckboxes).filter(cb=>cb.checked||cb.getAttribute('aria-checked')==='true'||cb.getAttribute('data-state')==='checked').length;R.bodyTotal=bodyCheckboxes.length;R.bodyChecked=checkedCount;R.allConsistent=(isChecked&&checkedCount===bodyCheckboxes.length)||(!isChecked&&checkedCount===0);R.ok=true;R.info=R.allConsistent?'✅ 10회 연타 후 체크박스 일관성 유지 ('+R.finalState+', body: '+checkedCount+'/'+bodyCheckboxes.length+')':'⚠️ 체크박스 상태 불일치 (header='+R.finalState+', body='+checkedCount+'/'+bodyCheckboxes.length+')';return JSON.stringify(R);})()", "timeout": 15000, "phase": "RAPID_CLICK" }, { "id": 4, "name": "[회계관리 > 매출관리] [RAPID] 체크박스 연타 후 안정화 대기", "action": "wait", "timeout": 1000 }, { "id": 5, "name": "[회계관리 > 매출관리] [RAPID] 등록 폼 열기", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'OPEN_FORM'};const priorities=['매출 등록','매출등록','등록','추가','신규'];let btn=null;for(const kw of priorities){btn=Array.from(document.querySelectorAll('button')).find(b=>{const t=b.innerText?.trim()||'';return t.includes(kw)&&b.offsetParent!==null&&!b.disabled;});if(btn)break;}if(!btn){R.err='등록 버튼 없음';R.ok=true;return JSON.stringify(R);}R.btnText=btn.innerText?.trim();btn.click();await w(2500);R.url=location.pathname+location.search;R.ok=true;return JSON.stringify(R);})()", "timeout": 15000, "phase": "OPEN_FORM" }, { "id": 6, "name": "[회계관리 > 매출관리] [RAPID] 폼 렌더링 대기", "action": "wait", "timeout": 2000 }, { "id": 7, "name": "[회계관리 > 매출관리] [RAPID] 등록 버튼 5회 연타 → 중복 제출 방지 확인", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'RAPID_SUBMIT'};const btn=Array.from(document.querySelectorAll('button')).find(b=>{const t=b.innerText?.trim()||'';return(/등록|저장|확인|제출/.test(t))&&b.offsetParent!==null&&!b.disabled;});if(!btn){R.err='저장/등록 버튼 없음';R.ok=true;return JSON.stringify(R);}R.btnText=btn.innerText?.trim();const beforeApiCount=((window.__E2E__?window.__E2E__.getApiLogs().logs:[])).length;let clickCount=0;for(let i=0;i<5;i++){btn.click();clickCount++;await w(50);}R.clickCount=clickCount;await w(3000);const afterApiCount=((window.__E2E__?window.__E2E__.getApiLogs().logs:[])).length;R.apiCallsDuringRapid=afterApiCount-beforeApiCount;const postCalls=((window.__E2E__?window.__E2E__.getApiLogs().logs:[])).slice(beforeApiCount).filter(l=>l.method==='POST');R.postCallCount=postCalls.length;R.duplicateProtection=R.postCallCount<=1;const toasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"]');R.toastCount=toasts.length;if(toasts.length>0){R.toastTexts=Array.from(toasts).map(t=>t.innerText?.trim().substring(0,80)).filter(Boolean);}const dialogs=document.querySelectorAll('[role=\"dialog\"],[role=\"alertdialog\"],[aria-modal=\"true\"]');const visibleDialogs=Array.from(dialogs).filter(d=>d.offsetParent!==null);R.dialogCount=visibleDialogs.length;const hasError=document.querySelector('[class*=\"error\"],[class*=\"Error\"],[role=\"alert\"]');R.hasError=!!hasError;R.ok=true;R.info=R.duplicateProtection?'✅ 5회 연타 시 중복 제출 방지 (POST '+R.postCallCount+'회)':'⚠️ 5회 연타 시 중복 POST 발생 ('+R.postCallCount+'회)';return JSON.stringify(R);})()", "timeout": 20000, "phase": "RAPID_CLICK" }, { "id": 8, "name": "[회계관리 > 매출관리] [RAPID] 연타 후 상태 확인 + 다이얼로그 닫기", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'RAPID_RESULT'};await w(1000);const dlg=document.querySelector('[role=\"alertdialog\"],[role=\"dialog\"]');if(dlg&&dlg.offsetParent!==null){R.dialogFound=true;const closeBtn=dlg.querySelector('button[class*=\"close\"]')||Array.from(dlg.querySelectorAll('button')).find(b=>/닫기|확인|취소|Close/.test(b.innerText?.trim()));if(closeBtn){closeBtn.click();await w(500);R.dialogClosed=true;}else{document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(500);}}R.url=location.pathname+location.search;R.pageStable=!document.querySelector('.loading,.spinner,[class*=\"skeleton\"]');R.ok=true;return JSON.stringify(R);})()", "timeout": 10000, "phase": "RAPID_CLICK" }, { "id": 9, "name": "[회계관리 > 매출관리] [RAPID] 품목 추가 버튼 10회 연타 → 적절한 행 수 확인", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'RAPID_ADD_ITEM'};const addBtn=Array.from(document.querySelectorAll('button')).find(b=>{const t=b.innerText?.trim()||'';return(/품목.*추가|행.*추가|\\+.*추가|추가/.test(t)||t==='+')&&b.offsetParent!==null&&!b.disabled;});if(!addBtn){R.err='품목 추가 버튼 없음 (폼 미진입 가능)';R.ok=true;return JSON.stringify(R);}R.addBtnText=addBtn.innerText?.trim();const beforeRows=document.querySelectorAll('table tbody tr, [class*=\"item-row\"],[class*=\"ItemRow\"],[class*=\"grid-row\"]').length;let clickCount=0;for(let i=0;i<10;i++){addBtn.click();clickCount++;await w(80);}R.clickCount=clickCount;await w(2000);const afterRows=document.querySelectorAll('table tbody tr, [class*=\"item-row\"],[class*=\"ItemRow\"],[class*=\"grid-row\"]').length;R.beforeRows=beforeRows;R.afterRows=afterRows;R.addedRows=afterRows-beforeRows;R.reasonableRowCount=R.addedRows<=10&&R.addedRows>=1;R.ok=true;R.info=R.reasonableRowCount?'✅ 10회 연타 후 품목 행 '+R.addedRows+'개 추가 (합리적)':'⚠️ 10회 연타 후 품목 행 '+R.addedRows+'개 추가 (비정상)';return JSON.stringify(R);})()", "timeout": 20000, "phase": "RAPID_CLICK" }, { "id": 10, "name": "[회계관리 > 매출관리] [CLOSE] 폼/모달 닫기 → 목록 복귀", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'CLOSE_FORM'};const dlg=document.querySelector('[role=\"alertdialog\"],[role=\"dialog\"]');if(dlg&&dlg.offsetParent!==null){const closeBtn=dlg.querySelector('button[class*=\"close\"]')||Array.from(dlg.querySelectorAll('button')).find(b=>/닫기|확인|취소|Close/.test(b.innerText?.trim()));if(closeBtn){closeBtn.click();await w(500);}else{document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(500);}}if(location.search.includes('mode=new')||location.search.includes('mode=edit')){const backBtn=Array.from(document.querySelectorAll('button,a')).find(b=>/목록|취소|뒤로/.test(b.innerText?.trim()));if(backBtn){backBtn.click();await w(2000);}else{history.back();await w(2000);}}const modal=document.querySelector('[role=\"dialog\"],[aria-modal=\"true\"],[class*=\"modal\"]:not([class*=\"tooltip\"])');if(modal&&modal.offsetParent!==null){const xBtn=modal.querySelector('button[class*=\"close\"],[aria-label=\"닫기\"],[aria-label=\"Close\"]')||Array.from(modal.querySelectorAll('button')).find(b=>/닫기|취소|Close/.test(b.innerText?.trim()));if(xBtn){xBtn.click();await w(500);}else{document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(500);}}R.url=location.pathname+location.search;R.ok=true;return JSON.stringify(R);})()", "timeout": 10000, "phase": "CLOSE_FORM" } ] }