2026-03-03 20:48:19 +09:00
|
|
|
|
{
|
|
|
|
|
|
"id": "hr-salary-long-term-care",
|
|
|
|
|
|
"name": "급여 장기요양보험 필드 검증 테스트",
|
|
|
|
|
|
"version": "1.0.0",
|
|
|
|
|
|
"enabled": true,
|
|
|
|
|
|
"screenshotPolicy": {
|
|
|
|
|
|
"captureOnFail": true,
|
|
|
|
|
|
"captureOnPass": false
|
|
|
|
|
|
},
|
|
|
|
|
|
"description": "인사관리 > 급여관리의 장기요양보험 필드 존재 확인 + 자동계산(건강보험×12.81%) 검증",
|
|
|
|
|
|
"baseUrl": "https://dev.codebridge-x.com",
|
|
|
|
|
|
"menuNavigation": {
|
|
|
|
|
|
"level1": "인사관리",
|
|
|
|
|
|
"level2": "급여관리",
|
|
|
|
|
|
"expectedUrl": "/hr/salary-management",
|
|
|
|
|
|
"searchWithinParent": true,
|
|
|
|
|
|
"closeOtherMenus": true
|
|
|
|
|
|
},
|
|
|
|
|
|
"auth": { "username": "TestUser5", "password": "password123!" },
|
|
|
|
|
|
"steps": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 1,
|
|
|
|
|
|
"name": "메뉴 진입: 인사관리 > 급여관리",
|
|
|
|
|
|
"action": "menu_navigate",
|
|
|
|
|
|
"level1": "인사관리",
|
|
|
|
|
|
"level2": "급여관리",
|
|
|
|
|
|
"expected": { "url_contains": "/hr/salary" }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 2,
|
|
|
|
|
|
"name": "페이지 로드 대기",
|
|
|
|
|
|
"action": "wait",
|
|
|
|
|
|
"timeout": 3000
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 3,
|
|
|
|
|
|
"name": "URL 검증",
|
|
|
|
|
|
"action": "verify_url",
|
|
|
|
|
|
"expected": { "url_contains": "/hr/salary-management" }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 4,
|
|
|
|
|
|
"name": "목업 감지",
|
|
|
|
|
|
"action": "verify_not_mockup",
|
|
|
|
|
|
"checks": ["급여 목록 표시", "등록 버튼 존재"]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 5,
|
|
|
|
|
|
"name": "테이블 로드 대기",
|
|
|
|
|
|
"action": "wait_for_table",
|
|
|
|
|
|
"timeout": 15000
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 6,
|
|
|
|
|
|
"name": "[READ] 첫 번째 행 클릭 (상세 다이얼로그 열기)",
|
|
|
|
|
|
"phase": "READ",
|
|
|
|
|
|
"action": "click_first_row"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 7,
|
|
|
|
|
|
"name": "[READ] 상세 다이얼로그 대기",
|
|
|
|
|
|
"phase": "READ",
|
|
|
|
|
|
"action": "wait",
|
|
|
|
|
|
"timeout": 2000
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 8,
|
|
|
|
|
|
"name": "[READ] 장기요양보험 필드 존재 확인 (상세)",
|
|
|
|
|
|
"phase": "READ",
|
|
|
|
|
|
"action": "evaluate",
|
|
|
|
|
|
"script": "(()=>{const R={phase:'DETAIL_LTC_CHECK'};const modal=document.querySelector('[role=\"dialog\"],[aria-modal=\"true\"],[class*=\"Dialog\"]');const scope=modal||document;const bodyText=scope.innerText||'';R.hasLongTermCare=bodyText.includes('장기요양')||bodyText.includes('장기요양보험');const labels=Array.from(scope.querySelectorAll('label,dt,[class*=\"label\"],[class*=\"Label\"]'));const ltcLabel=labels.find(l=>l.innerText?.includes('장기요양'));R.labelFound=!!ltcLabel;if(ltcLabel){const container=ltcLabel.closest('[class*=\"field\"],[class*=\"Field\"],[class*=\"form-item\"],[class*=\"row\"]');if(container){const valueEl=container.querySelector('input,span,dd,[class*=\"value\"],[class*=\"Value\"]');R.value=valueEl?.value||valueEl?.innerText?.trim()||'N/A';}}R.ok=R.hasLongTermCare||R.labelFound;R.info=R.ok?'pass: 장기요양보험 필드 발견':'fail: 장기요양보험 필드 미발견';return JSON.stringify(R);})()"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 9,
|
|
|
|
|
|
"name": "[READ] 건강보험/장기요양 값 비교 (자동계산 검증)",
|
|
|
|
|
|
"phase": "READ",
|
|
|
|
|
|
"action": "evaluate",
|
|
|
|
|
|
"script": "(()=>{const R={phase:'CALC_VERIFY'};const modal=document.querySelector('[role=\"dialog\"],[aria-modal=\"true\"],[class*=\"Dialog\"]');const scope=modal||document;const labels=Array.from(scope.querySelectorAll('label,dt,[class*=\"label\"],[class*=\"Label\"]'));const getVal=keyword=>{const lbl=labels.find(l=>l.innerText?.includes(keyword));if(!lbl)return null;const container=lbl.closest('[class*=\"field\"],[class*=\"Field\"],[class*=\"form-item\"],[class*=\"row\"]');if(!container)return null;const valEl=container.querySelector('input,span,dd,[class*=\"value\"]');const raw=valEl?.value||valEl?.innerText?.trim()||'';return parseInt(raw.replace(/[^0-9]/g,''))||null;};R.healthInsurance=getVal('건강보험');R.longTermCare=getVal('장기요양');if(R.healthInsurance&&R.longTermCare){const expected=Math.round(R.healthInsurance*0.1281);R.expectedLTC=expected;R.tolerance=Math.abs(R.longTermCare-expected);R.isCorrect=R.tolerance<=10;R.info=R.isCorrect?'pass: 장기요양='+R.longTermCare+' (건강보험 '+R.healthInsurance+'×12.81%='+expected+')':'warn: 장기요양='+R.longTermCare+' vs 예상='+expected+' (차이='+R.tolerance+')';}else{R.info='warn: 건강보험('+R.healthInsurance+') 또는 장기요양('+R.longTermCare+') 값 미확인';}R.ok=true;return JSON.stringify(R);})()"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 10,
|
|
|
|
|
|
"name": "[READ] 상세 다이얼로그 닫기",
|
|
|
|
|
|
"phase": "READ",
|
|
|
|
|
|
"action": "close_modal_if_open"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 11,
|
|
|
|
|
|
"name": "[CREATE] 등록 다이얼로그 열기",
|
|
|
|
|
|
"phase": "CREATE",
|
|
|
|
|
|
"action": "evaluate",
|
|
|
|
|
|
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(500);const R={phase:'CREATE_OPEN'};const btn=Array.from(document.querySelectorAll('button')).find(b=>/급여.*등록|등록|추가/.test(b.innerText?.trim())&&b.offsetParent!==null&&!b.disabled);if(btn){btn.click();await w(2000);R.clicked=true;}else{R.clicked=false;R.info='등록 버튼 미발견';}R.ok=true;return JSON.stringify(R);})()",
|
|
|
|
|
|
"timeout": 10000
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 12,
|
|
|
|
|
|
"name": "[CREATE] 등록 다이얼로그 대기",
|
|
|
|
|
|
"phase": "CREATE",
|
|
|
|
|
|
"action": "evaluate",
|
|
|
|
|
|
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));await w(2000);const dlg=document.querySelector('[role=\"dialog\"]');const isVis=dlg&&dlg.getBoundingClientRect().width>0;return JSON.stringify({ok:true,info:isVis?'pass: dialog open (position:fixed)':'warn: dialog not visible',dialogFound:!!dlg,visible:isVis});})()",
|
|
|
|
|
|
"timeout": 5000
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 13,
|
|
|
|
|
|
"name": "[CREATE] 등록 폼에서 장기요양보험 필드 확인",
|
|
|
|
|
|
"phase": "CREATE",
|
|
|
|
|
|
"action": "evaluate",
|
|
|
|
|
|
"script": "(()=>{const R={phase:'CREATE_LTC_CHECK'};const modal=document.querySelector('[role=\"dialog\"],[aria-modal=\"true\"],[class*=\"Dialog\"]');const scope=modal||document;const bodyText=scope.innerText||'';R.hasLongTermCare=bodyText.includes('장기요양')||bodyText.includes('장기요양보험');const labels=Array.from(scope.querySelectorAll('label'));const ltcLabel=labels.find(l=>l.innerText?.includes('장기요양'));R.labelFound=!!ltcLabel;if(ltcLabel){const container=ltcLabel.closest('[class*=\"field\"],[class*=\"Field\"],[class*=\"form-item\"],[class*=\"row\"]');if(container){const input=container.querySelector('input');R.hasInput=!!input;R.inputReadOnly=input?.readOnly||false;R.inputValue=input?.value||'';}}R.ok=R.hasLongTermCare||R.labelFound;R.info=R.ok?'pass: 등록 폼에 장기요양보험 필드 존재'+(R.inputReadOnly?' (자동계산, readOnly)':' (입력 가능)'):'fail: 등록 폼에 장기요양보험 필드 미발견';return JSON.stringify(R);})()"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 14,
|
|
|
|
|
|
"name": "[CREATE] 건강보험 입력 → 장기요양 자동계산 검증",
|
|
|
|
|
|
"phase": "CREATE",
|
|
|
|
|
|
"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:'AUTO_CALC'};const modal=document.querySelector('[role=\"dialog\"],[aria-modal=\"true\"],[class*=\"Dialog\"]');const scope=modal||document;const labels=Array.from(scope.querySelectorAll('label'));const healthLabel=labels.find(l=>l.innerText?.includes('건강보험')&&!l.innerText?.includes('장기'));if(!healthLabel){R.info='건강보험 입력 필드 미발견';R.ok=true;return JSON.stringify(R);}const healthContainer=healthLabel.closest('[class*=\"field\"],[class*=\"Field\"],[class*=\"form-item\"],[class*=\"row\"]');const healthInput=healthContainer?.querySelector('input');if(!healthInput||healthInput.readOnly||healthInput.disabled){R.info='건강보험 입력 불가 (readOnly)';R.ok=true;return JSON.stringify(R);}sv(healthInput,'100000');await w(800);const ltcLabel=labels.find(l=>l.innerText?.includes('장기요양'));if(ltcLabel){const ltcContainer=ltcLabel.closest('[class*=\"field\"],[class*=\"Field\"],[class*=\"form-item\"],[class*=\"row\"]');const ltcEl=ltcContainer?.querySelector('input,span,[class*=\"value\"]');const ltcVal=ltcEl?.value||ltcEl?.innerText?.trim()||'';const numVal=parseInt(ltcVal.replace(/[^0-9]/g,''))||0;R.healthInput=100000;R.longTermCareOutput=numVal;R.expected=Math.round(100000*0.1281);R.isAutoCalculated=Math.abs(numVal-R.expected)<=10;R.info=R.isAutoCalculated?'pass: 건강보험 100,000 → 장기요양 '+numVal+' (예상 '+R.expected+')':'warn: 장기요양='+numVal+' vs 예상='+R.expected;}else{R.info='장기요양 필드 미발견';}R.ok=true;return JSON.stringify(R);})()",
|
|
|
|
|
|
"timeout": 10000
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 15,
|
|
|
|
|
|
"name": "[CREATE] 등록 다이얼로그 닫기 (데이터 저장 안함)",
|
|
|
|
|
|
"phase": "CREATE",
|
|
|
|
|
|
"action": "evaluate",
|
|
|
|
|
|
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const modal=document.querySelector('[role=\"dialog\"],[aria-modal=\"true\"],[class*=\"Dialog\"]');if(modal&&modal.offsetParent!==null){const cancelBtn=Array.from(modal.querySelectorAll('button')).find(b=>/취소|닫기|Close/.test(b.innerText?.trim()));if(cancelBtn){cancelBtn.click();await w(500);}else{document.dispatchEvent(new KeyboardEvent('keydown',{key:'Escape',bubbles:true}));await w(500);}}return 'pass: dialog closed';})()"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": 16,
|
|
|
|
|
|
"name": "[SUMMARY] API 호출 통계",
|
|
|
|
|
|
"action": "evaluate",
|
2026-03-04 11:42:23 +09:00
|
|
|
|
"script": "(()=>{const logs=(window.__E2E__?window.__E2E__.getApiLogs().logs:[]);const salaryApi=logs.filter(l=>l.url.includes('salary')||l.url.includes('payroll'));return 'pass: API summary - total='+logs.length+' salary_api='+salaryApi.length+' success='+logs.filter(l=>l.status>=200&&l.status<300).length+' failed='+logs.filter(l=>l.status>=400).length;})()"
|
2026-03-03 20:48:19 +09:00
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"expectedAPIs": [
|
|
|
|
|
|
{ "method": "GET", "endpoint": "/api/v1/payrolls", "description": "급여 목록 조회" },
|
|
|
|
|
|
{ "method": "GET", "endpoint": "/api/v1/payrolls/{id}", "description": "급여 상세 조회" }
|
|
|
|
|
|
],
|
|
|
|
|
|
"rollbackPlan": {
|
|
|
|
|
|
"note": "조회 위주 테스트, 등록 다이얼로그는 취소로 닫음. 데이터 변경 없음."
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|