Files
sam-scenarios/pagination-sort-hr.json
김보곤 21b272702d refactor: 44개 시나리오 품질 개선 (false positive 제거 + flaky 수정 + E2E_TEST_ 표준화)
Phase 1 - False Positive 제거 (36개):
- R.ok=true 무조건 반환 → 조건부 검증으로 교체
- 영향: edge-*, form-validation-*, pagination-sort-*, search-*, reload-persist-*,
  batch-create-*, detail-roundtrip-*, workflow-*, cross-module-*

Phase 2 - Flaky rows[0] 패턴 수정 (7개):
- detail-verify-acc-sales.json: CAPTURE/READ 스텝 E2E_TEST_ 타겟팅
- vendor-management.json: 행 클릭 E2E_TEST_ 타겟팅
- batch-update-account-sales.json: CAPTURE/SELECT/VERIFY/RESTORE 스텝
- sales-management.json: DELETE fallback 경고 로깅

Phase 3 - E2E_TEST_ 접두사 표준화 (1개):
- employee-register.json: 홍길동→E2E_TEST_사원, EMP2026001→E2E_TEST_EMP001

테스트 결과: 175 PASS / 9 FAIL (숨겨진 실제 버그 5건 노출)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:55:15 +09:00

98 lines
13 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"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"
}
]
}