{ "id": "detail-roundtrip-hr-board", "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:'CAPTURE'};const rows=Array.from(document.querySelectorAll('table tbody tr')).filter(r=>r.offsetParent!==null);R.rowCount=rows.length;if(rows.length===0){R.warn='테이블에 데이터 없음 - 상세 조회 불가';R.ok=true;return JSON.stringify(R);}const testRow=rows.find(r=>r.innerText?.includes('E2E_TEST_'));const firstRow=testRow||rows[0];R.usedTestRow=!!testRow;const cells=Array.from(firstRow.querySelectorAll('td'));const cellTexts=cells.map(c=>c.innerText?.trim().substring(0,50)).filter(t=>t.length>0);R.firstRowTexts=cellTexts;const style=window.getComputedStyle(firstRow);R.hasCursor=style.cursor==='pointer';R.listUrl=window.location.href;window.__CAPTURED__={rowCount:rows.length,firstRowTexts:cellTexts,listUrl:window.location.href};R.ok=true;return JSON.stringify(R);})()", "timeout": 10000, "phase": "CAPTURE" }, { "id": 4, "name": "[인사관리 > 사원관리] 첫 행 클릭 → 상세 이동", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'CLICK_ROW'};const rows=Array.from(document.querySelectorAll('table tbody tr')).filter(r=>r.offsetParent!==null);if(rows.length===0){R.error='행 없음';R.ok=false;return JSON.stringify(R);}const testRow=rows.find(r=>r.innerText?.includes('E2E_TEST_'));const firstRow=testRow||rows[0];R.usedTestRow=!!testRow;const targetCell=firstRow.querySelector('td:nth-child(2)')||firstRow.querySelector('td');if(!targetCell){R.error='클릭할 셀 없음';R.ok=false;return JSON.stringify(R);}R.clickedText=targetCell.innerText?.trim().substring(0,30);R.urlBefore=window.location.href;targetCell.click();await w(500);if(window.location.href===R.urlBefore){ const link=firstRow.querySelector('a[href]'); if(link){link.click();await w(500);}}let waited=0;while(window.location.href===R.urlBefore&&waited<5000){await w(300);waited+=300;}R.urlAfter=window.location.href;R.urlChanged=R.urlAfter!==R.urlBefore;if(!R.urlChanged){R.warn='행 클릭 후 URL 변경 없음 - 모달 또는 인라인 상세일 수 있음';R.ok=true;return JSON.stringify(R);}R.ok=true;return JSON.stringify(R);})()", "timeout": 10000, "phase": "CLICK_ROW" }, { "id": 5, "name": "[인사관리 > 사원관리] 상세 페이지 데이터 검증", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VERIFY_DETAIL'};await w(1500);R.currentUrl=window.location.href;const idPattern=new RegExp('[/][0-9a-f]{8,}|[/][0-9]+[?]|[/][0-9]+$');R.hasIdInUrl=idPattern.test(R.currentUrl);R.hasViewMode=R.currentUrl.includes('mode=view')||R.currentUrl.includes('mode=edit');const captured=window.__CAPTURED__;R.hasCapturedData=!!captured;if(captured&&captured.firstRowTexts){ const pageText=document.body.innerText; let matchCount=0; const checks=[]; for(const t of captured.firstRowTexts){ if(t.length>=2){ const found=pageText.includes(t); if(found)matchCount++; checks.push({text:t.substring(0,20),found}); } } R.dataChecks=checks.slice(0,5); R.matchCount=matchCount; R.totalChecked=checks.length; R.dataMatch=matchCount>0;}const tabs=document.querySelectorAll('[role=\"tab\"],[role=\"tablist\"] button,button[data-state]');R.tabCount=tabs.length;R.tabLabels=Array.from(tabs).slice(0,5).map(t=>t.innerText?.trim().substring(0,20));const inputs=document.querySelectorAll('input:not([type=\"hidden\"]),textarea,select');R.inputCount=inputs.length;R.ok=R.dataMatch!==false;if(!R.ok)R.error='상세 페이지에서 목록 데이터 불일치';return JSON.stringify(R);})()", "timeout": 10000, "phase": "VERIFY_DETAIL" }, { "id": 6, "name": "[인사관리 > 사원관리] 목록으로 복귀", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'GO_BACK'};R.detailUrl=window.location.href;const captured=window.__CAPTURED__;const listUrl=captured?.listUrl||'';const listBtn=Array.from(document.querySelectorAll('button,a')).find(b=>{ const txt=b.innerText?.trim()||''; return /목록|리스트|뒤로|Back|List/i.test(txt)&&b.offsetParent!==null;});if(listBtn){ R.method='목록 버튼 클릭'; listBtn.click(); await w(2000);}else{ R.method='history.back()'; window.history.back(); await w(2000);}R.returnedUrl=window.location.href;R.urlMatches=listUrl?R.returnedUrl===listUrl:!R.returnedUrl.includes('mode=view');if(!R.urlMatches&&listUrl){ R.info='목록 URL과 다름: expected='+listUrl.substring(listUrl.length-40)+' actual='+R.returnedUrl.substring(R.returnedUrl.length-40);}R.ok=R.urlMatches!==false;if(!R.ok)R.error='목록 URL 복귀 실패';return JSON.stringify(R);})()", "timeout": 10000, "phase": "GO_BACK" }, { "id": 7, "name": "[인사관리 > 사원관리] 목록 무결성 확인", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(1500);const R={phase:'LIST_INTACT'};const captured=window.__CAPTURED__;if(!captured){R.warn='캡처 데이터 없음';R.ok=true;return JSON.stringify(R);}const rows=Array.from(document.querySelectorAll('table tbody tr')).filter(r=>r.offsetParent!==null);R.currentRowCount=rows.length;R.originalRowCount=captured.rowCount;R.rowCountMatch=R.currentRowCount===R.originalRowCount;if(rows.length>0){ const cells=Array.from(rows[0].querySelectorAll('td')); const cellTexts=cells.map(c=>c.innerText?.trim().substring(0,50)).filter(t=>t.length>0); R.currentFirstRow=cellTexts.slice(0,3); R.originalFirstRow=(captured.firstRowTexts||[]).slice(0,3); let textMatch=0; for(const t of R.originalFirstRow){ if(cellTexts.some(c=>c.includes(t)||t.includes(c)))textMatch++; } R.firstRowMatch=textMatch>0;}else{ R.firstRowMatch=false; R.warn='목록 복귀 후 행 없음';}R.intact=R.rowCountMatch&&R.firstRowMatch;if(!R.intact&&!R.rowCountMatch)R.info='행 수 변경: '+R.originalRowCount+'→'+R.currentRowCount;R.ok=R.intact!==false;return JSON.stringify(R);})()", "timeout": 10000, "phase": "LIST_INTACT" }, { "id": 8, "name": "[게시판 > 자유게시판] 메뉴 이동", "action": "menu_navigate", "level1": "게시판", "level2": "자유게시판", "timeout": 10000 }, { "id": 9, "name": "[게시판 > 자유게시판] 페이지 로드 대기", "action": "wait", "timeout": 3000 }, { "id": 10, "name": "[게시판 > 자유게시판] 테이블 로드 대기", "action": "wait_for_table", "timeout": 5000 }, { "id": 11, "name": "[게시판 > 자유게시판] 테이블 상태 캡처", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'CAPTURE'};const rows=Array.from(document.querySelectorAll('table tbody tr')).filter(r=>r.offsetParent!==null);R.rowCount=rows.length;if(rows.length===0){R.warn='테이블에 데이터 없음 - 상세 조회 불가';R.ok=true;return JSON.stringify(R);}const testRow=rows.find(r=>r.innerText?.includes('E2E_TEST_'));const firstRow=testRow||rows[0];R.usedTestRow=!!testRow;const cells=Array.from(firstRow.querySelectorAll('td'));const cellTexts=cells.map(c=>c.innerText?.trim().substring(0,50)).filter(t=>t.length>0);R.firstRowTexts=cellTexts;const style=window.getComputedStyle(firstRow);R.hasCursor=style.cursor==='pointer';R.listUrl=window.location.href;window.__CAPTURED__={rowCount:rows.length,firstRowTexts:cellTexts,listUrl:window.location.href};R.ok=true;return JSON.stringify(R);})()", "timeout": 10000, "phase": "CAPTURE" }, { "id": 12, "name": "[게시판 > 자유게시판] 첫 행 클릭 → 상세 이동", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'CLICK_ROW'};const rows=Array.from(document.querySelectorAll('table tbody tr')).filter(r=>r.offsetParent!==null);if(rows.length===0){R.error='행 없음';R.ok=false;return JSON.stringify(R);}const testRow=rows.find(r=>r.innerText?.includes('E2E_TEST_'));const firstRow=testRow||rows[0];R.usedTestRow=!!testRow;const targetCell=firstRow.querySelector('td:nth-child(2)')||firstRow.querySelector('td');if(!targetCell){R.error='클릭할 셀 없음';R.ok=false;return JSON.stringify(R);}R.clickedText=targetCell.innerText?.trim().substring(0,30);R.urlBefore=window.location.href;targetCell.click();await w(500);if(window.location.href===R.urlBefore){ const link=firstRow.querySelector('a[href]'); if(link){link.click();await w(500);}}let waited=0;while(window.location.href===R.urlBefore&&waited<5000){await w(300);waited+=300;}R.urlAfter=window.location.href;R.urlChanged=R.urlAfter!==R.urlBefore;if(!R.urlChanged){R.warn='행 클릭 후 URL 변경 없음 - 모달 또는 인라인 상세일 수 있음';R.ok=true;return JSON.stringify(R);}R.ok=true;return JSON.stringify(R);})()", "timeout": 10000, "phase": "CLICK_ROW" }, { "id": 13, "name": "[게시판 > 자유게시판] 상세 페이지 데이터 검증", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VERIFY_DETAIL'};await w(1500);R.currentUrl=window.location.href;const idPattern=new RegExp('[/][0-9a-f]{8,}|[/][0-9]+[?]|[/][0-9]+$');R.hasIdInUrl=idPattern.test(R.currentUrl);R.hasViewMode=R.currentUrl.includes('mode=view')||R.currentUrl.includes('mode=edit');const captured=window.__CAPTURED__;R.hasCapturedData=!!captured;if(captured&&captured.firstRowTexts){ const pageText=document.body.innerText; let matchCount=0; const checks=[]; for(const t of captured.firstRowTexts){ if(t.length>=2){ const found=pageText.includes(t); if(found)matchCount++; checks.push({text:t.substring(0,20),found}); } } R.dataChecks=checks.slice(0,5); R.matchCount=matchCount; R.totalChecked=checks.length; R.dataMatch=matchCount>0;}const tabs=document.querySelectorAll('[role=\"tab\"],[role=\"tablist\"] button,button[data-state]');R.tabCount=tabs.length;R.tabLabels=Array.from(tabs).slice(0,5).map(t=>t.innerText?.trim().substring(0,20));const inputs=document.querySelectorAll('input:not([type=\"hidden\"]),textarea,select');R.inputCount=inputs.length;R.ok=R.dataMatch!==false;if(!R.ok)R.error='상세 페이지에서 목록 데이터 불일치';return JSON.stringify(R);})()", "timeout": 10000, "phase": "VERIFY_DETAIL" }, { "id": 14, "name": "[게시판 > 자유게시판] 목록으로 복귀", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'GO_BACK'};R.detailUrl=window.location.href;const captured=window.__CAPTURED__;const listUrl=captured?.listUrl||'';const listBtn=Array.from(document.querySelectorAll('button,a')).find(b=>{ const txt=b.innerText?.trim()||''; return /목록|리스트|뒤로|Back|List/i.test(txt)&&b.offsetParent!==null;});if(listBtn){ R.method='목록 버튼 클릭'; listBtn.click(); await w(2000);}else{ R.method='history.back()'; window.history.back(); await w(2000);}R.returnedUrl=window.location.href;R.urlMatches=listUrl?R.returnedUrl===listUrl:!R.returnedUrl.includes('mode=view');if(!R.urlMatches&&listUrl){ R.info='목록 URL과 다름: expected='+listUrl.substring(listUrl.length-40)+' actual='+R.returnedUrl.substring(R.returnedUrl.length-40);}R.ok=R.urlMatches!==false;if(!R.ok)R.error='목록 URL 복귀 실패';return JSON.stringify(R);})()", "timeout": 10000, "phase": "GO_BACK" }, { "id": 15, "name": "[게시판 > 자유게시판] 목록 무결성 확인", "action": "evaluate", "script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(1500);const R={phase:'LIST_INTACT'};const captured=window.__CAPTURED__;if(!captured){R.warn='캡처 데이터 없음';R.ok=true;return JSON.stringify(R);}const rows=Array.from(document.querySelectorAll('table tbody tr')).filter(r=>r.offsetParent!==null);R.currentRowCount=rows.length;R.originalRowCount=captured.rowCount;R.rowCountMatch=R.currentRowCount===R.originalRowCount;if(rows.length>0){ const cells=Array.from(rows[0].querySelectorAll('td')); const cellTexts=cells.map(c=>c.innerText?.trim().substring(0,50)).filter(t=>t.length>0); R.currentFirstRow=cellTexts.slice(0,3); R.originalFirstRow=(captured.firstRowTexts||[]).slice(0,3); let textMatch=0; for(const t of R.originalFirstRow){ if(cellTexts.some(c=>c.includes(t)||t.includes(c)))textMatch++; } R.firstRowMatch=textMatch>0;}else{ R.firstRowMatch=false; R.warn='목록 복귀 후 행 없음';}R.intact=R.rowCountMatch&&R.firstRowMatch;if(!R.intact&&!R.rowCountMatch)R.info='행 수 변경: '+R.originalRowCount+'→'+R.currentRowCount;R.ok=R.intact!==false;return JSON.stringify(R);})()", "timeout": 10000, "phase": "LIST_INTACT" } ] }