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>
This commit is contained in:
김보곤
2026-02-19 21:55:15 +09:00
parent 1e60fa41a4
commit 21b272702d
44 changed files with 170 additions and 243 deletions

View File

@@ -43,7 +43,7 @@
"id": 5,
"name": "[판매관리 > 거래처관리] 빈 폼 제출 유효성 감사",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VALIDATION_AUDIT'};const beforeToasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"]').length;const beforeErrors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"]').length;const submitBtn=Array.from(document.querySelectorAll('button')).find(b=>{ const t=b.innerText?.trim()||''; return(/등록|저장|확인|제출/.test(t))&&b.offsetParent!==null&&!b.disabled;});if(!submitBtn){R.err='등록/저장 버튼 없음';R.ok=true;return JSON.stringify(R);}R.submitBtnText=submitBtn.innerText?.trim();submitBtn.click();await w(2000);const toasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"],[class*=\"Toaster\"] [data-content]');R.toastCount=toasts.length;R.newToasts=toasts.length-beforeToasts;if(toasts.length>0){R.toastTexts=Array.from(toasts).map(t=>t.innerText?.trim().substring(0,80)).filter(t=>t);}const errors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"],[class*=\"invalid\"]');R.errorCount=errors.length;R.newErrors=errors.length-beforeErrors;if(errors.length>0){R.errorTexts=Array.from(errors).slice(0,5).map(e=>e.innerText?.trim().substring(0,60)).filter(t=>t);}const invalidFields=document.querySelectorAll('[aria-invalid=\"true\"]');R.ariaInvalidCount=invalidFields.length;const redBorders=Array.from(document.querySelectorAll('input,textarea,select,[role=\"combobox\"]')).filter(el=>{ const cs=getComputedStyle(el); return cs.borderColor?.includes('rgb(239')||cs.borderColor?.includes('rgb(220')||cs.borderColor?.includes('rgb(248')||cs.outlineColor?.includes('rgb(239');});R.redBorderCount=redBorders.length;const dialogs=document.querySelectorAll('[role=\"alertdialog\"],[role=\"dialog\"]');const validationDialog=Array.from(dialogs).find(d=>d.offsetParent!==null);R.hasValidationDialog=!!validationDialog;if(validationDialog){R.dialogText=validationDialog.innerText?.trim().substring(0,100);}R.urlAfterSubmit=location.pathname+location.search;R.urlChanged=R.urlAfterSubmit!==location.pathname+location.search;R.totalValidationSignals=R.newToasts+R.newErrors+R.ariaInvalidCount+R.redBorderCount+(R.hasValidationDialog?1:0);R.hasValidation=R.totalValidationSignals>0;if(!R.hasValidation)R.warn='유효성 검증 미감지 - 빈 폼 제출 시 에러 메시지 없음';R.ok=true;return JSON.stringify(R);})()",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VALIDATION_AUDIT'};const beforeToasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"]').length;const beforeErrors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"]').length;const submitBtn=Array.from(document.querySelectorAll('button')).find(b=>{ const t=b.innerText?.trim()||''; return(/등록|저장|확인|제출/.test(t))&&b.offsetParent!==null&&!b.disabled;});if(!submitBtn){R.err='등록/저장 버튼 없음';R.ok=true;return JSON.stringify(R);}R.submitBtnText=submitBtn.innerText?.trim();submitBtn.click();await w(2000);const toasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"],[class*=\"Toaster\"] [data-content]');R.toastCount=toasts.length;R.newToasts=toasts.length-beforeToasts;if(toasts.length>0){R.toastTexts=Array.from(toasts).map(t=>t.innerText?.trim().substring(0,80)).filter(t=>t);}const errors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"],[class*=\"invalid\"]');R.errorCount=errors.length;R.newErrors=errors.length-beforeErrors;if(errors.length>0){R.errorTexts=Array.from(errors).slice(0,5).map(e=>e.innerText?.trim().substring(0,60)).filter(t=>t);}const invalidFields=document.querySelectorAll('[aria-invalid=\"true\"]');R.ariaInvalidCount=invalidFields.length;const redBorders=Array.from(document.querySelectorAll('input,textarea,select,[role=\"combobox\"]')).filter(el=>{ const cs=getComputedStyle(el); return cs.borderColor?.includes('rgb(239')||cs.borderColor?.includes('rgb(220')||cs.borderColor?.includes('rgb(248')||cs.outlineColor?.includes('rgb(239');});R.redBorderCount=redBorders.length;const dialogs=document.querySelectorAll('[role=\"alertdialog\"],[role=\"dialog\"]');const validationDialog=Array.from(dialogs).find(d=>d.offsetParent!==null);R.hasValidationDialog=!!validationDialog;if(validationDialog){R.dialogText=validationDialog.innerText?.trim().substring(0,100);}R.urlAfterSubmit=location.pathname+location.search;R.urlChanged=R.urlAfterSubmit!==location.pathname+location.search;R.totalValidationSignals=R.newToasts+R.newErrors+R.ariaInvalidCount+R.redBorderCount+(R.hasValidationDialog?1:0);R.hasValidation=R.totalValidationSignals>0;if(!R.hasValidation)R.warn='유효성 검증 미감지 - 빈 폼 제출 시 에러 메시지 없음';R.ok=R.hasValidation;return JSON.stringify(R);})()",
"timeout": 15000,
"phase": "VALIDATE"
},
@@ -91,7 +91,7 @@
"id": 12,
"name": "[판매관리 > 수주관리] 빈 폼 제출 유효성 감사",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VALIDATION_AUDIT'};const beforeToasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"]').length;const beforeErrors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"]').length;const submitBtn=Array.from(document.querySelectorAll('button')).find(b=>{ const t=b.innerText?.trim()||''; return(/등록|저장|확인|제출/.test(t))&&b.offsetParent!==null&&!b.disabled;});if(!submitBtn){R.err='등록/저장 버튼 없음';R.ok=true;return JSON.stringify(R);}R.submitBtnText=submitBtn.innerText?.trim();submitBtn.click();await w(2000);const toasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"],[class*=\"Toaster\"] [data-content]');R.toastCount=toasts.length;R.newToasts=toasts.length-beforeToasts;if(toasts.length>0){R.toastTexts=Array.from(toasts).map(t=>t.innerText?.trim().substring(0,80)).filter(t=>t);}const errors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"],[class*=\"invalid\"]');R.errorCount=errors.length;R.newErrors=errors.length-beforeErrors;if(errors.length>0){R.errorTexts=Array.from(errors).slice(0,5).map(e=>e.innerText?.trim().substring(0,60)).filter(t=>t);}const invalidFields=document.querySelectorAll('[aria-invalid=\"true\"]');R.ariaInvalidCount=invalidFields.length;const redBorders=Array.from(document.querySelectorAll('input,textarea,select,[role=\"combobox\"]')).filter(el=>{ const cs=getComputedStyle(el); return cs.borderColor?.includes('rgb(239')||cs.borderColor?.includes('rgb(220')||cs.borderColor?.includes('rgb(248')||cs.outlineColor?.includes('rgb(239');});R.redBorderCount=redBorders.length;const dialogs=document.querySelectorAll('[role=\"alertdialog\"],[role=\"dialog\"]');const validationDialog=Array.from(dialogs).find(d=>d.offsetParent!==null);R.hasValidationDialog=!!validationDialog;if(validationDialog){R.dialogText=validationDialog.innerText?.trim().substring(0,100);}R.urlAfterSubmit=location.pathname+location.search;R.urlChanged=R.urlAfterSubmit!==location.pathname+location.search;R.totalValidationSignals=R.newToasts+R.newErrors+R.ariaInvalidCount+R.redBorderCount+(R.hasValidationDialog?1:0);R.hasValidation=R.totalValidationSignals>0;if(!R.hasValidation)R.warn='유효성 검증 미감지 - 빈 폼 제출 시 에러 메시지 없음';R.ok=true;return JSON.stringify(R);})()",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VALIDATION_AUDIT'};const beforeToasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"]').length;const beforeErrors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"]').length;const submitBtn=Array.from(document.querySelectorAll('button')).find(b=>{ const t=b.innerText?.trim()||''; return(/등록|저장|확인|제출/.test(t))&&b.offsetParent!==null&&!b.disabled;});if(!submitBtn){R.err='등록/저장 버튼 없음';R.ok=true;return JSON.stringify(R);}R.submitBtnText=submitBtn.innerText?.trim();submitBtn.click();await w(2000);const toasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"],[class*=\"Toaster\"] [data-content]');R.toastCount=toasts.length;R.newToasts=toasts.length-beforeToasts;if(toasts.length>0){R.toastTexts=Array.from(toasts).map(t=>t.innerText?.trim().substring(0,80)).filter(t=>t);}const errors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"],[class*=\"invalid\"]');R.errorCount=errors.length;R.newErrors=errors.length-beforeErrors;if(errors.length>0){R.errorTexts=Array.from(errors).slice(0,5).map(e=>e.innerText?.trim().substring(0,60)).filter(t=>t);}const invalidFields=document.querySelectorAll('[aria-invalid=\"true\"]');R.ariaInvalidCount=invalidFields.length;const redBorders=Array.from(document.querySelectorAll('input,textarea,select,[role=\"combobox\"]')).filter(el=>{ const cs=getComputedStyle(el); return cs.borderColor?.includes('rgb(239')||cs.borderColor?.includes('rgb(220')||cs.borderColor?.includes('rgb(248')||cs.outlineColor?.includes('rgb(239');});R.redBorderCount=redBorders.length;const dialogs=document.querySelectorAll('[role=\"alertdialog\"],[role=\"dialog\"]');const validationDialog=Array.from(dialogs).find(d=>d.offsetParent!==null);R.hasValidationDialog=!!validationDialog;if(validationDialog){R.dialogText=validationDialog.innerText?.trim().substring(0,100);}R.urlAfterSubmit=location.pathname+location.search;R.urlChanged=R.urlAfterSubmit!==location.pathname+location.search;R.totalValidationSignals=R.newToasts+R.newErrors+R.ariaInvalidCount+R.redBorderCount+(R.hasValidationDialog?1:0);R.hasValidation=R.totalValidationSignals>0;if(!R.hasValidation)R.warn='유효성 검증 미감지 - 빈 폼 제출 시 에러 메시지 없음';R.ok=R.hasValidation;return JSON.stringify(R);})()",
"timeout": 15000,
"phase": "VALIDATE"
},
@@ -139,7 +139,7 @@
"id": 19,
"name": "[판매관리 > 견적관리] 빈 폼 제출 유효성 감사",
"action": "evaluate",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VALIDATION_AUDIT'};const beforeToasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"]').length;const beforeErrors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"]').length;const submitBtn=Array.from(document.querySelectorAll('button')).find(b=>{ const t=b.innerText?.trim()||''; return(/등록|저장|확인|제출/.test(t))&&b.offsetParent!==null&&!b.disabled;});if(!submitBtn){R.err='등록/저장 버튼 없음';R.ok=true;return JSON.stringify(R);}R.submitBtnText=submitBtn.innerText?.trim();submitBtn.click();await w(2000);const toasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"],[class*=\"Toaster\"] [data-content]');R.toastCount=toasts.length;R.newToasts=toasts.length-beforeToasts;if(toasts.length>0){R.toastTexts=Array.from(toasts).map(t=>t.innerText?.trim().substring(0,80)).filter(t=>t);}const errors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"],[class*=\"invalid\"]');R.errorCount=errors.length;R.newErrors=errors.length-beforeErrors;if(errors.length>0){R.errorTexts=Array.from(errors).slice(0,5).map(e=>e.innerText?.trim().substring(0,60)).filter(t=>t);}const invalidFields=document.querySelectorAll('[aria-invalid=\"true\"]');R.ariaInvalidCount=invalidFields.length;const redBorders=Array.from(document.querySelectorAll('input,textarea,select,[role=\"combobox\"]')).filter(el=>{ const cs=getComputedStyle(el); return cs.borderColor?.includes('rgb(239')||cs.borderColor?.includes('rgb(220')||cs.borderColor?.includes('rgb(248')||cs.outlineColor?.includes('rgb(239');});R.redBorderCount=redBorders.length;const dialogs=document.querySelectorAll('[role=\"alertdialog\"],[role=\"dialog\"]');const validationDialog=Array.from(dialogs).find(d=>d.offsetParent!==null);R.hasValidationDialog=!!validationDialog;if(validationDialog){R.dialogText=validationDialog.innerText?.trim().substring(0,100);}R.urlAfterSubmit=location.pathname+location.search;R.urlChanged=R.urlAfterSubmit!==location.pathname+location.search;R.totalValidationSignals=R.newToasts+R.newErrors+R.ariaInvalidCount+R.redBorderCount+(R.hasValidationDialog?1:0);R.hasValidation=R.totalValidationSignals>0;if(!R.hasValidation)R.warn='유효성 검증 미감지 - 빈 폼 제출 시 에러 메시지 없음';R.ok=true;return JSON.stringify(R);})()",
"script": "(async()=>{const w=ms=>new Promise(r=>setTimeout(r,ms));const R={phase:'VALIDATION_AUDIT'};const beforeToasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"]').length;const beforeErrors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"]').length;const submitBtn=Array.from(document.querySelectorAll('button')).find(b=>{ const t=b.innerText?.trim()||''; return(/등록|저장|확인|제출/.test(t))&&b.offsetParent!==null&&!b.disabled;});if(!submitBtn){R.err='등록/저장 버튼 없음';R.ok=true;return JSON.stringify(R);}R.submitBtnText=submitBtn.innerText?.trim();submitBtn.click();await w(2000);const toasts=document.querySelectorAll('[data-sonner-toast],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"],[class*=\"Toaster\"] [data-content]');R.toastCount=toasts.length;R.newToasts=toasts.length-beforeToasts;if(toasts.length>0){R.toastTexts=Array.from(toasts).map(t=>t.innerText?.trim().substring(0,80)).filter(t=>t);}const errors=document.querySelectorAll('[class*=\"error\"],[class*=\"Error\"],[class*=\"destructive\"],[role=\"alert\"],[class*=\"invalid\"]');R.errorCount=errors.length;R.newErrors=errors.length-beforeErrors;if(errors.length>0){R.errorTexts=Array.from(errors).slice(0,5).map(e=>e.innerText?.trim().substring(0,60)).filter(t=>t);}const invalidFields=document.querySelectorAll('[aria-invalid=\"true\"]');R.ariaInvalidCount=invalidFields.length;const redBorders=Array.from(document.querySelectorAll('input,textarea,select,[role=\"combobox\"]')).filter(el=>{ const cs=getComputedStyle(el); return cs.borderColor?.includes('rgb(239')||cs.borderColor?.includes('rgb(220')||cs.borderColor?.includes('rgb(248')||cs.outlineColor?.includes('rgb(239');});R.redBorderCount=redBorders.length;const dialogs=document.querySelectorAll('[role=\"alertdialog\"],[role=\"dialog\"]');const validationDialog=Array.from(dialogs).find(d=>d.offsetParent!==null);R.hasValidationDialog=!!validationDialog;if(validationDialog){R.dialogText=validationDialog.innerText?.trim().substring(0,100);}R.urlAfterSubmit=location.pathname+location.search;R.urlChanged=R.urlAfterSubmit!==location.pathname+location.search;R.totalValidationSignals=R.newToasts+R.newErrors+R.ariaInvalidCount+R.redBorderCount+(R.hasValidationDialog?1:0);R.hasValidation=R.totalValidationSignals>0;if(!R.hasValidation)R.warn='유효성 검증 미감지 - 빈 폼 제출 시 에러 메시지 없음';R.ok=R.hasValidation;return JSON.stringify(R);})()",
"timeout": 15000,
"phase": "VALIDATE"
},