{ "id": "full-crud-acc-bills", "name": "Full CRUD 테스트: 어음관리 (Enhanced)", "version": "2.0.0", "auth": { "role": "admin" }, "menuNavigation": { "level1": "회계관리", "level2": "어음관리" }, "screenshotPolicy": { "captureOnFail": true, "captureOnPass": false }, "steps": [ { "id": 1, "name": "[회계관리 > 어음관리] 페이지 로드 대기", "action": "wait", "timeout": 3000 }, { "id": 2, "name": "[회계관리 > 어음관리] ts 초기화 + sessionStorage 연동", "action": "evaluate", "script": "(()=>{try{sessionStorage.removeItem('__E2E_TS__');}catch(e){}delete window.__E2E_TS__;return JSON.stringify({ok:true,cleared:true});})()", "timeout": 3000 }, { "id": 3, "name": "[회계관리 > 어음관리] 테이블 로드 대기", "action": "wait_for_table", "timeout": 5000 }, { "id": 4, "name": "[회계관리 > 어음관리] [CREATE] 어음 등록 버튼 클릭", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const ts=window.__E2E_TS__||sessionStorage.getItem('__E2E_TS__')||(()=>{const n=new Date();const p=v=>v.toString().padStart(2,'0');return n.getFullYear()+p(n.getMonth()+1)+p(n.getDate())+'_'+p(n.getHours())+p(n.getMinutes())+p(n.getSeconds());})();window.__E2E_TS__=ts;try{sessionStorage.setItem('__E2E_TS__',ts);}catch(e){}const R={phase:'CREATE_OPEN',ts};const testId='EB'+ts.replace(/_/g,'').substring(4,10);window.__E2E_TEST_ID__=testId;R.testId=testId;const btn=Array.from(document.querySelectorAll('button')).find(b=>/어음.*등록|등록/.test(b.innerText?.trim()));if(!btn){R.error='등록 버튼 없음';return JSON.stringify(R);}btn.click();await w(2500);R.url=location.pathname+location.search;R.ok=true;return JSON.stringify(R);})()", "timeout": 15000, "phase": "CREATE" }, { "id": 5, "name": "[회계관리 > 어음관리] [CREATE] 폼 로드 대기", "action": "wait", "timeout": 1500 }, { "id": 6, "name": "[회계관리 > 어음관리] [CREATE] 어음번호 + 거래처(combobox) + 금액 + 비고 입력", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const p=el.tagName==='TEXTAREA'?HTMLTextAreaElement.prototype:HTMLInputElement.prototype;const ns=Object.getOwnPropertyDescriptor(p,'value')?.set;if(ns)ns.call(el,v);else el.value=v;el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));};const ts=window.__E2E_TS__||sessionStorage.getItem('__E2E_TS__')||'';const testId=window.__E2E_TEST_ID__||'EB'+ts.replace(/_/g,'').substring(4,10);const R={phase:'CREATE_FILL',ts,testId};const formArea=document.querySelector('main')||document.querySelector('[class*=\"content\"]')||document.body;const numInput=document.querySelector('input#billNumber,input[name*=\"billNumber\"],input[placeholder*=\"어음번호\"]')||Array.from(formArea.querySelectorAll('input[type=\"text\"]')).find(i=>i.offsetParent!==null&&!i.readOnly&&!i.disabled);if(numInput){sv(numInput,'E2E_TEST_'+testId);R.numFilled=true;await w(200);}const combos=Array.from(formArea.querySelectorAll('button[role=\"combobox\"]')).filter(b=>b.offsetParent!==null&&!b.closest('nav,[class*=sidebar],[class*=Sidebar]'));R.comboCount=combos.length;for(let i=0;i 어음관리] [CREATE] 발행일 + 만기일 날짜 선택 (date picker)", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'CREATE_DATES'};const formArea=document.querySelector('main')||document.querySelector('[class*=\"content\"]')||document.body;const dateButtons=Array.from(formArea.querySelectorAll('button')).filter(b=>b.innerText?.trim()==='날짜 선택'&&b.offsetParent!==null);R.dateButtonCount=dateButtons.length;for(let di=0;dib.getAttribute('aria-selected')==='true'||b.classList.contains('bg-primary')||b.tabIndex===0)||document.querySelector('button[name=\"day\"]')||document.querySelector('td[role=\"gridcell\"] button');if(today){today.click();R['date'+di+'Selected']=true;await w(300);}else{document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));R['date'+di+'Selected']=false;await w(200);}await w(200);}R.ok=true;return JSON.stringify(R);})()", "timeout": 20000, "phase": "CREATE" }, { "id": 8, "name": "[회계관리 > 어음관리] [CREATE] 금액 콤마 포맷 검증 (10,000)", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(300);const R={phase:'VERIFY_AMOUNT_FORMAT'};const pageText=document.body.innerText;const inputs=Array.from(document.querySelectorAll('input')).filter(i=>i.offsetParent!==null);const allVals=[pageText,...inputs.map(i=>i.value||'')].join(' ');R.has10000=allVals.includes('10,000')||allVals.includes('10000');R.formattedWithComma=allVals.includes('10,000');R.ok=R.has10000;R.info=R.formattedWithComma?'금액 10,000 콤마 포맷 확인':'금액 10000 (콤마 없음 - 포맷 미적용 가능)';return JSON.stringify(R);})()", "timeout": 10000, "phase": "VERIFY" }, { "id": 9, "name": "[회계관리 > 어음관리] [CREATE] 등록 제출", "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:'CREATE_SUBMIT'};const sub=Array.from(document.querySelectorAll('button')).find(b=>/^등록$|^저장$/.test(b.innerText?.trim())&&b.offsetParent!==null);if(!sub){R.error='등록 버튼 없음';return JSON.stringify(R);}sub.click();await w(3000);R.toast=toastInfo();R.toastOk=R.toast.text&&(/등록|완료|저장|성공/.test(R.toast.text));R.ok=true;return JSON.stringify(R);})()", "timeout": 20000, "phase": "CREATE" }, { "id": 10, "name": "[회계관리 > 어음관리] [CREATE] API POST 검증", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(500);const R={phase:'API_POST_CHECK'};const logs=(window.__E2E__?window.__E2E__.getApiLogs().logs:[]);const postLogs=logs.filter(l=>l.method==='POST'&&l.status>=200&&l.status<300);R.postCount=postLogs.length;R.found=postLogs.length>0;if(postLogs.length>0){const last=postLogs[postLogs.length-1];R.lastPost={url:last.url?.substring(0,80),status:last.status,duration:last.duration};}R.ok=true;R.info=R.found?'POST API '+R.lastPost.status+' ('+R.lastPost.duration+'ms)':'POST API 미감지 (모니터 타이밍 이슈 가능)';return JSON.stringify(R);})()", "timeout": 10000, "phase": "VERIFY" }, { "id": 11, "name": "[회계관리 > 어음관리] [CREATE] 생성 후 대기", "action": "wait", "timeout": 2000 }, { "id": 12, "name": "[회계관리 > 어음관리] [CREATE] 목록 복귀", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const onForm=location.search.includes('mode=new')||location.search.includes('mode=edit')||location.search.includes('mode=view')||new RegExp('/(new|[0-9]+|[0-9a-f]{8,})$').test(location.pathname);if(onForm){const btn=Array.from(document.querySelectorAll('button,a')).find(b=>/목록|취소|뒤로/.test(b.innerText?.trim()));if(btn){btn.click();await w(2000);}else{history.back();await w(2000);}}return JSON.stringify({url:location.pathname+location.search});})()", "timeout": 10000, "phase": "CREATE" }, { "id": 13, "name": "[회계관리 > 어음관리] [CREATE] 목록 안정화 대기", "action": "wait", "timeout": 2000 }, { "id": 14, "name": "[회계관리 > 어음관리] [VERIFY-CREATE] 목록에서 생성 데이터 + 첫행 셀값 캡처", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const ts=window.__E2E_TS__||sessionStorage.getItem('__E2E_TS__')||'';const R={phase:'VERIFY_CREATE'};await w(500);const rows=document.querySelectorAll('table tbody tr');R.rowCount=rows.length;const found=Array.from(rows).find(r=>r.innerText?.includes('E2E_TEST_'));R.found=!!found;if(found){R.foundText=found.innerText?.substring(0,100);const cells=found.querySelectorAll('td');window.__E2E_LIST_ROW__=Array.from(cells).map(c=>c.innerText?.trim());R.capturedCells=window.__E2E_LIST_ROW__.length;R.cellValues=window.__E2E_LIST_ROW__.slice(0,6);}R.ok=R.found;return JSON.stringify(R);})()", "timeout": 15000, "phase": "VERIFY" }, { "id": 15, "name": "[회계관리 > 어음관리] [READ] 상세 페이지 진입 (E2E_TEST_ 행 클릭)", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const ts=window.__E2E_TS__||sessionStorage.getItem('__E2E_TS__')||'';const R={phase:'READ'};let row;for(let i=0;i<3;i++){const rows=Array.from(document.querySelectorAll('table tbody tr'));row=rows.find(r=>r.innerText?.includes('E2E_TEST_'));if(row)break;await w(1000);}if(!row){R.error='E2E_TEST_ 행 없음';R.ok=false;return JSON.stringify(R);}row.click();await w(2500);R.detailUrl=location.pathname+location.search;R.ok=true;return JSON.stringify(R);})()", "timeout": 15000, "phase": "READ" }, { "id": 16, "name": "[회계관리 > 어음관리] [READ] 상세 페이지 로드 대기", "action": "wait", "timeout": 2000 }, { "id": 17, "name": "[회계관리 > 어음관리] [READ] 상세 필드별 1:1 대조 (목록↔상세 roundtrip)", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'DETAIL_VERIFY'};const listRow=window.__E2E_LIST_ROW__||[];R.hasCaptured=listRow.length>0;if(!R.hasCaptured){R.info='캡처 데이터 없음 - 기본 검증';const hasE2E=document.body.innerText.includes('E2E_TEST_');R.hasTestData=hasE2E;R.ok=hasE2E;return JSON.stringify(R);}const norm=s=>(s||'').replace(/[,\\s]/g,'').trim().toLowerCase();const pageText=document.body.innerText;const inputs=Array.from(document.querySelectorAll('input,textarea')).filter(i=>i.offsetParent!==null);const allValues=[pageText,...inputs.map(i=>i.value||'')].join(' ');const normAll=norm(allValues);const matches={};listRow.forEach((val,i)=>{if(!val||val.length<2)return;const nv=norm(val);const found=allValues.includes(val)||normAll.includes(nv);matches['col'+i]={expected:val.substring(0,30),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": "READ" }, { "id": 18, "name": "[회계관리 > 어음관리] [READ] 금액 10,000 표시 확인", "action": "evaluate", "script": "(async()=>{const R={phase:'READ_AMOUNT'};const pageText=document.body.innerText;const inputs=Array.from(document.querySelectorAll('input')).filter(i=>i.offsetParent!==null);const allVals=[pageText,...inputs.map(i=>i.value||'')].join(' ');R.has10000=allVals.includes('10,000')||allVals.includes('10000');R.ok=true;R.info=R.has10000?'금액 10,000 상세에서 확인':'금액 10,000 미감지 (포맷 차이 가능)';return JSON.stringify(R);})()", "timeout": 10000, "phase": "VERIFY" }, { "id": 19, "name": "[회계관리 > 어음관리] [UPDATE] 수정 버튼 클릭 + 금액 20,000 변경 + 비고 변경", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const p=el.tagName==='TEXTAREA'?HTMLTextAreaElement.prototype:HTMLInputElement.prototype;const ns=Object.getOwnPropertyDescriptor(p,'value')?.set;if(ns)ns.call(el,v);else el.value=v;el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));};const ts=window.__E2E_TS__||sessionStorage.getItem('__E2E_TS__')||'';const R={phase:'UPDATE'};const editBtn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='수정');if(!editBtn){R.error='수정 버튼 없음';R.ok=false;return JSON.stringify(R);}editBtn.click();await w(2000);R.editUrl=location.pathname+location.search;const amtInput=document.querySelector('input[placeholder*=\"금액\"]');if(amtInput){sv(amtInput,'20000');R.amtChanged=true;await w(200);}else{const numInputs=Array.from(document.querySelectorAll('input[type=\"text\"],input[type=\"number\"]')).filter(i=>i.offsetParent!==null&&!i.readOnly&&!i.disabled&&(i.value==='10000'||i.value==='10,000'));if(numInputs.length>0){sv(numInputs[0],'20000');R.amtChanged=true;R.amtChangedVia='value match';await w(200);}}const noteInput=document.querySelector('input[placeholder*=\"비고\"]')||document.querySelector('textarea[placeholder*=\"비고\"]');if(noteInput){sv(noteInput,'E2E_수정됨_'+ts);R.noteChanged=true;await w(200);}else{const inputs=Array.from(document.querySelectorAll('input[type=\"text\"],textarea')).filter(i=>i.offsetParent!==null&&!i.readOnly&&!i.disabled);const lastInput=inputs[inputs.length-1];if(lastInput){sv(lastInput,'E2E_수정됨_'+ts);R.noteChanged=true;R.noteChangedVia='last input';await w(200);}}R.ok=true;return JSON.stringify(R);})()", "timeout": 20000, "phase": "UPDATE" }, { "id": 20, "name": "[회계관리 > 어음관리] [UPDATE] 저장 클릭", "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:'UPDATE_SAVE'};const editBtn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='수정');const saveBtn=Array.from(document.querySelectorAll('button')).find(b=>/저장|수정|확인/.test(b.innerText?.trim())&&b!==editBtn&&b.offsetParent!==null);if(saveBtn){saveBtn.click();await w(3000);}R.toast=toastInfo();R.toastOk=R.toast.text&&(/수정|완료|저장|성공/.test(R.toast.text));R.ok=true;return JSON.stringify(R);})()", "timeout": 20000, "phase": "UPDATE" }, { "id": 21, "name": "[회계관리 > 어음관리] [UPDATE] API PUT 검증", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(500);const R={phase:'API_PUT_CHECK'};const logs=(window.__E2E__?window.__E2E__.getApiLogs().logs:[]);const putLogs=logs.filter(l=>(l.method==='PUT'||l.method==='PATCH')&&l.status>=200&&l.status<300);R.putCount=putLogs.length;R.found=putLogs.length>0;if(putLogs.length>0){const last=putLogs[putLogs.length-1];R.lastPut={url:last.url?.substring(0,80),status:last.status,duration:last.duration};}R.ok=true;R.info=R.found?'PUT/PATCH API '+R.lastPut.status+' ('+R.lastPut.duration+'ms)':'PUT/PATCH API 미감지';return JSON.stringify(R);})()", "timeout": 10000, "phase": "VERIFY" }, { "id": 22, "name": "[회계관리 > 어음관리] [UPDATE] 수정 후 대기", "action": "wait", "timeout": 2000 }, { "id": 23, "name": "[회계관리 > 어음관리] [UPDATE] 수정 내용 검증 (금액 20,000 + 비고 수정됨)", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VERIFY_UPDATE'};const pageText=document.body.innerText;const inputs=Array.from(document.querySelectorAll('input,textarea')).filter(i=>i.offsetParent!==null);const allVals=[pageText,...inputs.map(i=>i.value||'')].join(' ');R.hasModifiedNote=allVals.includes('수정됨');R.has20000=allVals.includes('20,000')||allVals.includes('20000');R.formattedWithComma=allVals.includes('20,000');R.ok=R.hasModifiedNote||R.has20000;R.info=[R.has20000?'금액 20,000 확인':'금액 20,000 미감지',R.hasModifiedNote?'비고 수정됨 확인':'비고 수정됨 미감지'].join(' | ');return JSON.stringify(R);})()", "timeout": 15000, "phase": "VERIFY" }, { "id": 24, "name": "[회계관리 > 어음관리] [UPDATE] 목록 복귀", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const onForm=location.search.includes('mode=new')||location.search.includes('mode=edit')||location.search.includes('mode=view')||new RegExp('/(new|[0-9]+|[0-9a-f]{8,})$').test(location.pathname);if(onForm){const btn=Array.from(document.querySelectorAll('button,a')).find(b=>/목록|취소|뒤로/.test(b.innerText?.trim()));if(btn){btn.click();await w(2000);}else{history.back();await w(2000);}}return JSON.stringify({url:location.pathname+location.search});})()", "timeout": 10000, "phase": "UPDATE" }, { "id": 25, "name": "[회계관리 > 어음관리] [UPDATE] 목록 안정화 대기", "action": "wait", "timeout": 2000 }, { "id": 26, "name": "[회계관리 > 어음관리] [DELETE] 데이터 삭제 (행 클릭 → 삭제 → 확인)", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const ts=window.__E2E_TS__||sessionStorage.getItem('__E2E_TS__')||'';const R={phase:'DELETE'};const onDetail=location.search.includes('mode=view')||location.search.includes('mode=edit')||new RegExp('/[0-9]+$|/[0-9a-f]{8,}$').test(location.pathname);if(!onDetail){const rows=Array.from(document.querySelectorAll('table tbody tr'));const row=rows.find(r=>r.innerText?.includes(ts))||rows.find(r=>r.innerText?.includes('E2E_TEST_'));if(!row){R.error='E2E_TEST_ 행 없음';R.ok=false;return JSON.stringify(R);}row.click();await w(2500);}R.detailUrl=location.pathname+location.search;const delBtn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='삭제'&&b.offsetParent!==null);if(!delBtn){R.error='삭제 버튼 없음';R.ok=false;return JSON.stringify(R);}delBtn.click();await w(1500);const dialog=document.querySelector('[role=\"alertdialog\"]');R.dialogFound=!!dialog;if(dialog){R.dialogText=dialog.innerText?.substring(0,100);}let cfm=document.querySelector('[role=\"alertdialog\"] [data-slot=\"alert-dialog-footer\"] button:last-child');if(!cfm){cfm=Array.from(document.querySelectorAll('[role=\"alertdialog\"] button')).find(b=>/삭제/.test(b.innerText?.trim())&&b!==delBtn);}if(!cfm){cfm=Array.from(document.querySelectorAll('button')).find(b=>/확인|삭제|예/.test(b.innerText?.trim())&&b!==delBtn&&b.offsetParent!==null);}if(cfm){cfm.click();await w(4000);}R.ok=true;return JSON.stringify(R);})()", "timeout": 30000, "phase": "DELETE", "critical": true }, { "id": 27, "name": "[회계관리 > 어음관리] [DELETE] API DELETE 검증", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(500);const R={phase:'API_DELETE_CHECK'};const logs=(window.__E2E__?window.__E2E__.getApiLogs().logs:[]);const delLogs=logs.filter(l=>l.method==='DELETE'&&l.status>=200&&l.status<300);R.delCount=delLogs.length;R.found=delLogs.length>0;if(delLogs.length>0){const last=delLogs[delLogs.length-1];R.lastDelete={url:last.url?.substring(0,80),status:last.status,duration:last.duration};}R.ok=true;R.info=R.found?'DELETE API '+R.lastDelete.status+' ('+R.lastDelete.duration+'ms)':'DELETE API 미감지';return JSON.stringify(R);})()", "timeout": 10000, "phase": "VERIFY" }, { "id": 28, "name": "[회계관리 > 어음관리] [DELETE] 삭제 후 대기", "action": "wait", "timeout": 3000 }, { "id": 29, "name": "[회계관리 > 어음관리] [DELETE] 목록 복귀", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const onForm=location.search.includes('mode=new')||location.search.includes('mode=edit')||location.search.includes('mode=view')||new RegExp('/(new|[0-9]+|[0-9a-f]{8,})$').test(location.pathname);if(onForm){const btn=Array.from(document.querySelectorAll('button,a')).find(b=>/목록|취소|뒤로/.test(b.innerText?.trim()));if(btn){btn.click();await w(2000);}else{history.back();await w(2000);}}return JSON.stringify({url:location.pathname+location.search});})()", "timeout": 10000, "phase": "DELETE" }, { "id": 30, "name": "[회계관리 > 어음관리] [DELETE] 목록 안정화 대기", "action": "wait", "timeout": 2000 }, { "id": 31, "name": "[회계관리 > 어음관리] [VERIFY-DELETE] 삭제 확인 (목록에서 E2E_TEST_ 부재 확인)", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const ts=window.__E2E_TS__||sessionStorage.getItem('__E2E_TS__')||'';const R={phase:'VERIFY_DELETE'};await w(1000);if(location.search.includes('mode=view')){const btn=Array.from(document.querySelectorAll('button,a')).find(b=>/목록/.test(b.innerText?.trim()));if(btn){btn.click();await w(2000);}else{history.back();await w(2000);}}const rows=document.querySelectorAll('table tbody tr');R.rowCount=rows.length;const found=Array.from(rows).find(r=>r.innerText?.includes(ts));R.stillExists=!!found;R.ok=!found;R.ts=ts;if(found)R.warn='E2E_TEST_ 데이터가 여전히 존재 - 삭제 실패 가능';return JSON.stringify(R);})()", "timeout": 15000, "phase": "VERIFY" }, { "id": 32, "name": "[회계관리 > 어음관리] [SUMMARY] API 호출 통계", "action": "evaluate", "script": "(async()=>{const R={phase:'API_SUMMARY'};const logs=(window.__E2E__?window.__E2E__.getApiLogs().logs:[]);R.totalCalls=logs.length;R.successCalls=logs.filter(l=>l.status>=200&&l.status<300).length;R.failedCalls=logs.filter(l=>l.status>=400).length;R.avgResponseTime=logs.length>0?Math.round(logs.reduce((s,l)=>s+(l.duration||0),0)/logs.length):0;R.slowCalls=logs.filter(l=>(l.duration||0)>2000).length;R.methods={GET:logs.filter(l=>l.method==='GET').length,POST:logs.filter(l=>l.method==='POST').length,PUT:logs.filter(l=>l.method==='PUT').length,PATCH:logs.filter(l=>l.method==='PATCH').length,DELETE:logs.filter(l=>l.method==='DELETE').length};R.ok=true;R.info='API: '+R.totalCalls+'건 (성공 '+R.successCalls+', 실패 '+R.failedCalls+', 평균 '+R.avgResponseTime+'ms, 느린호출 '+R.slowCalls+'건)';return JSON.stringify(R);})()", "timeout": 10000, "phase": "VERIFY" } ] }