"script":"(async()=>{const R={test:'react_onChange_trigger'};const si=['input[type=\"search\"]','input[placeholder*=\"검색\"]','input[role=\"searchbox\"]','[class*=\"search\"] input'];let el=null;for(const s of si){el=document.querySelector(s);if(el)break;}if(!el)return JSON.stringify({error:'검색 입력란 없음'});const nativeSetter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value')?.set;if(nativeSetter)nativeSetter.call(el,'');el.dispatchEvent(new Event('input',{bubbles:true}));await new Promise(r=>setTimeout(r,1000));const rowsReset=document.querySelectorAll('table tbody tr').length;R.rowsAfterClear=rowsReset;if(nativeSetter)nativeSetter.call(el,'zzz_no_match_react');const reactKey=Object.keys(el).find(k=>k.startsWith('__reactProps$'));if(reactKey&&el[reactKey]?.onChange){el[reactKey].onChange({target:el,currentTarget:el});R.reactOnChange=true;}else{el.dispatchEvent(new Event('input',{bubbles:true}));R.reactOnChange=false;}await new Promise(r=>setTimeout(r,2000));const rowsAfter=document.querySelectorAll('table tbody tr').length;R.rowsAfter=rowsAfter;R.filtered=rowsReset!==rowsAfter;R.verdict=R.filtered?'PASS: React onChange 검색 동작':'FAIL: React onChange 후에도 행 수 불변 ('+rowsReset+'→'+rowsAfter+')';return JSON.stringify(R)})()"
},
{
"id":7,
"name":"[테스트5] API 호출 모니터링 + 검색 재시도",
"action":"evaluate",
"script":"(async()=>{const R={test:'api_monitoring'};const captured=[];const origFetch=window.fetch;window.fetch=async function(...args){const url=typeof args[0]==='string'?args[0]:args[0]?.url||'';const method=args[1]?.method||'GET';captured.push({url:url.substring(0,100),method,time:Date.now()});return origFetch.apply(this,args);};const si=['input[type=\"search\"]','input[placeholder*=\"검색\"]','input[role=\"searchbox\"]','[class*=\"search\"] input'];let el=null;for(const s of si){el=document.querySelector(s);if(el)break;}if(!el){window.fetch=origFetch;return JSON.stringify({error:'검색 입력란 없음'});}const nativeSetter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value')?.set;if(nativeSetter)nativeSetter.call(el,'');el.dispatchEvent(new Event('input',{bubbles:true}));await new Promise(r=>setTimeout(r,500));captured.length=0;if(nativeSetter)nativeSetter.call(el,'api_test_query');el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));el.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',code:'Enter',keyCode:13,bubbles:true}));await new Promise(r=>setTimeout(r,3000));window.fetch=origFetch;R.apiCallsAfterSearch=captured.filter(c=>!c.url.includes('hot-update')&&!c.url.includes('sockjs'));R.apiCallCount=R.apiCallsAfterSearch.length;R.searchRelatedApis=R.apiCallsAfterSearch.filter(c=>c.url.includes('salary')||c.url.includes('search')||c.url.includes('query')||c.url.includes('keyword'));R.verdict=R.searchRelatedApis.length>0?'API 호출 감지됨':'FAIL: 검색 관련 API 호출 없음';return JSON.stringify(R)})()"
},
{
"id":8,
"name":"[검색 초기화] 입력란 비우기",
"action":"evaluate",
"script":"(async()=>{const si=['input[type=\"search\"]','input[placeholder*=\"검색\"]','input[role=\"searchbox\"]','[class*=\"search\"] input'];let el=null;for(const s of si){el=document.querySelector(s);if(el)break;}if(!el)return 'no search input';const nativeSetter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value')?.set;if(nativeSetter)nativeSetter.call(el,'');else el.value='';const reactKey=Object.keys(el).find(k=>k.startsWith('__reactProps$'));if(reactKey&&el[reactKey]?.onChange){el[reactKey].onChange({target:el,currentTarget:el});}el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));await new Promise(r=>setTimeout(r,1500));return 'cleared, rows: '+document.querySelectorAll('table tbody tr').length})()"
},
{
"id":9,
"name":"[테스트6] 실존 데이터로 검색 테스트",
"action":"evaluate",
"script":"(async()=>{const R={test:'real_data_search'};const rows=document.querySelectorAll('table tbody tr');R.totalRows=rows.length;if(rows.length===0)return JSON.stringify({error:'테이블 행 없음'});const firstRowCells=Array.from(rows[0].querySelectorAll('td'));R.firstRowData=firstRowCells.map(c=>c.innerText?.trim().substring(0,30));let searchTerm='';for(const cell of firstRowCells){const t=cell.innerText?.trim();if(t&&t.length>=2&&!/^\\d+$/.test(t)&&!t.includes('원')&&!t.includes(',')&&!t.includes('/')){searchTerm=t.substring(0,4);break;}}R.searchTerm=searchTerm;if(!searchTerm)return JSON.stringify({...R,error:'검색어 추출 실패'});const si=['input[type=\"search\"]','input[placeholder*=\"검색\"]','input[role=\"searchbox\"]','[class*=\"search\"] input'];let el=null;for(const s of si){el=document.querySelector(s);if(el)break;}if(!el)return JSON.stringify({error:'검색 입력란 없음'});const nativeSetter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value')?.set;if(nativeSetter)nativeSetter.call(el,searchTerm);const reactKey=Object.keys(el).find(k=>k.startsWith('__reactProps$'));if(reactKey&&el[reactKey]?.onChange){el[reactKey].onChange({target:el,currentTarget:el});}el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));el.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',code:'Enter',keyCode:13,bubbles:true}));await new Promise(r=>setTimeout(r,2000));const rowsAfter=document.querySelectorAll('table tbody tr').length;R.rowsAfter=rowsAfter;R.allMatched=Array.from(document.querySelectorAll('table tbody tr')).every(r=>r.innerText?.includes(searchTerm));R.verdict=R.totalRows!==rowsAfter?'PASS: 실존 데이터 검색 동작 ('+R.totalRows+'→'+rowsAfter+')':'FAIL: 실존 데이터 검색에도 행 수 불변 ('+R.totalRows+'→'+rowsAfter+')';return JSON.stringify(R)})()"
"script":"(async()=>{const si=['input[type=\"search\"]','input[placeholder*=\"검색\"]','input[role=\"searchbox\"]','[class*=\"search\"] input'];let el=null;for(const s of si){el=document.querySelector(s);if(el)break;}const R={page:'급여관리',url:location.href,searchInputExists:!!el,searchPlaceholder:el?.placeholder||'none',totalRows:document.querySelectorAll('table tbody tr').length,headers:Array.from(document.querySelectorAll('table thead th')).map(h=>h.innerText?.trim().substring(0,15)),filters:document.querySelectorAll('select,[role=\"combobox\"]').length};if(!el){R.conclusion='검색 입력란 없음. 검색 UI 미구현.';return JSON.stringify(R);}const nativeSetter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value')?.set;if(nativeSetter)nativeSetter.call(el,'');else el.value='';el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));await new Promise(r=>setTimeout(r,1000));const resetRows=document.querySelectorAll('table tbody tr').length;if(nativeSetter)nativeSetter.call(el,'zzz_conclusion_test');else el.value='zzz_conclusion_test';const rk=Object.keys(el).find(k=>k.startsWith('__reactProps$'));if(rk&&el[rk]?.onChange){el[rk].onChange({target:el,currentTarget:el});}el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));await new Promise(r=>setTimeout(r,2000));const afterInput=document.querySelectorAll('table tbody tr').length;R.inputTest={before:resetRows,after:afterInput,filtered:resetRows!==afterInput};if(R.inputTest.filtered){R.conclusion='검색 기능 정상 동작. 실시간 입력 필터링 방식 (검색 버튼 불필요). 입력 시 즉시 테이블 행 필터링 발생 ('+resetRows+'→'+afterInput+').';R.searchType='realtime_input';if(nativeSetter)nativeSetter.call(el,'');else el.value='';if(rk&&el[rk]?.onChange)el[rk].onChange({target:el,currentTarget:el});el.dispatchEvent(new Event('input',{bubbles:true}));return JSON.stringify(R);}el.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',code:'Enter',keyCode:13,bubbles:true}));await new Promise(r=>setTimeout(r,2000));const afterEnter=document.querySelectorAll('table tbody tr').length;R.enterTest={before:resetRows,after:afterEnter,filtered:resetRows!==afterEnter};if(R.enterTest.filtered){R.conclusion='검색 기능 정상 동작. Enter 키 검색 방식 (검색 버튼 불필요). Enter 입력 시 테이블 행 필터링 발생 ('+resetRows+'→'+afterEnter+').';R.searchType='enter_key';if(nativeSetter)nativeSetter.call(el,'');else el.value='';el.dispatchEvent(new Event('input',{bubbles:true}));return JSON.stringify(R);}const btns=Array.from(document.querySelectorAll('button'));const searchBtn=btns.find(b=>['검색','조회','Search'].some(t=>b.innerText?.trim()===t));R.searchButtonExists=!!searchBtn;R.conclusion='급여관리 페이지에 검색 입력란(placeholder: '+R.searchPlaceholder+')이 존재하지만, input/change/Enter/React onChange 어떤 방식으로도 테이블 행 필터링이 발생하지 않음 ('+resetRows+'→'+afterEnter+'). 검색 기능 미동작 또는 프론트엔드 클라이언트 필터링 미구현.';R.searchType='not_working';if(nativeSetter)nativeSetter.call(el,'');else el.value='';el.dispatchEvent(new Event('input',{bubbles:true}));return JSON.stringify(R)})()"