{ "id": "pagination-sort-hr", "name": "페이지네이션 & 정렬 검증: 인사/게시판", "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 w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'ROW_COUNT'};const rows=document.querySelectorAll('table tbody tr');R.rowCount=rows.length;const pageInfo=document.body.innerText.match(new RegExp('(\\\\d+)\\\\s*[-~]\\\\s*(\\\\d+)\\\\s*(of|중|개|건|/|총)\\\\s*(\\\\d+)','i'));if(pageInfo){R.pageInfoText=pageInfo[0];R.totalItems=parseInt(pageInfo[4]);}const totalBadge=Array.from(document.querySelectorAll('[class*=\"badge\"],[class*=\"count\"],[class*=\"total\"]')).find(e=>/\\d+/.test(e.innerText));if(totalBadge)R.totalBadgeText=totalBadge.innerText?.trim();const emptyRows=Array.from(rows).filter(r=>r.innerText?.trim().length===0);R.emptyRowCount=emptyRows.length;if(emptyRows.length>0)R.warn='⚠️ 빈 행 '+emptyRows.length+'개 발견 (렌더링 버그 의심)';const rowTexts=Array.from(rows).map(r=>r.innerText?.trim().substring(0,80));const duplicates=rowTexts.filter((t,i)=>rowTexts.indexOf(t)!==i);R.duplicateCount=duplicates.length;if(duplicates.length>0)R.warn=(R.warn||'')+' ⚠️ 중복 행 '+duplicates.length+'개 발견';R.ok=true;return JSON.stringify(R);})()", "timeout": 10000, "phase": "VERIFY" }, { "id": 4, "name": "[인사관리 > 사원관리] 컬럼 정렬 검증", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SORT'};const headers=Array.from(document.querySelectorAll('table thead th,table thead td,[role=\"columnheader\"]'));R.headerCount=headers.length;R.headerTexts=headers.map(h=>h.innerText?.trim()).filter(t=>t).slice(0,10);const sortableHeaders=headers.filter(h=>{ const t=h.innerText?.trim()||''; return t.length>0&&!h.querySelector('input[type=\"checkbox\"]')&&h.offsetParent!==null;});R.sortableCount=sortableHeaders.length;if(sortableHeaders.length===0){R.warn='정렬 가능한 헤더 없음';R.ok=true;return JSON.stringify(R);}const getFirstColValues=()=>Array.from(document.querySelectorAll('table tbody tr')).slice(0,5).map(r=>{const cells=r.querySelectorAll('td');return(cells[1]||cells[0])?.innerText?.trim().substring(0,30)||'';});R.beforeSort=getFirstColValues();const targetHeader=sortableHeaders.find(h=>h.innerText?.trim().length>1)||sortableHeaders[0];R.sortColumn=targetHeader.innerText?.trim();targetHeader.click();await w(1500);R.afterSort1=getFirstColValues();R.sortChanged1=JSON.stringify(R.beforeSort)!==JSON.stringify(R.afterSort1);targetHeader.click();await w(1500);R.afterSort2=getFirstColValues();R.sortChanged2=JSON.stringify(R.afterSort1)!==JSON.stringify(R.afterSort2);if(!R.sortChanged1&&!R.sortChanged2)R.warn='⚠️ 컬럼 클릭 후 정렬 변화 없음 (정렬 미구현 의심)';else if(R.sortChanged1&&!R.sortChanged2)R.warn='⚠️ 역순 정렬 미동작 (한방향만 정렬)';R.sortWorked=R.sortChanged1||R.sortChanged2;R.ok=R.sortWorked!==false;return JSON.stringify(R);})()", "timeout": 15000, "phase": "SORT" }, { "id": 5, "name": "[인사관리 > 사원관리] 페이지네이션 검증", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'PAGINATION'};const rows1=Array.from(document.querySelectorAll('table tbody tr'));R.page1RowCount=rows1.length;R.page1FirstRow=rows1[0]?.innerText?.substring(0,60)||'';R.page1LastRow=rows1[rows1.length-1]?.innerText?.substring(0,60)||'';const paginationBtns=Array.from(document.querySelectorAll('button,a,[role=\"button\"]')).filter(b=>{ const t=b.innerText?.trim()||'';const al=b.getAttribute('aria-label')||''; return(/^[2-9]$|^\\d{2,}$/.test(t)||/next|다음|chevron.?right|›|»|>/.test(t+al+b.className))&&b.offsetParent!==null;});R.paginationBtnCount=paginationBtns.length;const navPagination=document.querySelector('nav[aria-label*=\"pagination\"],nav[aria-label*=\"page\"]');if(navPagination){ const navBtns=Array.from(navPagination.querySelectorAll('button,a')).filter(b=>b.offsetParent!==null); R.navPaginationBtns=navBtns.length;}let nextBtn=paginationBtns.find(b=>{const t=(b.innerText?.trim()||'')+(b.getAttribute('aria-label')||'');return/next|다음|›|»|chevron.?right/i.test(t+b.className);});if(!nextBtn)nextBtn=paginationBtns.find(b=>b.innerText?.trim()==='2');if(!nextBtn&&navPagination){nextBtn=Array.from(navPagination.querySelectorAll('button,a')).find(b=>/next|다음|›|»/i.test((b.innerText||'')+(b.getAttribute('aria-label')||'')+b.className)&&b.offsetParent!==null);}R.hasNextBtn=!!nextBtn;if(!nextBtn){R.warn='페이지네이션 버튼 없음 (데이터 부족 또는 미구현)';R.ok=true;return JSON.stringify(R);}nextBtn.click();await w(2000);const rows2=Array.from(document.querySelectorAll('table tbody tr'));R.page2RowCount=rows2.length;R.page2FirstRow=rows2[0]?.innerText?.substring(0,60)||'';R.dataChanged=R.page1FirstRow!==R.page2FirstRow;R.hasRows=R.page2RowCount>0;if(!R.dataChanged&&R.hasRows)R.warn='⚠️ 페이지 변경 후 동일 데이터 표시 (페이지네이션 미동작 의심)';if(!R.hasRows)R.warn='⚠️ 2페이지에 데이터 없음';const prevBtn=Array.from(document.querySelectorAll('button,a,[role=\"button\"]')).find(b=>{const t=(b.innerText?.trim()||'')+(b.getAttribute('aria-label')||'');return(/prev|이전|‹|«|chevron.?left/i.test(t+b.className)||b.innerText?.trim()==='1')&&b.offsetParent!==null;});if(!prevBtn&&navPagination){const pb=Array.from(navPagination.querySelectorAll('button,a')).find(b=>/prev|이전|‹|«/i.test((b.innerText||'')+(b.getAttribute('aria-label')||'')+b.className)&&b.offsetParent!==null);if(pb)pb.click();}else if(prevBtn){prevBtn.click();}await w(1500);const rows3=Array.from(document.querySelectorAll('table tbody tr'));R.backToPage1=rows3[0]?.innerText?.substring(0,60)===R.page1FirstRow;if(!R.backToPage1)R.warn=(R.warn||'')+' ⚠️ 1페이지 복귀 후 데이터 불일치';R.paginationWorked=R.dataChanged&&R.hasRows;R.ok=R.paginationWorked!==false;return JSON.stringify(R);})()", "timeout": 20000, "phase": "PAGINATION" }, { "id": 6, "name": "[게시판 > 자유게시판] 메뉴 이동", "action": "menu_navigate", "level1": "게시판", "level2": "자유게시판", "timeout": 10000 }, { "id": 7, "name": "[게시판 > 자유게시판] 페이지 로드 대기", "action": "wait", "timeout": 3000 }, { "id": 8, "name": "[게시판 > 자유게시판] 테이블 로드 대기", "action": "wait_for_table", "timeout": 5000 }, { "id": 9, "name": "[게시판 > 자유게시판] 행 수/중복 검증", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'ROW_COUNT'};const rows=document.querySelectorAll('table tbody tr');R.rowCount=rows.length;const pageInfo=document.body.innerText.match(new RegExp('(\\\\d+)\\\\s*[-~]\\\\s*(\\\\d+)\\\\s*(of|중|개|건|/|총)\\\\s*(\\\\d+)','i'));if(pageInfo){R.pageInfoText=pageInfo[0];R.totalItems=parseInt(pageInfo[4]);}const totalBadge=Array.from(document.querySelectorAll('[class*=\"badge\"],[class*=\"count\"],[class*=\"total\"]')).find(e=>/\\d+/.test(e.innerText));if(totalBadge)R.totalBadgeText=totalBadge.innerText?.trim();const emptyRows=Array.from(rows).filter(r=>r.innerText?.trim().length===0);R.emptyRowCount=emptyRows.length;if(emptyRows.length>0)R.warn='⚠️ 빈 행 '+emptyRows.length+'개 발견 (렌더링 버그 의심)';const rowTexts=Array.from(rows).map(r=>r.innerText?.trim().substring(0,80));const duplicates=rowTexts.filter((t,i)=>rowTexts.indexOf(t)!==i);R.duplicateCount=duplicates.length;if(duplicates.length>0)R.warn=(R.warn||'')+' ⚠️ 중복 행 '+duplicates.length+'개 발견';R.ok=true;return JSON.stringify(R);})()", "timeout": 10000, "phase": "VERIFY" }, { "id": 10, "name": "[게시판 > 자유게시판] 컬럼 정렬 검증", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'SORT'};const headers=Array.from(document.querySelectorAll('table thead th,table thead td,[role=\"columnheader\"]'));R.headerCount=headers.length;R.headerTexts=headers.map(h=>h.innerText?.trim()).filter(t=>t).slice(0,10);const sortableHeaders=headers.filter(h=>{ const t=h.innerText?.trim()||''; return t.length>0&&!h.querySelector('input[type=\"checkbox\"]')&&h.offsetParent!==null;});R.sortableCount=sortableHeaders.length;if(sortableHeaders.length===0){R.warn='정렬 가능한 헤더 없음';R.ok=true;return JSON.stringify(R);}const getFirstColValues=()=>Array.from(document.querySelectorAll('table tbody tr')).slice(0,5).map(r=>{const cells=r.querySelectorAll('td');return(cells[1]||cells[0])?.innerText?.trim().substring(0,30)||'';});R.beforeSort=getFirstColValues();const targetHeader=sortableHeaders.find(h=>h.innerText?.trim().length>1)||sortableHeaders[0];R.sortColumn=targetHeader.innerText?.trim();targetHeader.click();await w(1500);R.afterSort1=getFirstColValues();R.sortChanged1=JSON.stringify(R.beforeSort)!==JSON.stringify(R.afterSort1);targetHeader.click();await w(1500);R.afterSort2=getFirstColValues();R.sortChanged2=JSON.stringify(R.afterSort1)!==JSON.stringify(R.afterSort2);if(!R.sortChanged1&&!R.sortChanged2)R.warn='⚠️ 컬럼 클릭 후 정렬 변화 없음 (정렬 미구현 의심)';else if(R.sortChanged1&&!R.sortChanged2)R.warn='⚠️ 역순 정렬 미동작 (한방향만 정렬)';R.sortWorked=R.sortChanged1||R.sortChanged2;R.ok=R.sortWorked!==false;return JSON.stringify(R);})()", "timeout": 15000, "phase": "SORT" }, { "id": 11, "name": "[게시판 > 자유게시판] 페이지네이션 검증", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'PAGINATION'};const rows1=Array.from(document.querySelectorAll('table tbody tr'));R.page1RowCount=rows1.length;R.page1FirstRow=rows1[0]?.innerText?.substring(0,60)||'';R.page1LastRow=rows1[rows1.length-1]?.innerText?.substring(0,60)||'';const paginationBtns=Array.from(document.querySelectorAll('button,a,[role=\"button\"]')).filter(b=>{ const t=b.innerText?.trim()||'';const al=b.getAttribute('aria-label')||''; return(/^[2-9]$|^\\d{2,}$/.test(t)||/next|다음|chevron.?right|›|»|>/.test(t+al+b.className))&&b.offsetParent!==null;});R.paginationBtnCount=paginationBtns.length;const navPagination=document.querySelector('nav[aria-label*=\"pagination\"],nav[aria-label*=\"page\"]');if(navPagination){ const navBtns=Array.from(navPagination.querySelectorAll('button,a')).filter(b=>b.offsetParent!==null); R.navPaginationBtns=navBtns.length;}let nextBtn=paginationBtns.find(b=>{const t=(b.innerText?.trim()||'')+(b.getAttribute('aria-label')||'');return/next|다음|›|»|chevron.?right/i.test(t+b.className);});if(!nextBtn)nextBtn=paginationBtns.find(b=>b.innerText?.trim()==='2');if(!nextBtn&&navPagination){nextBtn=Array.from(navPagination.querySelectorAll('button,a')).find(b=>/next|다음|›|»/i.test((b.innerText||'')+(b.getAttribute('aria-label')||'')+b.className)&&b.offsetParent!==null);}R.hasNextBtn=!!nextBtn;if(!nextBtn){R.warn='페이지네이션 버튼 없음 (데이터 부족 또는 미구현)';R.ok=true;return JSON.stringify(R);}nextBtn.click();await w(2000);const rows2=Array.from(document.querySelectorAll('table tbody tr'));R.page2RowCount=rows2.length;R.page2FirstRow=rows2[0]?.innerText?.substring(0,60)||'';R.dataChanged=R.page1FirstRow!==R.page2FirstRow;R.hasRows=R.page2RowCount>0;if(!R.dataChanged&&R.hasRows)R.warn='⚠️ 페이지 변경 후 동일 데이터 표시 (페이지네이션 미동작 의심)';if(!R.hasRows)R.warn='⚠️ 2페이지에 데이터 없음';const prevBtn=Array.from(document.querySelectorAll('button,a,[role=\"button\"]')).find(b=>{const t=(b.innerText?.trim()||'')+(b.getAttribute('aria-label')||'');return(/prev|이전|‹|«|chevron.?left/i.test(t+b.className)||b.innerText?.trim()==='1')&&b.offsetParent!==null;});if(!prevBtn&&navPagination){const pb=Array.from(navPagination.querySelectorAll('button,a')).find(b=>/prev|이전|‹|«/i.test((b.innerText||'')+(b.getAttribute('aria-label')||'')+b.className)&&b.offsetParent!==null);if(pb)pb.click();}else if(prevBtn){prevBtn.click();}await w(1500);const rows3=Array.from(document.querySelectorAll('table tbody tr'));R.backToPage1=rows3[0]?.innerText?.substring(0,60)===R.page1FirstRow;if(!R.backToPage1)R.warn=(R.warn||'')+' ⚠️ 1페이지 복귀 후 데이터 불일치';R.paginationWorked=R.dataChanged&&R.hasRows;R.ok=R.paginationWorked!==false;return JSON.stringify(R);})()", "timeout": 20000, "phase": "PAGINATION" } ] }