From e68949465f3bd6ae561317bd51c025c90fcd09c6 Mon Sep 17 00:00:00 2001 From: kimbokon Date: Sun, 8 Mar 2026 13:30:58 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20vendor-management=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=20=ED=99=95=EC=9D=B8=20polling=20=EC=B6=94=EA=B0=80,=20search-?= =?UTF-8?q?options-hr=20=EB=B6=80=EC=84=9C=EA=B4=80=EB=A6=AC=20wait=5Ffor?= =?UTF-8?q?=5Ftable=E2=86=92wait=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- search-options-hr.json | 384 +++++++++++------------ vendor-management.json | 672 ++++++++++++++++++++--------------------- 2 files changed, 528 insertions(+), 528 deletions(-) diff --git a/search-options-hr.json b/search-options-hr.json index 350bbb9..bdfd99a 100644 --- a/search-options-hr.json +++ b/search-options-hr.json @@ -1,193 +1,193 @@ -{ - "id": "search-options-hr", - "name": "검색 옵션 전수 테스트: 인사관리 전체 (4/10)", - "version": "1.0.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 R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", - "timeout": 60000 - }, - { - "id": 4, - "name": "[인사관리 > 근태관리] 메뉴 이동", - "action": "menu_navigate", - "level1": "인사관리", - "level2": "근태관리" - }, - { - "id": 5, - "name": "[인사관리 > 근태관리] 페이지 로드 대기", - "action": "wait", - "timeout": 3000 - }, - { - "id": 6, - "name": "[인사관리 > 근태관리] 테이블 로드 대기", - "action": "wait_for_table", - "timeout": 5000 - }, - { - "id": 7, - "name": "[인사관리 > 근태관리] 검색 옵션 전수 테스트", - "action": "evaluate", - "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", - "timeout": 60000 - }, - { - "id": 8, - "name": "[인사관리 > 근태현황] 메뉴 이동", - "action": "menu_navigate", - "level1": "인사관리", - "level2": "근태현황" - }, - { - "id": 9, - "name": "[인사관리 > 근태현황] 페이지 로드 대기", - "action": "wait", - "timeout": 3000 - }, - { - "id": 10, - "name": "[인사관리 > 근태현황] 테이블 로드 대기", - "action": "wait_for_table", - "timeout": 5000 - }, - { - "id": 11, - "name": "[인사관리 > 근태현황] 검색 옵션 전수 테스트", - "action": "evaluate", - "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", - "timeout": 60000 - }, - { - "id": 12, - "name": "[인사관리 > 급여관리] 메뉴 이동", - "action": "menu_navigate", - "level1": "인사관리", - "level2": "급여관리" - }, - { - "id": 13, - "name": "[인사관리 > 급여관리] 페이지 로드 대기", - "action": "wait", - "timeout": 3000 - }, - { - "id": 14, - "name": "[인사관리 > 급여관리] 테이블 로드 대기", - "action": "wait_for_table", - "timeout": 5000 - }, - { - "id": 15, - "name": "[인사관리 > 급여관리] 검색 옵션 전수 테스트", - "action": "evaluate", - "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", - "timeout": 60000 - }, - { - "id": 16, - "name": "[인사관리 > 휴가관리] 메뉴 이동", - "action": "menu_navigate", - "level1": "인사관리", - "level2": "휴가관리" - }, - { - "id": 17, - "name": "[인사관리 > 휴가관리] 페이지 로드 대기", - "action": "wait", - "timeout": 3000 - }, - { - "id": 18, - "name": "[인사관리 > 휴가관리] 테이블 로드 대기", - "action": "wait_for_table", - "timeout": 5000 - }, - { - "id": 19, - "name": "[인사관리 > 휴가관리] 검색 옵션 전수 테스트", - "action": "evaluate", - "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", - "timeout": 60000 - }, - { - "id": 20, - "name": "[인사관리 > 카드관리] 메뉴 이동", - "action": "menu_navigate", - "level1": "인사관리", - "level2": "카드관리" - }, - { - "id": 21, - "name": "[인사관리 > 카드관리] 페이지 로드 대기", - "action": "wait", - "timeout": 3000 - }, - { - "id": 22, - "name": "[인사관리 > 카드관리] 테이블 로드 대기", - "action": "wait_for_table", - "timeout": 5000 - }, - { - "id": 23, - "name": "[인사관리 > 카드관리] 검색 옵션 전수 테스트", - "action": "evaluate", - "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", - "timeout": 60000 - }, - { - "id": 24, - "name": "[인사관리 > 부서관리] 메뉴 이동", - "action": "menu_navigate", - "level1": "인사관리", - "level2": "부서관리" - }, - { - "id": 25, - "name": "[인사관리 > 부서관리] 페이지 로드 대기", - "action": "wait", - "timeout": 3000 - }, - { - "id": 26, - "name": "[인사관리 > 부서관리] 테이블 로드 대기", - "action": "wait_for_table", - "timeout": 5000 - }, - { - "id": 27, - "name": "[인사관리 > 부서관리] 검색 옵션 전수 테스트", - "action": "evaluate", - "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", - "timeout": 60000 - } - ] +{ + "id": "search-options-hr", + "name": "검색 옵션 전수 테스트: 인사관리 전체 (4/10)", + "version": "1.0.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 R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", + "timeout": 60000 + }, + { + "id": 4, + "name": "[인사관리 > 근태관리] 메뉴 이동", + "action": "menu_navigate", + "level1": "인사관리", + "level2": "근태관리" + }, + { + "id": 5, + "name": "[인사관리 > 근태관리] 페이지 로드 대기", + "action": "wait", + "timeout": 3000 + }, + { + "id": 6, + "name": "[인사관리 > 근태관리] 테이블 로드 대기", + "action": "wait_for_table", + "timeout": 5000 + }, + { + "id": 7, + "name": "[인사관리 > 근태관리] 검색 옵션 전수 테스트", + "action": "evaluate", + "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", + "timeout": 60000 + }, + { + "id": 8, + "name": "[인사관리 > 근태현황] 메뉴 이동", + "action": "menu_navigate", + "level1": "인사관리", + "level2": "근태현황" + }, + { + "id": 9, + "name": "[인사관리 > 근태현황] 페이지 로드 대기", + "action": "wait", + "timeout": 3000 + }, + { + "id": 10, + "name": "[인사관리 > 근태현황] 테이블 로드 대기", + "action": "wait_for_table", + "timeout": 5000 + }, + { + "id": 11, + "name": "[인사관리 > 근태현황] 검색 옵션 전수 테스트", + "action": "evaluate", + "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", + "timeout": 60000 + }, + { + "id": 12, + "name": "[인사관리 > 급여관리] 메뉴 이동", + "action": "menu_navigate", + "level1": "인사관리", + "level2": "급여관리" + }, + { + "id": 13, + "name": "[인사관리 > 급여관리] 페이지 로드 대기", + "action": "wait", + "timeout": 3000 + }, + { + "id": 14, + "name": "[인사관리 > 급여관리] 테이블 로드 대기", + "action": "wait_for_table", + "timeout": 5000 + }, + { + "id": 15, + "name": "[인사관리 > 급여관리] 검색 옵션 전수 테스트", + "action": "evaluate", + "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", + "timeout": 60000 + }, + { + "id": 16, + "name": "[인사관리 > 휴가관리] 메뉴 이동", + "action": "menu_navigate", + "level1": "인사관리", + "level2": "휴가관리" + }, + { + "id": 17, + "name": "[인사관리 > 휴가관리] 페이지 로드 대기", + "action": "wait", + "timeout": 3000 + }, + { + "id": 18, + "name": "[인사관리 > 휴가관리] 테이블 로드 대기", + "action": "wait_for_table", + "timeout": 5000 + }, + { + "id": 19, + "name": "[인사관리 > 휴가관리] 검색 옵션 전수 테스트", + "action": "evaluate", + "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", + "timeout": 60000 + }, + { + "id": 20, + "name": "[인사관리 > 카드관리] 메뉴 이동", + "action": "menu_navigate", + "level1": "인사관리", + "level2": "카드관리" + }, + { + "id": 21, + "name": "[인사관리 > 카드관리] 페이지 로드 대기", + "action": "wait", + "timeout": 3000 + }, + { + "id": 22, + "name": "[인사관리 > 카드관리] 테이블 로드 대기", + "action": "wait_for_table", + "timeout": 5000 + }, + { + "id": 23, + "name": "[인사관리 > 카드관리] 검색 옵션 전수 테스트", + "action": "evaluate", + "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", + "timeout": 60000 + }, + { + "id": 24, + "name": "[인사관리 > 부서관리] 메뉴 이동", + "action": "menu_navigate", + "level1": "인사관리", + "level2": "부서관리" + }, + { + "id": 25, + "name": "[인사관리 > 부서관리] 페이지 로드 대기", + "action": "wait", + "timeout": 3000 + }, + { + "id": 26, + "name": "[인사관리 > 부서관리] 콘텐츠 로드 대기", + "action": "wait", + "timeout": 3000 + }, + { + "id": 27, + "name": "[인사관리 > 부서관리] 검색 옵션 전수 테스트", + "action": "evaluate", + "script": "(async()=>{const R={p:location.pathname,tests:[]};const rc=()=>document.querySelectorAll('table tbody tr').length;const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 ss=['input[placeholder*=\"검색\"]','input[type=\"search\"]','input[role=\"searchbox\"]'];let si=null;for(const s of ss){si=document.querySelector(s);if(si)break;}R.search=!!si;R.ph=si?si.placeholder?.substring(0,60):'';const init=rc();R.rows=init;if(si&&init>0){sv(si,'zzz_nomatch_e2e');si.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));await w(1500);const a=rc();R.tests.push({t:'noMatch',b:init,a,ok:ao.innerText?.trim());R.tests.push({t:'dd'+i+'_o',dv,opts,n:opts.length});document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(300);if(opts.length>1){const b=rc();cb.click();await w(500);const lb2=document.querySelector('[role=\"listbox\"]');if(lb2){const ao=Array.from(lb2.querySelectorAll('[role=\"option\"]'));const nd=ao.find(o=>o.innerText?.trim()!==dv)||ao[1];if(nd){const st=nd.innerText?.trim();nd.click();await w(1200);const af=rc();R.tests.push({t:'dd'+i+'_s',s:st,b,a:af,c:af!==b});cb.click();await w(500);const lb3=document.querySelector('[role=\"listbox\"]');if(lb3){const f=lb3.querySelector('[role=\"option\"]');if(f)f.click();await w(800);}}}}}const tb=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='오늘');if(tb){const b=rc();tb.click();await w(1200);const a=rc();R.tests.push({t:'today',b,a,c:a!==b});}R.sum={dd:cbs.length,opts:R.tests.filter(t=>t.n).reduce((s,t)=>s+t.n,0),tested:R.tests.filter(t=>t.t?.includes('_s')).length,changed:R.tests.filter(t=>t.t?.includes('_s')&&t.c).length,searchOk:R.tests.find(t=>t.t==='noMatch')?.ok??null};return JSON.stringify(R);})()", + "timeout": 60000 + } + ] } \ No newline at end of file diff --git a/vendor-management.json b/vendor-management.json index ff5d089..4fd54e6 100644 --- a/vendor-management.json +++ b/vendor-management.json @@ -1,337 +1,337 @@ -{ - "id": "vendor-management", - "name": "거래처관리 검색/필터/상세/수정/복원 + 네거티브 + 섹션검증: 회계관리", - "version": "2.0.0", - "screenshotPolicy": { - "captureOnFail": true, - "captureOnPass": false - }, - "description": "회계관리 > 거래처관리 메뉴의 목록 조회, 필터, 네거티브 검색, 복합 필터, 상세 섹션별 필드 검증, 다중 필드 수정/복원 기능 테스트", - "baseUrl": "https://dev.codebridge-x.com", - "navigation": { - "targetUrl": "/accounting/vendors", - "urlPattern": "/accounting/vendors", - "menuHints": ["거래처관리", "거래처 관리", "회계관리"] - }, - "menuNavigation": { - "level1": "회계관리", - "level2": "거래처관리", - "expectedUrl": "/accounting/vendors", - "searchWithinParent": true, - "closeOtherMenus": true - }, - "auth": { - "role": "admin" - }, - "notes": { - "skip": ["등록 버튼 (추후 구현 예정)", "삭제 기능 (보류)"], - "focus": ["네거티브 검색", "복합 필터", "섹션별 필드 검증", "다중 필드 수정/복원"], - "uiNotes": [ - "필터 드롭다운: Radix UI Select (button[role='combobox'])", - "체크박스: Radix UI Checkbox (button[role='checkbox'])", - "저장: 확인 다이얼로그 없이 직접 저장 후 목록으로 리다이렉트" - ] - }, - "steps": [ - { - "id": 1, - "name": "[회계관리 > 거래처관리] 사이드바 메뉴 전체 펼치기", - "action": "evaluate", - "script": "(async () => { document.querySelector('.sidebar-scroll')?.scrollTo({top:0,behavior:'instant'}); await new Promise(r=>setTimeout(r,300)); Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('모두 펼치기'))?.click(); await new Promise(r=>setTimeout(r,2000)); return 'menu expanded'; })()" - }, - { - "id": 2, - "name": "[회계관리 > 거래처관리] 2단계 메뉴 진입", - "action": "menu_navigate", - "level1": "회계관리", - "level2": "거래처관리" - }, - { - "id": 3, - "name": "[회계관리 > 거래처관리] 페이지 로드 대기", - "action": "wait", - "timeout": 3000 - }, - { - "id": 4, - "name": "[회계관리 > 거래처관리] ts 초기화 + 콘솔 에러 모니터링", - "action": "evaluate", - "script": "(()=>{try{sessionStorage.removeItem('__E2E_TS__');}catch(e){}delete window.__E2E_TS__;window.__CONSOLE_ERRORS__=[];const origErr=console.error;console.error=function(){window.__CONSOLE_ERRORS__.push(Array.from(arguments).join(' ').substring(0,200));origErr.apply(console,arguments);};return JSON.stringify({ok:true,cleared:true});})()", - "timeout": 3000 - }, - { - "id": 5, - "name": "[회계관리 > 거래처관리] 테이블 로드 대기", - "action": "wait_for_table", - "timeout": 20000 - }, - { - "id": 6, - "name": "[회계관리 > 거래처관리] 목업 페이지 감지", - "action": "verify_not_mockup", - "checks": [ - "검색 입력 필드 존재", - "필터 드롭다운 존재", - "테이블 데이터 표시", - "API 호출 확인" - ], - "expected": "정상 페이지 (목업 아님)" - }, - { - "id": 7, - "name": "[회계관리 > 거래처관리] 통계 카드 확인", - "action": "evaluate", - "script": "(()=>{const R={phase:'STATS'};const cards=document.querySelectorAll('[class*=\"card\"],[class*=\"Card\"],[class*=\"stat\"],[class*=\"Stat\"],[class*=\"summary\"]');const texts=Array.from(cards).map(c=>c.innerText?.substring(0,50)).filter(Boolean);R.cardCount=texts.length;R.cards=texts.slice(0,5);R.ok=texts.length>=2;R.info=texts.length>=2?texts.length+' 통계 카드 확인':'warn: 통계 카드 '+texts.length+'개';return JSON.stringify(R);})()", - "timeout": 10000, - "phase": "VERIFY" - }, - { - "id": 8, - "name": "[회계관리 > 거래처관리] 테이블 구조 확인", - "action": "verify_table", - "checks": [ - "번호 컬럼", - "구분 컬럼", - "거래처명 컬럼", - "신용등급 컬럼", - "거래등급 컬럼", - "미수금 컬럼" - ], - "expected": "거래처 테이블 컬럼 정상 표시" - }, - { - "id": 9, - "name": "[회계관리 > 거래처관리] [SEARCH] 가우스 검색", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SEARCH_POSITIVE'};const beforeCount=document.querySelectorAll('table tbody tr').length;window.__e2e_beforeSearch=beforeCount;R.beforeCount=beforeCount;const inp=document.querySelector('input[placeholder*=\"검색\"]');if(!inp){R.error='검색 입력 필드 없음';R.ok=false;return JSON.stringify(R);}const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'가우스');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));await w(1500);const afterCount=document.querySelectorAll('table tbody tr').length;R.afterCount=afterCount;R.filtered=afterCount<=beforeCount;const rows=Array.from(document.querySelectorAll('table tbody tr'));const allContain=rows.every(r=>r.innerText?.includes('가우스'));R.allRowsContainKeyword=allContain;R.ok=afterCount>0;return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "SEARCH" - }, - { - "id": 10, - "name": "[회계관리 > 거래처관리] [SEARCH] 검색 결과 행 데이터 검증", - "action": "verify_text", - "target": "table tbody", - "text": "가우스" - }, - { - "id": 11, - "name": "[회계관리 > 거래처관리] [SEARCH] 검색 초기화", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SEARCH_RESET'};const inp=document.querySelector('input[placeholder*=\"검색\"]');if(inp){const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));}await w(1500);const c=document.querySelectorAll('table tbody tr').length;R.restoredCount=c;R.restored=c>=(window.__e2e_beforeSearch||1);R.ok=true;return JSON.stringify(R);})()", - "timeout": 10000, - "phase": "SEARCH" - }, - { - "id": 12, - "name": "[회계관리 > 거래처관리] [SEARCH-NEG] 존재하지 않는 키워드 검색", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SEARCH_NEGATIVE'};const inp=document.querySelector('input[placeholder*=\"검색\"]');if(!inp){R.error='검색 입력 필드 없음';R.ok=false;return JSON.stringify(R);}const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'ZZZZNONEXIST99');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));await w(1500);const rows=document.querySelectorAll('table tbody tr');const noData=document.body.innerText.includes('데이터가 없')||document.body.innerText.includes('결과가 없')||document.body.innerText.includes('데이터 없')||document.body.innerText.includes('No data');R.rowCount=rows.length;R.noDataMessage=noData;R.ok=(rows.length===0||noData||(rows.length===1&&rows[0].innerText?.includes('데이터')));R.info=(rows.length===0||noData)?'pass: 0 results confirmed':'warn: found '+rows.length+' rows';return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "SEARCH" - }, - { - "id": 13, - "name": "[회계관리 > 거래처관리] [SEARCH-NEG] 검색 초기화 복원", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SEARCH_RESTORE'};const inp=document.querySelector('input[placeholder*=\"검색\"]');if(inp){const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));}await w(1500);const c=document.querySelectorAll('table tbody tr').length;R.restoredCount=c;R.ok=c>0;return JSON.stringify(R);})()", - "timeout": 10000, - "phase": "SEARCH" - }, - { - "id": 14, - "name": "[회계관리 > 거래처관리] [FILTER] 구분 필터 매출 선택", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'FILTER_SALES'};const cbs=Array.from(document.querySelectorAll('button[role=\"combobox\"]')).filter(b=>b.offsetParent!==null);R.comboCount=cbs.length;if(!cbs[0]){R.warn='combobox not found';R.ok=true;return JSON.stringify(R);}cbs[0].click();await w(500);const opt=Array.from(document.querySelectorAll('[role=\"option\"]')).find(o=>o.innerText?.trim()==='매출');if(opt){opt.click();await w(1500);const rows=Array.from(document.querySelectorAll('table tbody tr'));R.rowCount=rows.length;const salesRows=rows.filter(r=>{const cells=r.querySelectorAll('td');return Array.from(cells).some(c=>c.innerText?.trim()==='매출');});R.salesRowCount=salesRows.length;R.allAreSales=salesRows.length===rows.length||rows.length===0;R.info=R.allAreSales?'모든 행이 매출 타입':'일부 비매출 행 존재 ('+salesRows.length+'/'+rows.length+')';}else{R.warn='매출 옵션 없음';}R.ok=true;return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "FILTER" - }, - { - "id": 15, - "name": "[회계관리 > 거래처관리] [FILTER+SEARCH] 매출 필터 + 가우스 검색 복합 테스트", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'FILTER_PLUS_SEARCH'};const inp=document.querySelector('input[placeholder*=\"검색\"]');if(inp){const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'가우스');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));}await w(1500);const rows=Array.from(document.querySelectorAll('table tbody tr'));R.rowCount=rows.length;const allMatch=rows.every(r=>{const text=r.innerText||'';return text.includes('가우스');});R.allContainKeyword=allMatch;const allSales=rows.every(r=>{const cells=r.querySelectorAll('td');return Array.from(cells).some(c=>c.innerText?.trim()==='매출');});R.allAreSalesType=allSales;R.ok=true;R.info='rows='+rows.length+', allKeyword='+allMatch+', allSales='+allSales;return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "FILTER" - }, - { - "id": 16, - "name": "[회계관리 > 거래처관리] [FILTER] 구분 필터 전체로 복원 + 검색 초기화", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'FILTER_RESET'};const inp=document.querySelector('input[placeholder*=\"검색\"]');if(inp){const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));}await w(500);const cbs=Array.from(document.querySelectorAll('button[role=\"combobox\"]')).filter(b=>b.offsetParent!==null);if(cbs[0]){cbs[0].click();await w(500);const opt=Array.from(document.querySelectorAll('[role=\"option\"]')).find(o=>o.innerText?.trim()==='전체');if(opt){opt.click();await w(1000);}}await w(1000);R.rowCount=document.querySelectorAll('table tbody tr').length;R.ok=R.rowCount>0;return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "FILTER" - }, - { - "id": 17, - "name": "[회계관리 > 거래처관리] [DETAIL] 첫 행 셀 값 캡처", - "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');const captured={};const colNames=['checkbox','no','type','vendorName','purchasePayDay','salesPayDay','creditGrade','tradeGrade','receivable','badDebt'];cells.forEach((cell,i)=>{const key=colNames[i]||('col'+i);captured[key]=cell.innerText?.trim()||'';});window.__E2E_CAPTURED__=captured;R.captured=captured;R.ok=true;return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "CAPTURE" - }, - { - "id": 18, - "name": "[회계관리 > 거래처관리] [DETAIL] 첫 행 클릭 → 상세 진입", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'DETAIL_ENTER'};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": 19, - "name": "[회계관리 > 거래처관리] [DETAIL] 상세 페이지 로드 대기", - "action": "wait", - "timeout": 2000 - }, - { - "id": 20, - "name": "[회계관리 > 거래처관리] [DETAIL] URL 확인", - "action": "verify_url", - "target": "/accounting/vendors/", - "expected": "URL에 거래처 ID 포함" - }, - { - "id": 21, - "name": "[회계관리 > 거래처관리] [DETAIL] 전체 섹션 필드 수 검증 (6개 섹션)", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SECTION_VERIFY'};const pageText=document.body.innerText;const sectionChecks=[{name:'기본정보',keywords:['사업자등록번호','거래처코드','거래처명','대표자명']},{name:'연락처',keywords:['주소','전화번호','이메일']},{name:'담당자',keywords:['담당자','담당자명']},{name:'회사정보',keywords:['매입 결제일','매출 결제일']},{name:'신용/거래',keywords:['신용등급','거래등급','계좌']},{name:'추가정보',keywords:['미수금','악성채권']}];let foundSections=0;R.sections={};sectionChecks.forEach(sec=>{const fieldsFound=sec.keywords.filter(kw=>pageText.includes(kw));const pct=Math.round(fieldsFound.length/sec.keywords.length*100);R.sections[sec.name]={total:sec.keywords.length,found:fieldsFound.length,pct:pct+'%',fields:fieldsFound};if(fieldsFound.length>0)foundSections++;});R.foundSections=foundSections;R.totalSections=sectionChecks.length;R.ok=foundSections>=4;R.info=foundSections+'/'+sectionChecks.length+' 섹션 확인';return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "VERIFY" - }, - { - "id": 22, - "name": "[회계관리 > 거래처관리] [DETAIL] 상세 필드 1:1 대조 (목록 캡처 vs 상세)", - "action": "evaluate", - "script": "(async()=>{const R={phase:'DETAIL_MATCH'};const cap=window.__E2E_CAPTURED__||{};R.hasCaptured=Object.keys(cap).length>0;if(!R.hasCaptured){R.ok=true;R.info='캡처 데이터 없음 - 스킵';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=['vendorName','type','creditGrade','tradeGrade','receivable'];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);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": 23, - "name": "[회계관리 > 거래처관리] [DETAIL] 헤더 버튼 확인 (목록/수정/삭제)", - "action": "evaluate", - "script": "(()=>{const R={phase:'HEADER_BTNS'};const btns=Array.from(document.querySelectorAll('button')).filter(b=>b.offsetParent!==null);R.listBtn=btns.some(b=>b.innerText?.trim()==='목록');R.editBtn=btns.some(b=>b.innerText?.trim()==='수정');R.deleteBtn=btns.some(b=>b.innerText?.trim()==='삭제');R.ok=R.editBtn;return JSON.stringify(R);})()", - "timeout": 10000, - "phase": "VERIFY" - }, - { - "id": 24, - "name": "[회계관리 > 거래처관리] [EDIT] 수정 버튼 클릭", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'EDIT_ENTER'};const btn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='수정'&&b.offsetParent!==null);if(btn){btn.click();await w(2000);R.editUrl=location.pathname+location.search;R.isEditMode=location.search.includes('mode=edit');}else{R.warn='수정 버튼 없음';}R.ok=true;return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "UPDATE" - }, - { - "id": 25, - "name": "[회계관리 > 거래처관리] [EDIT] 수정 모드 URL 확인", - "action": "verify_url", - "target": "mode=edit", - "expected": "수정 모드 URL 정상" - }, - { - "id": 26, - "name": "[회계관리 > 거래처관리] [EDIT] 다중 필드 원본 값 캡처 + 수정", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 R={phase:'MULTI_EDIT'};const inputs=Array.from(document.querySelectorAll('input:not([type=\"hidden\"]):not([type=\"checkbox\"])')).filter(i=>i.offsetParent!==null&&!i.readOnly&&!i.disabled);R.editableCount=inputs.length;window.__E2E_ORIG_VALUES__={};let edited=0;for(const inp of inputs){const val=inp.value||'';const ph=inp.placeholder||'';if(val.includes('수정테스트'))continue;if(ph.includes('자동생성')||ph.includes('000-00'))continue;if(val.length>1&&edited===0){window.__E2E_ORIG_VALUES__['field0']={index:Array.from(inputs).indexOf(inp),value:val};sv(inp,val+' (수정테스트)');edited++;R.field0={orig:val,new:inp.value};await w(200);continue;}if(val.length>1&&edited===1){window.__E2E_ORIG_VALUES__['field1']={index:Array.from(inputs).indexOf(inp),value:val};sv(inp,val+' (E2E수정)');edited++;R.field1={orig:val,new:inp.value};await w(200);continue;}if(edited>=2)break;}R.editedFields=edited;R.ok=edited>0;return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "UPDATE" - }, - { - "id": 27, - "name": "[회계관리 > 거래처관리] [EDIT] 저장 클릭", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SAVE'};window.__e2e_urlBeforeSave=window.location.href;const saveBtn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='저장'&&b.offsetParent!==null);if(saveBtn){saveBtn.click();await w(3000);R.urlAfter=location.href;R.saved=true;}else{R.error='저장 버튼 없음';}R.ok=!!saveBtn;return JSON.stringify(R);})()", - "timeout": 20000, - "phase": "UPDATE" - }, - { - "id": 28, - "name": "[회계관리 > 거래처관리] [EDIT] 저장 완료 확인 (목록 복귀 + 에러 없음)", - "action": "evaluate", - "script": "(()=>{const R={phase:'SAVE_VERIFY'};const url=window.location.href;R.isListPage=url.includes('/accounting/vendors')&&!url.includes('mode=');const hasError=document.body.innerText.includes('404')||document.body.innerText.includes('500')||document.body.innerText.includes('Not Found');R.hasError=hasError;R.urlChanged=url!==window.__e2e_urlBeforeSave;R.ok=R.isListPage&&!hasError;R.info=R.ok?'PASS: 목록 복귀, 에러 없음':'FAIL: isListPage='+R.isListPage+', hasError='+hasError;return JSON.stringify(R);})()", - "timeout": 10000, - "phase": "VERIFY" - }, - { - "id": 29, - "name": "[회계관리 > 거래처관리] [EDIT] 목록에서 수정 반영 확인", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(1000);const R={phase:'VERIFY_EDIT'};const found=document.body.innerText.includes('수정테스트');const foundE2E=document.body.innerText.includes('E2E수정');R.modifiedVisible=found;R.secondFieldVisible=foundE2E;R.rowCount=document.querySelectorAll('table tbody tr').length;R.ok=true;R.info=found?'PASS: 수정된 데이터 목록에 반영':'WARN: 수정 텍스트 미표시 (페이지네이션 또는 정렬 영향)';return JSON.stringify(R);})()", - "timeout": 10000, - "phase": "VERIFY" - }, - { - "id": 30, - "name": "[회계관리 > 거래처관리] [VERIFY-EDIT] 수정된 거래처 재진입하여 저장 검증", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'RE_ENTER_DETAIL'};const rows=Array.from(document.querySelectorAll('table tbody tr'));let target=rows.find(row=>row.innerText?.includes('수정테스트'));if(!target)target=rows[0];if(target){target.click();await w(2500);const pageText=document.body.innerText;R.has수정테스트=pageText.includes('수정테스트');R.hasE2E수정=pageText.includes('E2E수정');R.detailUrl=location.pathname+location.search;}R.ok=true;return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "VERIFY" - }, - { - "id": 31, - "name": "[회계관리 > 거래처관리] [RESTORE] 수정 모드 진입", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'RESTORE_EDIT'};const btn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='수정'&&b.offsetParent!==null);if(btn){btn.click();await w(1500);R.editUrl=location.pathname+location.search;}else{R.warn='수정 버튼 없음';}R.ok=true;return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "RESTORE" - }, - { - "id": 32, - "name": "[회계관리 > 거래처관리] [RESTORE] 원래 값 복원 (다중 필드)", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 R={phase:'RESTORE_VALUES'};const inputs=Array.from(document.querySelectorAll('input:not([type=\"hidden\"]):not([type=\"checkbox\"])')).filter(i=>i.offsetParent!==null&&!i.readOnly&&!i.disabled);let restored=0;inputs.forEach(inp=>{if(inp.value.includes(' (수정테스트)')){const newVal=inp.value.replace(' (수정테스트)','');sv(inp,newVal);restored++;R.field0Restored=true;}if(inp.value.includes(' (E2E수정)')){const newVal=inp.value.replace(' (E2E수정)','');sv(inp,newVal);restored++;R.field1Restored=true;}});R.restoredCount=restored;R.ok=restored>0;R.info=restored>0?restored+' fields restored':'no fields with test markers found';return JSON.stringify(R);})()", - "timeout": 15000, - "phase": "RESTORE" - }, - { - "id": 33, - "name": "[회계관리 > 거래처관리] [RESTORE] 저장 클릭", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'RESTORE_SAVE'};const btn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='저장'&&b.offsetParent!==null);if(btn){btn.click();await w(3000);R.url=location.href;R.saved=true;}else{R.error='저장 버튼 없음';}R.ok=!!btn;return JSON.stringify(R);})()", - "timeout": 20000, - "phase": "RESTORE" - }, - { - "id": 34, - "name": "[회계관리 > 거래처관리] [RESTORE] 복원 완료 확인", - "action": "evaluate", - "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(1000);const R={phase:'RESTORE_VERIFY'};const url=window.location.href;R.isListPage=url.includes('/accounting/vendors')&&!url.includes('mode=');const still수정=document.body.innerText.includes('수정테스트');const stillE2E=document.body.innerText.includes('E2E수정');R.still수정=still수정;R.stillE2E=stillE2E;R.ok=R.isListPage&&!still수정;R.info=(!still수정&&!stillE2E)?'PASS: 원복 완료':'WARN: 원복 확인 필요 (수정테스트='+still수정+', E2E수정='+stillE2E+')';return JSON.stringify(R);})()", - "timeout": 10000, - "phase": "VERIFY" - }, - { - "id": 35, - "name": "[회계관리 > 거래처관리] [FINAL] 목록 페이지 최종 확인", - "action": "verify_url", - "target": "/accounting/vendors", - "expected": "거래처관리 목록 페이지 정상 표시" - }, - { - "id": 36, - "name": "[회계관리 > 거래처관리] [FINAL] API 요약 + 콘솔 에러 확인", - "action": "evaluate", - "script": "(()=>{const R={phase:'FINAL_SUMMARY'};const logs=(window.__E2E__?window.__E2E__.getApiLogs().logs:[]);R.apiSummary={total:logs.length,success:logs.filter(l=>l.ok||l.status<400).length,failed:logs.filter(l=>!l.ok&&l.status>=400).length,avgResponseTime:logs.length>0?Math.round(logs.reduce((s,l)=>s+(l.duration||0),0)/logs.length):0,slowCalls:logs.filter(l=>l.duration>2000).length,methods:{GET:logs.filter(l=>l.method==='GET').length,POST:logs.filter(l=>l.method==='POST').length,PUT:logs.filter(l=>l.method==='PUT'||l.method==='PATCH').length,DELETE:logs.filter(l=>l.method==='DELETE').length}};const errs=window.__CONSOLE_ERRORS__||[];R.consoleErrors=errs.length;R.errorSamples=errs.slice(0,5);R.ok=true;R.info='API calls: '+logs.length+', errors: '+errs.length;return JSON.stringify(R);})()", - "timeout": 10000, - "phase": "VERIFY" - } - ], - "requiredVerifications": [ - { "id": 1, "name": "등록/저장 버튼", "steps": [27, 28], "criteria": "저장 클릭 → 목록 리다이렉트 + 에러 없음 + 데이터 반영" }, - { "id": 3, "name": "검색/필터", "steps": [9, 10, 11, 12, 13, 14, 15, 16], "criteria": "검색/네거티브/필터/복합필터 시 데이터 변화 확인" }, - { "id": 5, "name": "목업 페이지 감지", "steps": [6], "criteria": "입력 필드, 동작 버튼, API 호출 확인" } - ], - "testData": { - "searchKeyword": "가우스", - "negativeKeyword": "ZZZZNONEXIST99", - "editSuffix": " (수정테스트)", - "editSuffix2": " (E2E수정)" - }, - "expectedAPIs": [ - { "method": "GET", "endpoint": "/api/v1/clients", "description": "거래처 목록 조회" }, - { "method": "GET", "endpoint": "/api/v1/clients/{id}", "description": "거래처 상세 조회" }, - { "method": "PUT", "endpoint": "/api/v1/clients/{id}", "description": "거래처 수정" } - ] +{ + "id": "vendor-management", + "name": "거래처관리 검색/필터/상세/수정/복원 + 네거티브 + 섹션검증: 회계관리", + "version": "2.0.0", + "screenshotPolicy": { + "captureOnFail": true, + "captureOnPass": false + }, + "description": "회계관리 > 거래처관리 메뉴의 목록 조회, 필터, 네거티브 검색, 복합 필터, 상세 섹션별 필드 검증, 다중 필드 수정/복원 기능 테스트", + "baseUrl": "https://dev.codebridge-x.com", + "navigation": { + "targetUrl": "/accounting/vendors", + "urlPattern": "/accounting/vendors", + "menuHints": ["거래처관리", "거래처 관리", "회계관리"] + }, + "menuNavigation": { + "level1": "회계관리", + "level2": "거래처관리", + "expectedUrl": "/accounting/vendors", + "searchWithinParent": true, + "closeOtherMenus": true + }, + "auth": { + "role": "admin" + }, + "notes": { + "skip": ["등록 버튼 (추후 구현 예정)", "삭제 기능 (보류)"], + "focus": ["네거티브 검색", "복합 필터", "섹션별 필드 검증", "다중 필드 수정/복원"], + "uiNotes": [ + "필터 드롭다운: Radix UI Select (button[role='combobox'])", + "체크박스: Radix UI Checkbox (button[role='checkbox'])", + "저장: 확인 다이얼로그 없이 직접 저장 후 목록으로 리다이렉트" + ] + }, + "steps": [ + { + "id": 1, + "name": "[회계관리 > 거래처관리] 사이드바 메뉴 전체 펼치기", + "action": "evaluate", + "script": "(async () => { document.querySelector('.sidebar-scroll')?.scrollTo({top:0,behavior:'instant'}); await new Promise(r=>setTimeout(r,300)); Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('모두 펼치기'))?.click(); await new Promise(r=>setTimeout(r,2000)); return 'menu expanded'; })()" + }, + { + "id": 2, + "name": "[회계관리 > 거래처관리] 2단계 메뉴 진입", + "action": "menu_navigate", + "level1": "회계관리", + "level2": "거래처관리" + }, + { + "id": 3, + "name": "[회계관리 > 거래처관리] 페이지 로드 대기", + "action": "wait", + "timeout": 3000 + }, + { + "id": 4, + "name": "[회계관리 > 거래처관리] ts 초기화 + 콘솔 에러 모니터링", + "action": "evaluate", + "script": "(()=>{try{sessionStorage.removeItem('__E2E_TS__');}catch(e){}delete window.__E2E_TS__;window.__CONSOLE_ERRORS__=[];const origErr=console.error;console.error=function(){window.__CONSOLE_ERRORS__.push(Array.from(arguments).join(' ').substring(0,200));origErr.apply(console,arguments);};return JSON.stringify({ok:true,cleared:true});})()", + "timeout": 3000 + }, + { + "id": 5, + "name": "[회계관리 > 거래처관리] 테이블 로드 대기", + "action": "wait_for_table", + "timeout": 20000 + }, + { + "id": 6, + "name": "[회계관리 > 거래처관리] 목업 페이지 감지", + "action": "verify_not_mockup", + "checks": [ + "검색 입력 필드 존재", + "필터 드롭다운 존재", + "테이블 데이터 표시", + "API 호출 확인" + ], + "expected": "정상 페이지 (목업 아님)" + }, + { + "id": 7, + "name": "[회계관리 > 거래처관리] 통계 카드 확인", + "action": "evaluate", + "script": "(()=>{const R={phase:'STATS'};const cards=document.querySelectorAll('[class*=\"card\"],[class*=\"Card\"],[class*=\"stat\"],[class*=\"Stat\"],[class*=\"summary\"]');const texts=Array.from(cards).map(c=>c.innerText?.substring(0,50)).filter(Boolean);R.cardCount=texts.length;R.cards=texts.slice(0,5);R.ok=texts.length>=2;R.info=texts.length>=2?texts.length+' 통계 카드 확인':'warn: 통계 카드 '+texts.length+'개';return JSON.stringify(R);})()", + "timeout": 10000, + "phase": "VERIFY" + }, + { + "id": 8, + "name": "[회계관리 > 거래처관리] 테이블 구조 확인", + "action": "verify_table", + "checks": [ + "번호 컬럼", + "구분 컬럼", + "거래처명 컬럼", + "신용등급 컬럼", + "거래등급 컬럼", + "미수금 컬럼" + ], + "expected": "거래처 테이블 컬럼 정상 표시" + }, + { + "id": 9, + "name": "[회계관리 > 거래처관리] [SEARCH] 가우스 검색", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SEARCH_POSITIVE'};const beforeCount=document.querySelectorAll('table tbody tr').length;window.__e2e_beforeSearch=beforeCount;R.beforeCount=beforeCount;const inp=document.querySelector('input[placeholder*=\"검색\"]');if(!inp){R.error='검색 입력 필드 없음';R.ok=false;return JSON.stringify(R);}const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'가우스');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));await w(1500);const afterCount=document.querySelectorAll('table tbody tr').length;R.afterCount=afterCount;R.filtered=afterCount<=beforeCount;const rows=Array.from(document.querySelectorAll('table tbody tr'));const allContain=rows.every(r=>r.innerText?.includes('가우스'));R.allRowsContainKeyword=allContain;R.ok=afterCount>0;return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "SEARCH" + }, + { + "id": 10, + "name": "[회계관리 > 거래처관리] [SEARCH] 검색 결과 행 데이터 검증", + "action": "verify_text", + "target": "table tbody", + "text": "가우스" + }, + { + "id": 11, + "name": "[회계관리 > 거래처관리] [SEARCH] 검색 초기화", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SEARCH_RESET'};const inp=document.querySelector('input[placeholder*=\"검색\"]');if(inp){const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));}await w(1500);const c=document.querySelectorAll('table tbody tr').length;R.restoredCount=c;R.restored=c>=(window.__e2e_beforeSearch||1);R.ok=true;return JSON.stringify(R);})()", + "timeout": 10000, + "phase": "SEARCH" + }, + { + "id": 12, + "name": "[회계관리 > 거래처관리] [SEARCH-NEG] 존재하지 않는 키워드 검색", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SEARCH_NEGATIVE'};const inp=document.querySelector('input[placeholder*=\"검색\"]');if(!inp){R.error='검색 입력 필드 없음';R.ok=false;return JSON.stringify(R);}const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'ZZZZNONEXIST99');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));await w(1500);const rows=document.querySelectorAll('table tbody tr');const noData=document.body.innerText.includes('데이터가 없')||document.body.innerText.includes('결과가 없')||document.body.innerText.includes('데이터 없')||document.body.innerText.includes('No data');R.rowCount=rows.length;R.noDataMessage=noData;R.ok=(rows.length===0||noData||(rows.length===1&&rows[0].innerText?.includes('데이터')));R.info=(rows.length===0||noData)?'pass: 0 results confirmed':'warn: found '+rows.length+' rows';return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "SEARCH" + }, + { + "id": 13, + "name": "[회계관리 > 거래처관리] [SEARCH-NEG] 검색 초기화 복원", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SEARCH_RESTORE'};const inp=document.querySelector('input[placeholder*=\"검색\"]');if(inp){const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));}await w(1500);const c=document.querySelectorAll('table tbody tr').length;R.restoredCount=c;R.ok=c>0;return JSON.stringify(R);})()", + "timeout": 10000, + "phase": "SEARCH" + }, + { + "id": 14, + "name": "[회계관리 > 거래처관리] [FILTER] 구분 필터 매출 선택", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'FILTER_SALES'};const cbs=Array.from(document.querySelectorAll('button[role=\"combobox\"]')).filter(b=>b.offsetParent!==null);R.comboCount=cbs.length;if(!cbs[0]){R.warn='combobox not found';R.ok=true;return JSON.stringify(R);}cbs[0].click();await w(500);const opt=Array.from(document.querySelectorAll('[role=\"option\"]')).find(o=>o.innerText?.trim()==='매출');if(opt){opt.click();await w(1500);const rows=Array.from(document.querySelectorAll('table tbody tr'));R.rowCount=rows.length;const salesRows=rows.filter(r=>{const cells=r.querySelectorAll('td');return Array.from(cells).some(c=>c.innerText?.trim()==='매출');});R.salesRowCount=salesRows.length;R.allAreSales=salesRows.length===rows.length||rows.length===0;R.info=R.allAreSales?'모든 행이 매출 타입':'일부 비매출 행 존재 ('+salesRows.length+'/'+rows.length+')';}else{R.warn='매출 옵션 없음';}R.ok=true;return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "FILTER" + }, + { + "id": 15, + "name": "[회계관리 > 거래처관리] [FILTER+SEARCH] 매출 필터 + 가우스 검색 복합 테스트", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'FILTER_PLUS_SEARCH'};const inp=document.querySelector('input[placeholder*=\"검색\"]');if(inp){const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'가우스');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));}await w(1500);const rows=Array.from(document.querySelectorAll('table tbody tr'));R.rowCount=rows.length;const allMatch=rows.every(r=>{const text=r.innerText||'';return text.includes('가우스');});R.allContainKeyword=allMatch;const allSales=rows.every(r=>{const cells=r.querySelectorAll('td');return Array.from(cells).some(c=>c.innerText?.trim()==='매출');});R.allAreSalesType=allSales;R.ok=true;R.info='rows='+rows.length+', allKeyword='+allMatch+', allSales='+allSales;return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "FILTER" + }, + { + "id": 16, + "name": "[회계관리 > 거래처관리] [FILTER] 구분 필터 전체로 복원 + 검색 초기화", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'FILTER_RESET'};const inp=document.querySelector('input[placeholder*=\"검색\"]');if(inp){const nset=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;nset.call(inp,'');inp.dispatchEvent(new Event('input',{bubbles:true}));inp.dispatchEvent(new Event('change',{bubbles:true}));}await w(500);const cbs=Array.from(document.querySelectorAll('button[role=\"combobox\"]')).filter(b=>b.offsetParent!==null);if(cbs[0]){cbs[0].click();await w(500);const opt=Array.from(document.querySelectorAll('[role=\"option\"]')).find(o=>o.innerText?.trim()==='전체');if(opt){opt.click();await w(1000);}}await w(1000);R.rowCount=document.querySelectorAll('table tbody tr').length;R.ok=R.rowCount>0;return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "FILTER" + }, + { + "id": 17, + "name": "[회계관리 > 거래처관리] [DETAIL] 첫 행 셀 값 캡처", + "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');const captured={};const colNames=['checkbox','no','type','vendorName','purchasePayDay','salesPayDay','creditGrade','tradeGrade','receivable','badDebt'];cells.forEach((cell,i)=>{const key=colNames[i]||('col'+i);captured[key]=cell.innerText?.trim()||'';});window.__E2E_CAPTURED__=captured;R.captured=captured;R.ok=true;return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "CAPTURE" + }, + { + "id": 18, + "name": "[회계관리 > 거래처관리] [DETAIL] 첫 행 클릭 → 상세 진입", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'DETAIL_ENTER'};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": 19, + "name": "[회계관리 > 거래처관리] [DETAIL] 상세 페이지 로드 대기", + "action": "wait", + "timeout": 2000 + }, + { + "id": 20, + "name": "[회계관리 > 거래처관리] [DETAIL] URL 확인", + "action": "verify_url", + "target": "/accounting/vendors/", + "expected": "URL에 거래처 ID 포함" + }, + { + "id": 21, + "name": "[회계관리 > 거래처관리] [DETAIL] 전체 섹션 필드 수 검증 (6개 섹션)", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SECTION_VERIFY'};const pageText=document.body.innerText;const sectionChecks=[{name:'기본정보',keywords:['사업자등록번호','거래처코드','거래처명','대표자명']},{name:'연락처',keywords:['주소','전화번호','이메일']},{name:'담당자',keywords:['담당자','담당자명']},{name:'회사정보',keywords:['매입 결제일','매출 결제일']},{name:'신용/거래',keywords:['신용등급','거래등급','계좌']},{name:'추가정보',keywords:['미수금','악성채권']}];let foundSections=0;R.sections={};sectionChecks.forEach(sec=>{const fieldsFound=sec.keywords.filter(kw=>pageText.includes(kw));const pct=Math.round(fieldsFound.length/sec.keywords.length*100);R.sections[sec.name]={total:sec.keywords.length,found:fieldsFound.length,pct:pct+'%',fields:fieldsFound};if(fieldsFound.length>0)foundSections++;});R.foundSections=foundSections;R.totalSections=sectionChecks.length;R.ok=foundSections>=4;R.info=foundSections+'/'+sectionChecks.length+' 섹션 확인';return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "VERIFY" + }, + { + "id": 22, + "name": "[회계관리 > 거래처관리] [DETAIL] 상세 필드 1:1 대조 (목록 캡처 vs 상세)", + "action": "evaluate", + "script": "(async()=>{const R={phase:'DETAIL_MATCH'};const cap=window.__E2E_CAPTURED__||{};R.hasCaptured=Object.keys(cap).length>0;if(!R.hasCaptured){R.ok=true;R.info='캡처 데이터 없음 - 스킵';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=['vendorName','type','creditGrade','tradeGrade','receivable'];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);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": 23, + "name": "[회계관리 > 거래처관리] [DETAIL] 헤더 버튼 확인 (목록/수정/삭제)", + "action": "evaluate", + "script": "(()=>{const R={phase:'HEADER_BTNS'};const btns=Array.from(document.querySelectorAll('button')).filter(b=>b.offsetParent!==null);R.listBtn=btns.some(b=>b.innerText?.trim()==='목록');R.editBtn=btns.some(b=>b.innerText?.trim()==='수정');R.deleteBtn=btns.some(b=>b.innerText?.trim()==='삭제');R.ok=R.editBtn;return JSON.stringify(R);})()", + "timeout": 10000, + "phase": "VERIFY" + }, + { + "id": 24, + "name": "[회계관리 > 거래처관리] [EDIT] 수정 버튼 클릭", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'EDIT_ENTER'};const btn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='수정'&&b.offsetParent!==null);if(btn){btn.click();await w(2000);R.editUrl=location.pathname+location.search;R.isEditMode=location.search.includes('mode=edit');}else{R.warn='수정 버튼 없음';}R.ok=true;return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "UPDATE" + }, + { + "id": 25, + "name": "[회계관리 > 거래처관리] [EDIT] 수정 모드 URL 확인", + "action": "verify_url", + "target": "mode=edit", + "expected": "수정 모드 URL 정상" + }, + { + "id": 26, + "name": "[회계관리 > 거래처관리] [EDIT] 다중 필드 원본 값 캡처 + 수정", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 R={phase:'MULTI_EDIT'};const inputs=Array.from(document.querySelectorAll('input:not([type=\"hidden\"]):not([type=\"checkbox\"])')).filter(i=>i.offsetParent!==null&&!i.readOnly&&!i.disabled);R.editableCount=inputs.length;window.__E2E_ORIG_VALUES__={};let edited=0;for(const inp of inputs){const val=inp.value||'';const ph=inp.placeholder||'';if(val.includes('수정테스트'))continue;if(ph.includes('자동생성')||ph.includes('000-00'))continue;if(val.length>1&&edited===0){window.__E2E_ORIG_VALUES__['field0']={index:Array.from(inputs).indexOf(inp),value:val};sv(inp,val+' (수정테스트)');edited++;R.field0={orig:val,new:inp.value};await w(200);continue;}if(val.length>1&&edited===1){window.__E2E_ORIG_VALUES__['field1']={index:Array.from(inputs).indexOf(inp),value:val};sv(inp,val+' (E2E수정)');edited++;R.field1={orig:val,new:inp.value};await w(200);continue;}if(edited>=2)break;}R.editedFields=edited;R.ok=edited>0;return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "UPDATE" + }, + { + "id": 27, + "name": "[회계관리 > 거래처관리] [EDIT] 저장 클릭", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SAVE'};window.__e2e_urlBeforeSave=window.location.href;const saveBtn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='저장'&&b.offsetParent!==null);if(saveBtn){saveBtn.click();await w(3000);R.urlAfter=location.href;R.saved=true;}else{R.error='저장 버튼 없음';}R.ok=!!saveBtn;return JSON.stringify(R);})()", + "timeout": 20000, + "phase": "UPDATE" + }, + { + "id": 28, + "name": "[회계관리 > 거래처관리] [EDIT] 저장 완료 확인 (목록 복귀 + 에러 없음)", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SAVE_VERIFY'};let isListPage=false;for(let i=0;i<10;i++){const url=window.location.href;isListPage=url.includes('/accounting/vendors')&&!url.includes('mode=');if(isListPage)break;await w(500);}R.isListPage=isListPage;const hasError=document.body.innerText.includes('404')||document.body.innerText.includes('500')||document.body.innerText.includes('Not Found');R.hasError=hasError;R.ok=isListPage&&!hasError;R.info=R.ok?'PASS: 목록 복귀, 에러 없음':'FAIL: isListPage='+isListPage+', hasError='+hasError;return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "VERIFY" + }, + { + "id": 29, + "name": "[회계관리 > 거래처관리] [EDIT] 목록에서 수정 반영 확인", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(1000);const R={phase:'VERIFY_EDIT'};const found=document.body.innerText.includes('수정테스트');const foundE2E=document.body.innerText.includes('E2E수정');R.modifiedVisible=found;R.secondFieldVisible=foundE2E;R.rowCount=document.querySelectorAll('table tbody tr').length;R.ok=true;R.info=found?'PASS: 수정된 데이터 목록에 반영':'WARN: 수정 텍스트 미표시 (페이지네이션 또는 정렬 영향)';return JSON.stringify(R);})()", + "timeout": 10000, + "phase": "VERIFY" + }, + { + "id": 30, + "name": "[회계관리 > 거래처관리] [VERIFY-EDIT] 수정된 거래처 재진입하여 저장 검증", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'RE_ENTER_DETAIL'};const rows=Array.from(document.querySelectorAll('table tbody tr'));let target=rows.find(row=>row.innerText?.includes('수정테스트'));if(!target)target=rows[0];if(target){target.click();await w(2500);const pageText=document.body.innerText;R.has수정테스트=pageText.includes('수정테스트');R.hasE2E수정=pageText.includes('E2E수정');R.detailUrl=location.pathname+location.search;}R.ok=true;return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "VERIFY" + }, + { + "id": 31, + "name": "[회계관리 > 거래처관리] [RESTORE] 수정 모드 진입", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'RESTORE_EDIT'};const btn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='수정'&&b.offsetParent!==null);if(btn){btn.click();await w(1500);R.editUrl=location.pathname+location.search;}else{R.warn='수정 버튼 없음';}R.ok=true;return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "RESTORE" + }, + { + "id": 32, + "name": "[회계관리 > 거래처관리] [RESTORE] 원래 값 복원 (다중 필드)", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const sv=(el,v)=>{const ns=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'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 R={phase:'RESTORE_VALUES'};const inputs=Array.from(document.querySelectorAll('input:not([type=\"hidden\"]):not([type=\"checkbox\"])')).filter(i=>i.offsetParent!==null&&!i.readOnly&&!i.disabled);let restored=0;inputs.forEach(inp=>{if(inp.value.includes(' (수정테스트)')){const newVal=inp.value.replace(' (수정테스트)','');sv(inp,newVal);restored++;R.field0Restored=true;}if(inp.value.includes(' (E2E수정)')){const newVal=inp.value.replace(' (E2E수정)','');sv(inp,newVal);restored++;R.field1Restored=true;}});R.restoredCount=restored;R.ok=restored>0;R.info=restored>0?restored+' fields restored':'no fields with test markers found';return JSON.stringify(R);})()", + "timeout": 15000, + "phase": "RESTORE" + }, + { + "id": 33, + "name": "[회계관리 > 거래처관리] [RESTORE] 저장 클릭", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'RESTORE_SAVE'};const btn=Array.from(document.querySelectorAll('button')).find(b=>b.innerText?.trim()==='저장'&&b.offsetParent!==null);if(btn){btn.click();await w(3000);R.url=location.href;R.saved=true;}else{R.error='저장 버튼 없음';}R.ok=!!btn;return JSON.stringify(R);})()", + "timeout": 20000, + "phase": "RESTORE" + }, + { + "id": 34, + "name": "[회계관리 > 거래처관리] [RESTORE] 복원 완료 확인", + "action": "evaluate", + "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(1000);const R={phase:'RESTORE_VERIFY'};const url=window.location.href;R.isListPage=url.includes('/accounting/vendors')&&!url.includes('mode=');const still수정=document.body.innerText.includes('수정테스트');const stillE2E=document.body.innerText.includes('E2E수정');R.still수정=still수정;R.stillE2E=stillE2E;R.ok=R.isListPage&&!still수정;R.info=(!still수정&&!stillE2E)?'PASS: 원복 완료':'WARN: 원복 확인 필요 (수정테스트='+still수정+', E2E수정='+stillE2E+')';return JSON.stringify(R);})()", + "timeout": 10000, + "phase": "VERIFY" + }, + { + "id": 35, + "name": "[회계관리 > 거래처관리] [FINAL] 목록 페이지 최종 확인", + "action": "verify_url", + "target": "/accounting/vendors", + "expected": "거래처관리 목록 페이지 정상 표시" + }, + { + "id": 36, + "name": "[회계관리 > 거래처관리] [FINAL] API 요약 + 콘솔 에러 확인", + "action": "evaluate", + "script": "(()=>{const R={phase:'FINAL_SUMMARY'};const logs=(window.__E2E__?window.__E2E__.getApiLogs().logs:[]);R.apiSummary={total:logs.length,success:logs.filter(l=>l.ok||l.status<400).length,failed:logs.filter(l=>!l.ok&&l.status>=400).length,avgResponseTime:logs.length>0?Math.round(logs.reduce((s,l)=>s+(l.duration||0),0)/logs.length):0,slowCalls:logs.filter(l=>l.duration>2000).length,methods:{GET:logs.filter(l=>l.method==='GET').length,POST:logs.filter(l=>l.method==='POST').length,PUT:logs.filter(l=>l.method==='PUT'||l.method==='PATCH').length,DELETE:logs.filter(l=>l.method==='DELETE').length}};const errs=window.__CONSOLE_ERRORS__||[];R.consoleErrors=errs.length;R.errorSamples=errs.slice(0,5);R.ok=true;R.info='API calls: '+logs.length+', errors: '+errs.length;return JSON.stringify(R);})()", + "timeout": 10000, + "phase": "VERIFY" + } + ], + "requiredVerifications": [ + { "id": 1, "name": "등록/저장 버튼", "steps": [27, 28], "criteria": "저장 클릭 → 목록 리다이렉트 + 에러 없음 + 데이터 반영" }, + { "id": 3, "name": "검색/필터", "steps": [9, 10, 11, 12, 13, 14, 15, 16], "criteria": "검색/네거티브/필터/복합필터 시 데이터 변화 확인" }, + { "id": 5, "name": "목업 페이지 감지", "steps": [6], "criteria": "입력 필드, 동작 버튼, API 호출 확인" } + ], + "testData": { + "searchKeyword": "가우스", + "negativeKeyword": "ZZZZNONEXIST99", + "editSuffix": " (수정테스트)", + "editSuffix2": " (E2E수정)" + }, + "expectedAPIs": [ + { "method": "GET", "endpoint": "/api/v1/clients", "description": "거래처 목록 조회" }, + { "method": "GET", "endpoint": "/api/v1/clients/{id}", "description": "거래처 상세 조회" }, + { "method": "PUT", "endpoint": "/api/v1/clients/{id}", "description": "거래처 수정" } + ] } \ No newline at end of file