Files
sam-scenarios/input-fields-production.json
김보곤 5128a4231d feat: 입력 필드 전수 테스트 시나리오 추가 (combobox, datepicker, text, radio, toggle)
12개 페이지에서 모든 필드 유형을 동적 발견하여 테스트하는 5개 시나리오.
- input-fields-acc-1: 어음/입금/출금 (회계관리)
- input-fields-acc-2: 거래처/악성채권 (회계관리)
- input-fields-sales: 거래처/수주/견적 (판매관리)
- input-fields-production: 작업지시/작업실적 (생산관리)
- input-fields-material-quality: 입고/제품검사 (자재/품질관리)
5/5 PASS, 79 steps, 3.1분 소요
2026-02-12 08:36:02 +09:00

103 lines
23 KiB
JSON

{
"id": "input-fields-production",
"name": "입력 필드 전수 테스트: 작업지시/작업실적 (4/5)",
"version": "1.1.0",
"auth": {
"role": "admin"
},
"menuNavigation": {
"level1": "생산관리",
"level2": "작업지시 관리"
},
"screenshotPolicy": {
"captureOnFail": true,
"captureOnPass": false
},
"steps": [
{
"id": 1,
"name": "[생산관리 > 작업지시 관리] 페이지 로드 대기",
"action": "wait",
"timeout": 3000
},
{
"id": 2,
"name": "[생산관리 > 작업지시 관리] 테이블 로드 대기",
"action": "wait_for_table",
"timeout": 5000
},
{
"id": 3,
"name": "[생산관리 > 작업지시 관리] 등록 폼 열기",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const urlBefore=location.href;const btns=Array.from(document.querySelectorAll(\"button\")).filter(b=>b.offsetParent!==null&&!b.disabled);const priorities=[ b=>/등록/.test(b.innerText?.trim()), b=>/추가/.test(b.innerText?.trim()), b=>/작성/.test(b.innerText?.trim()), b=>/^\\+/.test(b.innerText?.trim()), b=>/신규/.test(b.innerText?.trim())&&!/신규업체|신규거래/.test(b.innerText?.trim()),];let regBtn=null;for(const pred of priorities){regBtn=btns.find(pred);if(regBtn)break;}if(!regBtn)regBtn=btns.find(b=>/신규/.test(b.innerText?.trim()));if(regBtn){ regBtn.scrollIntoView({block:\"center\"});await w(300); regBtn.click();await w(2500); const modal=document.querySelector(\"[role='dialog'],[aria-modal='true']\"); const hasModal=modal&&modal.offsetParent!==null; const urlChanged=location.href!==urlBefore; const inputs=document.querySelectorAll(\"input,textarea,select,button[role='combobox']\"); const vis=Array.from(inputs).filter(el=>el.offsetParent!==null&&!el.disabled).length; return JSON.stringify({opened:true,hasModal,urlChanged,url:location.pathname+location.search,btnText:regBtn.innerText?.trim().substring(0,30),visibleInputs:vis});}const row=document.querySelector(\"table tbody tr\");if(row){ row.click();await w(2500); const modal=document.querySelector(\"[role='dialog'],[aria-modal='true']\"); const hasModal=modal&&modal.offsetParent!==null; const urlChanged=location.href!==urlBefore; const inputs=document.querySelectorAll(\"input,textarea,select,button[role='combobox']\"); const vis=Array.from(inputs).filter(el=>el.offsetParent!==null&&!el.disabled).length; return JSON.stringify({opened:true,hasModal,urlChanged,usedRowClick:true,visibleInputs:vis});}const form=document.querySelector(\"form\");if(form){return JSON.stringify({opened:true,isInlineForm:true});}return JSON.stringify({opened:false,noBtn:true,available:btns.slice(0,8).map(b=>b.innerText?.trim().substring(0,20)).filter(Boolean)});})()",
"timeout": 15000
},
{
"id": 4,
"name": "[생산관리 > 작업지시 관리] 폼 렌더링 대기",
"action": "wait",
"timeout": 1500
},
{
"id": 5,
"name": "[생산관리 > 작업지시 관리] 입력 필드 전수 테스트",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={url:location.pathname+location.search,fields:[],summary:{}};const modal=document.querySelector(\"[role='dialog'],[aria-modal='true']\");const hasModal=modal&&modal.offsetParent!==null;const mainContent=document.querySelector(\"main,[class*='content']:not(nav):not(aside),[class*='Content']:not(nav)\");const scope=hasModal?modal:(mainContent||document.body);R.scope=hasModal?\"modal\":mainContent?\"main\":\"body\";const sv=(el,v)=>{ const proto=el.tagName===\"TEXTAREA\"?HTMLTextAreaElement.prototype:HTMLInputElement.prototype; const ns=Object.getOwnPropertyDescriptor(proto,\"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}));};let tested=0,errors=0,skipped=0;const textSel=\"input[type=text],input[type=number],input[type=email],input[type=tel],input[type=url],input:not([type]):not([role])\";const textInputs=scope.querySelectorAll(textSel);for(const el of textInputs){ if(el.offsetParent===null||el.readOnly||el.disabled)continue; if(el.placeholder?.includes(\"검색\")||el.type===\"search\")continue; const label=(el.closest(\"[class*=field],[class*=Field],[class*=form-item],[class*=FormItem]\")?.querySelector(\"label\")?.innerText||el.getAttribute(\"aria-label\")||el.getAttribute(\"placeholder\")||el.name||\"\").substring(0,40); const orig=el.value; try{sv(el,\"E2E_TEST\");await w(80);const ok=el.value===\"E2E_TEST\";sv(el,orig);await w(50); R.fields.push({type:el.type||\"text\",label,ok});tested++; }catch(e){R.fields.push({type:el.type||\"text\",label,err:e.message?.substring(0,60)});errors++;}}const textareas=scope.querySelectorAll(\"textarea\");for(const el of textareas){ if(el.offsetParent===null||el.readOnly||el.disabled)continue; const label=(el.closest(\"[class*=field],[class*=Field],[class*=form-item]\")?.querySelector(\"label\")?.innerText||el.getAttribute(\"placeholder\")||\"\").substring(0,40); const orig=el.value; try{sv(el,\"E2E_TEXTAREA\");await w(80);const ok=el.value===\"E2E_TEXTAREA\";sv(el,orig);await w(50); R.fields.push({type:\"textarea\",label,ok});tested++; }catch(e){R.fields.push({type:\"textarea\",label,err:e.message?.substring(0,60)});errors++;}}const combos=scope.querySelectorAll(\"button[role=combobox]\");for(const cb of combos){ if(cb.offsetParent===null||cb.disabled)continue; const label=(cb.closest(\"[class*=field],[class*=Field],[class*=form-item]\")?.querySelector(\"label\")?.innerText||cb.getAttribute(\"aria-label\")||cb.innerText?.trim()||\"\").substring(0,40); const origText=cb.innerText?.trim(); try{ cb.scrollIntoView({block:\"center\"});await w(200);cb.click();await w(600); const lb=document.querySelector(\"[role=listbox]\"); if(!lb){document.dispatchEvent(new KeyboardEvent(\"keydown\",{key:\"Escape\",bubbles:true}));await w(200); R.fields.push({type:\"combobox\",label,opts:0,err:\"no listbox\"});skipped++;continue;} const opts=Array.from(lb.querySelectorAll(\"[role=option]\")); R.fields.push({type:\"combobox\",label,opts:opts.length,samples:opts.slice(0,5).map(o=>o.innerText?.trim())}); if(opts.length>0){ const pick=opts.find(o=>o.innerText?.trim()!==origText)||opts[0]; pick.click();await w(400); const changed=cb.innerText?.trim()!==origText; R.fields[R.fields.length-1].selected=pick.innerText?.trim().substring(0,30); R.fields[R.fields.length-1].changed=changed; cb.click();await w(400); const lb2=document.querySelector(\"[role=listbox]\"); if(lb2){const r2=Array.from(lb2.querySelectorAll(\"[role=option]\")).find(o=>o.innerText?.trim()===origText)||lb2.querySelector(\"[role=option]\");if(r2)r2.click();await w(300);} else{document.dispatchEvent(new KeyboardEvent(\"keydown\",{key:\"Escape\",bubbles:true}));await w(200);} }else{document.dispatchEvent(new KeyboardEvent(\"keydown\",{key:\"Escape\",bubbles:true}));await w(200);} tested++; }catch(e){document.dispatchEvent(new KeyboardEvent(\"keydown\",{key:\"Escape\",bubbles:true})); R.fields.push({type:\"combobox\",label,err:e.message?.substring(0,60)});errors++;}}const dateSel=\"input[type=date],input[placeholder*=날짜],input[placeholder*=일자],input[placeholder*=YYYY],input[placeholder*=yyyy]\";const dateInputs=scope.querySelectorAll(dateSel);for(const el of dateInputs){ if(el.offsetParent===null||el.readOnly||el.disabled)continue; const label=(el.closest(\"[class*=field],[class*=Field]\")?.querySelector(\"label\")?.innerText||el.getAttribute(\"placeholder\")||\"\").substring(0,40); const orig=el.value; try{sv(el,\"2026-01-15\");await w(150);const ok=el.value.includes(\"2026\");sv(el,orig);await w(80); R.fields.push({type:\"datepicker\",label,ok});tested++; }catch(e){R.fields.push({type:\"datepicker\",label,err:e.message?.substring(0,60)});errors++;}}const radios=scope.querySelectorAll(\"input[type=radio]\");const radioGroups=new Map();for(const r of radios){if(r.offsetParent===null||r.disabled)continue;const n=r.name||r.id||\"unnamed\";if(!radioGroups.has(n))radioGroups.set(n,[]);radioGroups.get(n).push(r);}for(const[name,group]of radioGroups){ const lbl=group[0].closest(\"[class*=field],[class*=Field],[class*=form-item]\"); const label=(lbl?.querySelector(\"label\")?.innerText||group[0].closest(\"label\")?.innerText||name).substring(0,40); try{const origIdx=group.findIndex(r=>r.checked);const target=group.find(r=>!r.checked)||group[0]; target.click();await w(200);const ok=target.checked; if(origIdx>=0&&group[origIdx]){group[origIdx].click();await w(150);} R.fields.push({type:\"radio\",label,count:group.length,options:group.map(r=>r.closest(\"label\")?.innerText?.trim()||r.value).slice(0,5),ok});tested++; }catch(e){R.fields.push({type:\"radio\",label,err:e.message?.substring(0,60)});errors++;}}const switches=scope.querySelectorAll(\"[role=switch]\");for(const sw of switches){ if(sw.offsetParent===null||sw.disabled)continue; const label=(sw.closest(\"[class*=field],[class*=Field]\")?.querySelector(\"label\")?.innerText||sw.getAttribute(\"aria-label\")||\"\").substring(0,40); const origState=sw.getAttribute(\"aria-checked\")||sw.getAttribute(\"data-state\"); try{sw.click();await w(250); const newState=sw.getAttribute(\"aria-checked\")||sw.getAttribute(\"data-state\"); const changed=newState!==origState; sw.click();await w(250); R.fields.push({type:\"toggle\",label,origState,changed});tested++; }catch(e){R.fields.push({type:\"toggle\",label,err:e.message?.substring(0,60)});errors++;}}const checkboxes=scope.querySelectorAll(\"input[type=checkbox]\");for(const cb of checkboxes){ if(cb.offsetParent===null||cb.disabled)continue; const label=(cb.closest(\"label\")?.innerText||cb.getAttribute(\"aria-label\")||cb.name||\"\").substring(0,40); if(/전체|all|select.all/i.test(label))continue; const orig=cb.checked; try{cb.click();await w(150);const toggled=cb.checked!==orig;cb.click();await w(150); R.fields.push({type:\"checkbox\",label,toggled});tested++; }catch(e){R.fields.push({type:\"checkbox\",label,err:e.message?.substring(0,60)});errors++;}}R.summary={totalFields:R.fields.length,totalTested:tested,totalErrors:errors,totalSkipped:skipped,byType:{}};for(const f of R.fields){R.summary.byType[f.type]=(R.summary.byType[f.type]||0)+1;}R.summary.coverage=R.fields.length>0?Math.round(tested/R.fields.length*100):0;return JSON.stringify(R);})()",
"timeout": 60000
},
{
"id": 6,
"name": "[생산관리 > 작업지시 관리] 모달/폼 닫기",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));for(let i=0;i<3;i++){ const modal=document.querySelector(\"[role='dialog'],[aria-modal='true']\"); if(!modal||modal.offsetParent===null)break; const closeBtn=modal.querySelector(\"button[class*=close],[aria-label=닫기],[aria-label=Close]\") ||Array.from(modal.querySelectorAll(\"button\")).find(b=>/닫기|Close|취소|Cancel/.test(b.innerText?.trim())); if(closeBtn){closeBtn.click();await w(500);} else{document.dispatchEvent(new KeyboardEvent(\"keydown\",{key:\"Escape\",keyCode:27,bubbles:true}));await w(500);}}if(/mode=(new|edit)/.test(location.search)){ const cancelBtn=Array.from(document.querySelectorAll(\"button\")).find(b=>/취소|Cancel|목록/.test(b.innerText?.trim())); if(cancelBtn){cancelBtn.click();await w(1500);} else{history.back();await w(1500);}}return JSON.stringify({url:location.pathname+location.search});})()",
"timeout": 10000
},
{
"id": 7,
"name": "[생산관리 > 작업실적] 메뉴 이동",
"action": "menu_navigate",
"level1": "생산관리",
"level2": "작업실적"
},
{
"id": 8,
"name": "[생산관리 > 작업실적] 페이지 로드 대기",
"action": "wait",
"timeout": 3000
},
{
"id": 9,
"name": "[생산관리 > 작업실적] 테이블 로드 대기",
"action": "wait_for_table",
"timeout": 5000
},
{
"id": 10,
"name": "[생산관리 > 작업실적] 등록 폼 열기",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const urlBefore=location.href;const btns=Array.from(document.querySelectorAll(\"button\")).filter(b=>b.offsetParent!==null&&!b.disabled);const priorities=[ b=>/등록/.test(b.innerText?.trim()), b=>/추가/.test(b.innerText?.trim()), b=>/작성/.test(b.innerText?.trim()), b=>/^\\+/.test(b.innerText?.trim()), b=>/신규/.test(b.innerText?.trim())&&!/신규업체|신규거래/.test(b.innerText?.trim()),];let regBtn=null;for(const pred of priorities){regBtn=btns.find(pred);if(regBtn)break;}if(!regBtn)regBtn=btns.find(b=>/신규/.test(b.innerText?.trim()));if(regBtn){ regBtn.scrollIntoView({block:\"center\"});await w(300); regBtn.click();await w(2500); const modal=document.querySelector(\"[role='dialog'],[aria-modal='true']\"); const hasModal=modal&&modal.offsetParent!==null; const urlChanged=location.href!==urlBefore; const inputs=document.querySelectorAll(\"input,textarea,select,button[role='combobox']\"); const vis=Array.from(inputs).filter(el=>el.offsetParent!==null&&!el.disabled).length; return JSON.stringify({opened:true,hasModal,urlChanged,url:location.pathname+location.search,btnText:regBtn.innerText?.trim().substring(0,30),visibleInputs:vis});}const row=document.querySelector(\"table tbody tr\");if(row){ row.click();await w(2500); const modal=document.querySelector(\"[role='dialog'],[aria-modal='true']\"); const hasModal=modal&&modal.offsetParent!==null; const urlChanged=location.href!==urlBefore; const inputs=document.querySelectorAll(\"input,textarea,select,button[role='combobox']\"); const vis=Array.from(inputs).filter(el=>el.offsetParent!==null&&!el.disabled).length; return JSON.stringify({opened:true,hasModal,urlChanged,usedRowClick:true,visibleInputs:vis});}const form=document.querySelector(\"form\");if(form){return JSON.stringify({opened:true,isInlineForm:true});}return JSON.stringify({opened:false,noBtn:true,available:btns.slice(0,8).map(b=>b.innerText?.trim().substring(0,20)).filter(Boolean)});})()",
"timeout": 15000
},
{
"id": 11,
"name": "[생산관리 > 작업실적] 폼 렌더링 대기",
"action": "wait",
"timeout": 1500
},
{
"id": 12,
"name": "[생산관리 > 작업실적] 입력 필드 전수 테스트",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={url:location.pathname+location.search,fields:[],summary:{}};const modal=document.querySelector(\"[role='dialog'],[aria-modal='true']\");const hasModal=modal&&modal.offsetParent!==null;const mainContent=document.querySelector(\"main,[class*='content']:not(nav):not(aside),[class*='Content']:not(nav)\");const scope=hasModal?modal:(mainContent||document.body);R.scope=hasModal?\"modal\":mainContent?\"main\":\"body\";const sv=(el,v)=>{ const proto=el.tagName===\"TEXTAREA\"?HTMLTextAreaElement.prototype:HTMLInputElement.prototype; const ns=Object.getOwnPropertyDescriptor(proto,\"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}));};let tested=0,errors=0,skipped=0;const textSel=\"input[type=text],input[type=number],input[type=email],input[type=tel],input[type=url],input:not([type]):not([role])\";const textInputs=scope.querySelectorAll(textSel);for(const el of textInputs){ if(el.offsetParent===null||el.readOnly||el.disabled)continue; if(el.placeholder?.includes(\"검색\")||el.type===\"search\")continue; const label=(el.closest(\"[class*=field],[class*=Field],[class*=form-item],[class*=FormItem]\")?.querySelector(\"label\")?.innerText||el.getAttribute(\"aria-label\")||el.getAttribute(\"placeholder\")||el.name||\"\").substring(0,40); const orig=el.value; try{sv(el,\"E2E_TEST\");await w(80);const ok=el.value===\"E2E_TEST\";sv(el,orig);await w(50); R.fields.push({type:el.type||\"text\",label,ok});tested++; }catch(e){R.fields.push({type:el.type||\"text\",label,err:e.message?.substring(0,60)});errors++;}}const textareas=scope.querySelectorAll(\"textarea\");for(const el of textareas){ if(el.offsetParent===null||el.readOnly||el.disabled)continue; const label=(el.closest(\"[class*=field],[class*=Field],[class*=form-item]\")?.querySelector(\"label\")?.innerText||el.getAttribute(\"placeholder\")||\"\").substring(0,40); const orig=el.value; try{sv(el,\"E2E_TEXTAREA\");await w(80);const ok=el.value===\"E2E_TEXTAREA\";sv(el,orig);await w(50); R.fields.push({type:\"textarea\",label,ok});tested++; }catch(e){R.fields.push({type:\"textarea\",label,err:e.message?.substring(0,60)});errors++;}}const combos=scope.querySelectorAll(\"button[role=combobox]\");for(const cb of combos){ if(cb.offsetParent===null||cb.disabled)continue; const label=(cb.closest(\"[class*=field],[class*=Field],[class*=form-item]\")?.querySelector(\"label\")?.innerText||cb.getAttribute(\"aria-label\")||cb.innerText?.trim()||\"\").substring(0,40); const origText=cb.innerText?.trim(); try{ cb.scrollIntoView({block:\"center\"});await w(200);cb.click();await w(600); const lb=document.querySelector(\"[role=listbox]\"); if(!lb){document.dispatchEvent(new KeyboardEvent(\"keydown\",{key:\"Escape\",bubbles:true}));await w(200); R.fields.push({type:\"combobox\",label,opts:0,err:\"no listbox\"});skipped++;continue;} const opts=Array.from(lb.querySelectorAll(\"[role=option]\")); R.fields.push({type:\"combobox\",label,opts:opts.length,samples:opts.slice(0,5).map(o=>o.innerText?.trim())}); if(opts.length>0){ const pick=opts.find(o=>o.innerText?.trim()!==origText)||opts[0]; pick.click();await w(400); const changed=cb.innerText?.trim()!==origText; R.fields[R.fields.length-1].selected=pick.innerText?.trim().substring(0,30); R.fields[R.fields.length-1].changed=changed; cb.click();await w(400); const lb2=document.querySelector(\"[role=listbox]\"); if(lb2){const r2=Array.from(lb2.querySelectorAll(\"[role=option]\")).find(o=>o.innerText?.trim()===origText)||lb2.querySelector(\"[role=option]\");if(r2)r2.click();await w(300);} else{document.dispatchEvent(new KeyboardEvent(\"keydown\",{key:\"Escape\",bubbles:true}));await w(200);} }else{document.dispatchEvent(new KeyboardEvent(\"keydown\",{key:\"Escape\",bubbles:true}));await w(200);} tested++; }catch(e){document.dispatchEvent(new KeyboardEvent(\"keydown\",{key:\"Escape\",bubbles:true})); R.fields.push({type:\"combobox\",label,err:e.message?.substring(0,60)});errors++;}}const dateSel=\"input[type=date],input[placeholder*=날짜],input[placeholder*=일자],input[placeholder*=YYYY],input[placeholder*=yyyy]\";const dateInputs=scope.querySelectorAll(dateSel);for(const el of dateInputs){ if(el.offsetParent===null||el.readOnly||el.disabled)continue; const label=(el.closest(\"[class*=field],[class*=Field]\")?.querySelector(\"label\")?.innerText||el.getAttribute(\"placeholder\")||\"\").substring(0,40); const orig=el.value; try{sv(el,\"2026-01-15\");await w(150);const ok=el.value.includes(\"2026\");sv(el,orig);await w(80); R.fields.push({type:\"datepicker\",label,ok});tested++; }catch(e){R.fields.push({type:\"datepicker\",label,err:e.message?.substring(0,60)});errors++;}}const radios=scope.querySelectorAll(\"input[type=radio]\");const radioGroups=new Map();for(const r of radios){if(r.offsetParent===null||r.disabled)continue;const n=r.name||r.id||\"unnamed\";if(!radioGroups.has(n))radioGroups.set(n,[]);radioGroups.get(n).push(r);}for(const[name,group]of radioGroups){ const lbl=group[0].closest(\"[class*=field],[class*=Field],[class*=form-item]\"); const label=(lbl?.querySelector(\"label\")?.innerText||group[0].closest(\"label\")?.innerText||name).substring(0,40); try{const origIdx=group.findIndex(r=>r.checked);const target=group.find(r=>!r.checked)||group[0]; target.click();await w(200);const ok=target.checked; if(origIdx>=0&&group[origIdx]){group[origIdx].click();await w(150);} R.fields.push({type:\"radio\",label,count:group.length,options:group.map(r=>r.closest(\"label\")?.innerText?.trim()||r.value).slice(0,5),ok});tested++; }catch(e){R.fields.push({type:\"radio\",label,err:e.message?.substring(0,60)});errors++;}}const switches=scope.querySelectorAll(\"[role=switch]\");for(const sw of switches){ if(sw.offsetParent===null||sw.disabled)continue; const label=(sw.closest(\"[class*=field],[class*=Field]\")?.querySelector(\"label\")?.innerText||sw.getAttribute(\"aria-label\")||\"\").substring(0,40); const origState=sw.getAttribute(\"aria-checked\")||sw.getAttribute(\"data-state\"); try{sw.click();await w(250); const newState=sw.getAttribute(\"aria-checked\")||sw.getAttribute(\"data-state\"); const changed=newState!==origState; sw.click();await w(250); R.fields.push({type:\"toggle\",label,origState,changed});tested++; }catch(e){R.fields.push({type:\"toggle\",label,err:e.message?.substring(0,60)});errors++;}}const checkboxes=scope.querySelectorAll(\"input[type=checkbox]\");for(const cb of checkboxes){ if(cb.offsetParent===null||cb.disabled)continue; const label=(cb.closest(\"label\")?.innerText||cb.getAttribute(\"aria-label\")||cb.name||\"\").substring(0,40); if(/전체|all|select.all/i.test(label))continue; const orig=cb.checked; try{cb.click();await w(150);const toggled=cb.checked!==orig;cb.click();await w(150); R.fields.push({type:\"checkbox\",label,toggled});tested++; }catch(e){R.fields.push({type:\"checkbox\",label,err:e.message?.substring(0,60)});errors++;}}R.summary={totalFields:R.fields.length,totalTested:tested,totalErrors:errors,totalSkipped:skipped,byType:{}};for(const f of R.fields){R.summary.byType[f.type]=(R.summary.byType[f.type]||0)+1;}R.summary.coverage=R.fields.length>0?Math.round(tested/R.fields.length*100):0;return JSON.stringify(R);})()",
"timeout": 60000
},
{
"id": 13,
"name": "[생산관리 > 작업실적] 모달/폼 닫기",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));for(let i=0;i<3;i++){ const modal=document.querySelector(\"[role='dialog'],[aria-modal='true']\"); if(!modal||modal.offsetParent===null)break; const closeBtn=modal.querySelector(\"button[class*=close],[aria-label=닫기],[aria-label=Close]\") ||Array.from(modal.querySelectorAll(\"button\")).find(b=>/닫기|Close|취소|Cancel/.test(b.innerText?.trim())); if(closeBtn){closeBtn.click();await w(500);} else{document.dispatchEvent(new KeyboardEvent(\"keydown\",{key:\"Escape\",keyCode:27,bubbles:true}));await w(500);}}if(/mode=(new|edit)/.test(location.search)){ const cancelBtn=Array.from(document.querySelectorAll(\"button\")).find(b=>/취소|Cancel|목록/.test(b.innerText?.trim())); if(cancelBtn){cancelBtn.click();await w(1500);} else{history.back();await w(1500);}}return JSON.stringify({url:location.pathname+location.search});})()",
"timeout": 10000
}
]
}