와이즈스톤
경력근거: 스크립트 오류 원인 분석 및 수정, 반복 실행 안정성 관리
공고의 주요업무에는 'Playwright 기반 UI 자동화 스크립트 개발' 과 '스크립트 오류 원인 분석 및 수정, 반복 실행 안정성 관리', '실행 결과 리포트 작성 및 이력 관리' 가 한 묶음으로 적혀 있고, 그 앞에는 '고객사 환경 내 솔루션 설치 및 실행 환경 구성' 이 있습니다. 고객사 현장에 설치한 솔루션이 매일 돌면 FDE 가 아침마다 받는 것이 실패 목록인데, 그중 무엇이 고객사 개발팀에 넘길 결함이고 무엇이 와이즈스톤 쪽 스크립트나 설치 환경 문제인지 가르는 일이 이 세 업무가 만나는 자리입니다. 이 CLI 는 그 갈림을 실행 이력으로 계산하고, 결과를 리포트와 이력으로 남깁니다. 자격요건의 '테스트 시나리오 설계 및 결함 분석 경험자' 와 우대사항의 '이커머스 등 웹, 앱 서비스 도메인 QA·테스트 경험자 우대' 는 쿠폰·장바구니·주문 목록 케이스로 꾸민 데모 쇼핑몰로 받습니다. 이 포트폴리오는 테스트 실행 기록을 규칙으로 판정해 리포트로 바꾸는 기술을 보여줍니다. 와이즈스톤 솔루션 내부 구조나 고객사 서비스 도메인에 대한 전문성은 주장하지 않습니다.
주요업무에 'Playwright 기반 UI 자동화 스크립트 개발' 과 '스크립트 오류 원인 분석 및 수정, 반복 실행 안정성 관리' 가 있습니다. 같은 실행 안에서 재시도로 통과한 케이스를 불안정한 스크립트로 따로 세우는 판정이 그 관리의 첫 단계입니다.
'실행 결과 리포트 작성 및 이력 관리' 와 '결함 재현·원인 분석 및 개선 요구사항 도출' 이 함께 적혀 있습니다. 판정을 마크다운 리포트로 남기고 결함 의심에 재현 절차 초안을 붙이는 출력이 이 두 줄에 닿습니다.
'고객사 환경 내 솔루션 설치 및 실행 환경 구성' 과 '실서비스 적용 후 정상 동작 검증 및 현장 이슈 대응' 이 있습니다. 실행 단위로 환경 탓 실패를 먼저 걸러내는 판정과 3주차 Docker 설치 문서가 이 업무를 받습니다.
와이즈스톤 FDE 엔지니어가 고객사 현장에 설치한 테스트 자동화 솔루션의 Playwright 실행 결과를 받아, 이번 빌드의 실패 케이스를 결함 의심·불안정한 스크립트·요소를 못 찾음·실행 환경 문제로 나눠 주는 CLI 입니다. 실행 목록과 시도별 결과를 읽어 직전 빌드를 시각 순서로 찾고, 실행마다 실패 비율과 오류 종류로 환경 탓인 실행을 먼저 걸러낸 뒤, 남은 시도에서 같은 실행 안 결과 뒤바뀜과 오류 종류로 케이스를 판정합니다. 판정은 터미널에 갈래별로 찍히고 같은 내용이 고객사 QA 담당자에게 넘길 마크다운 리포트로 남습니다.
고객사 현장에 테스트 자동화 솔루션을 설치하고 Playwright 스크립트를 매일 돌리는 와이즈스톤 FDE 엔지니어 — 실행 결과 리포트를 고객사 QA 담당자에게 넘기기 전에 자기 터미널에서 실패를 추린다
Playwright 리포트에는 실패와 재시도, 오류 메시지가 이미 다 남지만 실패 목록 그대로는 결함과 스크립트 문제가 섞여 있습니다. FDE 엔지니어는 그 목록을 받아 어느 것이 직전 빌드에서도 통과했는지, 같은 실행 안에서 재시도로 통과했는지, 같은 시각에 다른 케이스도 줄줄이 실패했는지를 머릿속으로 맞춰 봅니다. 실행 목록과 시도별 결과 두 표에서 이 세 질문을 계산으로 바꾸면, 고객사에 넘길 것은 결함 의심만 남고 나머지는 FDE 가 직접 고칠 목록으로 갈립니다.
FDE 엔지니어가 실행 목록과 시도별 결과 파일에 빌드 번호를 넣어 pw-triage 를 치면, 직전 빌드를 찾고 환경 탓인 실행을 걸러낸 뒤 케이스마다 다섯 판정 중 하나를 터미널에 갈래별로 찍고 같은 내용을 마크다운 리포트로 남기는 TypeScript CLI 입니다. 입력은 Playwright JSON 리포터 결과를 두 표로 펼친 합성 기록으로 시작하고, 실제 실행은 2주차에 붙입니다.
작은 데모 쇼핑몰을 직접 띄우고 두 빌드 사이에 할인 계산 오류, 선택자 변경, 느린 응답, 서버 끊김 같은 변경 12건을 일부러 넣은 뒤 Playwright 스크립트를 반복 실행해 실제 기록을 만듭니다. 변경 12건이 판정 다섯 갈래로 맞게 나뉘는지 세는 평가 러너와, 실행마다 결과를 SQLite 이력에 쌓는 사용자 정의 리포터를 더합니다.
CLI 와 리포터를 Docker 이미지 하나로 묶어 고객사 실행 서버에 설치하는 절차를 문서로 남기고, Selenium·Appium 의 JUnit XML 결과도 같은 두 표로 읽는 어댑터를 더합니다. 결함 의심 케이스에는 클로드가 error_message 와 케이스 이름으로 쓴 결함 보고 초안이 붙습니다.
FDE 엔지니어가 `pw-triage --runs runs.csv --results results.csv --build 4.12.0` 을 치면 checkout/coupon-apply 가 결함 의심 갈래에 4.11.3 통과 → 4.12.0 네 번 모두 실패와 할인액 0원 메시지로, R-1002-2 는 실행 환경 문제 갈래에 케이스 4/5 실패로 찍힙니다. 같은 실행이 남긴 마크다운 리포트에는 고객사에 넘길 결함 의심 한 건과 FDE 가 고칠 두 건이 따로 적혀 있습니다.
데모 쇼핑몰 두 빌드 사이에 일부러 넣은 변경 12건이 판정 다섯 갈래로 맞게 나뉜 건수를 셉니다. 할인 계산 오류는 결함 의심, 선택자 변경은 요소를 못 찾음처럼 넣을 때 이미 갈래를 정해 둔 변경이라, 이 수가 12에 못 미치면 오류 메시지 분류나 판정 순서 코드에 구멍이 있다는 뜻입니다.
실패 비율이 정확히 50% 인 실행, 건너뛴 케이스, 브라우저가 죽어 걸린 시간이 비어 있는 시도, 결과가 한 줄도 없는 실행, 환경 탓 실행에서만 실패한 케이스를 담은 기록 한 벌을 저장소에 두고, pw-triage 출력이 저장해 둔 기대 리포트와 한 글자라도 달라지면 CI 가 멈추게 합니다.
진짜 성능 검증은 여기서 멈춥니다. 위 수치는 작성자가 만든 쇼핑몰과 기록으로 잰 값이라, 고객사 현장에서 결함 보고가 덜 헛돌게 되는지는 실제 고객사 실행 이력과 QA 담당자의 재현 결과가 있어야 잴 수 있습니다. 고객사 history.db 를 넘겨받으면 결함 의심으로 넘긴 케이스 가운데 QA 담당자가 재현한 비율을 첫 숫자로 삼습니다.
변경 12건 일치 수는 제가 넣은 변경을 제 규칙이 맞힌 결과라, 고객사 서비스의 실패를 잘 가른다는 증거가 아닙니다. 이 CLI 가 지금 증명하는 것은 R-1002-3 처럼 실패 비율이 정확히 50% 인 실행이 어느 쪽으로 떨어지는지까지이고, 실제 이력을 받으면 QA 담당자가 재현하지 못한 결함 의심이 몇 건인지가 첫 점검 대상입니다.
실행 기록을 제가 지으면 실패가 갈래별로 하나씩 깔끔하게 떨어지기 쉬워, 어느 갈래로 갈지 헷갈리는 기록을 먼저 정하고 나머지를 채웠습니다. 실패 비율이 정확히 50% 인 R-1002-3, 검색 케이스를 건너뛴 시도, 걸린 시간이 빈 브라우저 종료 시도, 결과가 없는 R-1002-4, 환경 탓 실행에서만 실패한 검색 케이스가 그 기록입니다.
error_kind 는 오류 메시지 문자열로 분류하므로 Playwright 버전이나 고객사 서비스의 메시지 문구가 바뀌면 분류가 어긋납니다. 분류되지 않은 메시지는 따로 세어 리포트 끝에 적고, 실제 도입에서는 고객사별 메시지 사례로 규칙을 늘려야 합니다.
결함 의심은 기대값과 실제값이 어긋났다는 뜻이지 결함이 확정됐다는 뜻이 아닙니다. 테스트 데이터(쿠폰 유효기간 등)가 바뀌어 기대값 자체가 낡은 경우도 같은 갈래에 들어오므로, 고객사로 넘기기 전 마지막 확인은 FDE 엔지니어가 합니다.
실행 기록에는 고객사 스테이징 주소와 계정 정보가 섞여 들어올 수 있습니다. 결함 보고 초안을 쓰는 모델에는 error_message 의 주소를 지운 뒤 넘깁니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
고객사 실행 서버의 history.db 에서 내보낸 Playwright 실행 목록입니다. 실행 한 번이 한 줄이고 실행 번호, 그때 배포된 빌드, 시작 시각, 실행 계기를 둡니다. 실행 목록은 지난 열흘 치를 모두 내보내지만 시도별 결과는 이번 빌드와 직전 빌드 몫만 내보냅니다. 13:05 실행처럼 시작만 하고 결과가 하나도 없는 실행도 목록에는 남습니다.
| run_id | build_id | started_at | trigger |
|---|---|---|---|
| R-0922-1 | 4.11.1 | 2026-09-22 09:30 | 예약 실행 |
| R-0923-1 | 4.11.1 | 2026-09-23 09:30 | 예약 실행 |
| R-0925-1 | 4.11.2 | 2026-09-25 09:30 | 배포 직후 |
| R-0926-1 | 4.11.2 | 2026-09-26 09:30 | 예약 실행 |
| R-0929-1 | 4.11.2 | 2026-09-29 09:30 | 예약 실행 |
아래 JSON Schema 를 그대로 만족하는 Playwright 실행 목록을 60줄 더 만들어 주세요. 한 고객사 서비스가 2026-09 한 달 동안 빌드를 여덟 번 배포했다고 보고, 빌드마다 배포 직후 실행 한 번과 매일 예약 실행, 가끔 수동 재실행을 둡니다. run_id 는 'R-' 뒤에 월일 네 자리와 그날 순번을 씁니다. 쉬운 데이터만 만들지 말고 시작 기록만 있고 결과가 없을 실행을 두세 건 섞으세요. 판정 결과는 넣지 마세요. 형식 깨뜨리기 시도로 started_at 을 '10/02 09:40'·'2026-10-02T09:40:00+09:00' 처럼 적은 줄 3건을 따로 만듭니다.
{"type":"object","properties":{"run_id":{"type":"string"},"build_id":{"type":"string","description":"고객사 서비스의 배포 버전입니다"},"started_at":{"type":"string","description":"실행 시작 시각, YYYY-MM-DD HH:MM"},"trigger":{"type":"string","enum":["예약 실행","배포 직후","수동 재실행"]}},"required":["run_id","build_id","started_at","trigger"]}{"type":"object","properties":{"run_id":{"type":"string"},"build_id":{"type":"string","description":"고객사 서비스의 배포 버전입니다"},"started_at":{"type":"string","description":"실행 시작 시각, YYYY-MM-DD HH:MM"},"trigger":{"type":"string","enum":["예약 실행","배포 직후","수동 재실행"]}},"required":["run_id","build_id","started_at","trigger"]}같은 history.db 에서 내보낸 시도별 결과입니다. 케이스 한 번의 시도가 한 줄이고, 재시도하면 attempt 2 줄이 더 생깁니다. 상태는 Playwright 값(passed·failed·skipped) 그대로이고, 실패한 시도에는 오류 메시지 첫 줄과 그 메시지로 분류한 오류 종류가 붙습니다. 브라우저가 중간에 죽은 시도는 걸린 시간이 비어 있습니다.
| run_id | case_id | attempt | status | error_kind | duration_ms | error_message |
|---|---|---|---|---|---|---|
| R-1001-1 | checkout/coupon-apply.spec.ts | 1 | passed | — | 8,420 | — |
| R-1001-1 | cart/qty-change.spec.ts | 1 | passed | — | 5,310 | — |
| R-1001-1 | mypage/order-list.spec.ts | 1 | passed | — | 4,120 | — |
| R-1001-1 | auth/login.spec.ts | 1 | passed | — | 2,980 | — |
| R-1001-1 | search/keyword.spec.ts | 1 | passed | — | 3,650 | — |
아래 JSON Schema 를 그대로 만족하는 Playwright 시도별 결과를 400줄 더 만들어 주세요. 실행 목록의 실행마다 checkout·cart·mypage·auth·search·product 아래 스펙 파일 열두 개를 돌리고, 실패하면 한 번 재시도합니다. 쉬운 데이터만 만들지 말고 다음을 섞으세요: 직전 빌드에서 통과했다가 이번 빌드에서 모든 시도가 기대값 불일치로 실패하는 케이스, 같은 실행 안에서 첫 시도 실패 뒤 재시도 통과하는 케이스, 선택자 시간 초과로만 실패하는 케이스, 한 실행에서 케이스 절반을 넘게 연결 오류로 실패하는 실행, 실패 비율이 정확히 절반인 실행, skipped 케이스, duration_ms 가 null 인 브라우저 종료 시도. 판정 결과는 넣지 마세요. error_message 는 실제 Playwright 메시지 꼴로 쓰고 고객사 실제 주소 대신 stg-shop.example.com 을 씁니다.
{"type":"object","properties":{"run_id":{"type":"string"},"case_id":{"type":"string","description":"Playwright 스펙 파일 경로입니다"},"attempt":{"type":"integer","minimum":1,"description":"재시도 차수입니다. 첫 시도가 1입니다"},"status":{"type":"string","enum":["passed","failed","skipped"]},"error_kind":{"type":["string","null"],"enum":["assertion","locator_timeout","network","browser",null],"description":"실패 시 오류 메시지에서 분류한 종류입니다. 통과·건너뜀이면 null 입니다"},"duration_ms":{"type":["integer","null"],"minimum":0,"description":"그 시도에 걸린 시간입니다. 브라우저가 중간에 죽어 기록이 남지 않으면 null 입니다"},"error_message":{"type":["string","null"],"description":"Playwright 오류 메시지 첫 줄입니다"}},"required":["run_id","case_id","attempt","status","error_kind","duration_ms","error_message"]}{"type":"object","properties":{"run_id":{"type":"string"},"case_id":{"type":"string","description":"Playwright 스펙 파일 경로입니다"},"attempt":{"type":"integer","minimum":1,"description":"재시도 차수입니다. 첫 시도가 1입니다"},"status":{"type":"string","enum":["passed","failed","skipped"]},"error_kind":{"type":["string","null"],"enum":["assertion","locator_timeout","network","browser",null],"description":"실패 시 오류 메시지에서 분류한 종류입니다. 통과·건너뜀이면 null 입니다"},"duration_ms":{"type":["integer","null"],"minimum":0,"description":"그 시도에 걸린 시간입니다. 브라우저가 중간에 죽어 기록이 남지 않으면 null 입니다"},"error_message":{"type":["string","null"],"description":"Playwright 오류 메시지 첫 줄입니다"}},"required":["run_id","case_id","attempt","status","error_kind","duration_ms","error_message"]}| Playwright | JSON 리포터 결과에서 케이스·재시도·상태·오류 메시지를 꺼내고, 2주차에는 데모 쇼핑몰 UI 자동화 스크립트와 결과를 이력에 쌓는 사용자 정의 리포터를 씁니다. |
| TypeScript | 실행·결과 레코드 타입, 직전 빌드 찾기, 실행별 실패 비율, 케이스 판정 우선순위를 CLI 로 짭니다. |
| Selenium | 3주차에 Selenium 결과의 JUnit XML 을 같은 두 표로 바꾸는 어댑터를 붙입니다. |
| Appium | 3주차에 앱 테스트 결과도 같은 어댑터로 읽어 웹과 앱 실패를 한 리포트에 판정합니다. |
| 테스트 시나리오 설계 | 쿠폰 적용·수량 변경·주문 목록·로그인·검색 다섯 시나리오를 데모 쇼핑몰 케이스로 짭니다. |
| 결함 분석 | error_message 를 assertion·locator_timeout·network·browser 로 분류하는 규칙을 짜고, 결함 의심 케이스의 기대값과 실제값을 리포트에 옮깁니다. |
| 이커머스 QA | 데모 쇼핑몰의 할인·장바구니 합계·주문 목록처럼 이커머스에서 결함이 잦은 화면을 케이스로 둡니다. |
| LLM/AI 도구 | 결함 의심 케이스의 결함 보고 초안과 FDE 할 일 문안을 클로드로 씁니다. |
| SQLite | 2주차부터 실행 목록과 시도별 결과를 이력으로 쌓아 직전 빌드와 최근 10회 실행을 조회합니다. |
| Docker | 3주차에 CLI·리포터·브라우저를 이미지 하나로 묶어 고객사 실행 서버마다 같은 환경을 만듭니다. |
✗ fail → 50% 실행이 환경 탓으로 빠지면 비율 계산에서 건너뛴 케이스를 빼는 순서와 부등호부터 고칩니다.
✓ pass → 2주차 데모 쇼핑몰 실제 실행과 평가 러너로 넘어갑니다.
✗ fail → 분류에서 나오면 error_kind 규칙부터 고치고, 다른 프레임워크로 넓히는 일은 미룹니다.
✓ pass → 3주차 Docker 설치 문서와 JUnit XML 어댑터, 결함 보고 초안으로 넘어갑니다.
실패 케이스 판정은 이번 빌드의 케이스별 판정과, 환경 탓으로 판정된 실행의 실행별 판정입니다. 케이스 줄에는 판정에 쓴 실행 수와 시도 수, 오류 종류, 직전 빌드 상태를, 실행 줄에는 실패 비율을 싣고, 둘 다 고객사 QA 담당자나 FDE 엔지니어에게 넘길 문안을 붙여 터미널 출력과 마크다운 리포트가 같은 행을 읽습니다.
{"type":"object","properties":{"build_id":{"type":"string"},"prev_build_id":{"type":["string","null"],"description":"이번 빌드 첫 실행의 started_at 보다 앞선 마지막 실행의 build_id 입니다"},"scope":{"type":"string","enum":["케이스","실행"],"description":"케이스면 case_id 하나의 판정, 실행이면 run_id 하나 전체의 판정입니다"},"run_id":{"type":["string","null"],"description":"scope 가 실행일 때만 채웁니다"},"case_id":{"type":["string","null"],"description":"scope 가 케이스일 때만 채웁니다"},"verdict":{"type":"string","enum":["통과","결함 의심","불안정한 스크립트","요소를 못 찾음","실행 환경 문제"]},"runs_counted":{"type":["integer","null"],"minimum":0,"description":"케이스 판정에 쓴 이번 빌드 실행 수입니다. 실행 환경 문제로 판정된 실행과 그 케이스를 건너뛴 실행은 세지 않습니다"},"attempts_failed":{"type":["integer","null"],"minimum":0},"attempts_total":{"type":["integer","null"],"minimum":0},"error_kinds":{"type":"array","items":{"type":"string"},"description":"판정에 쓴 실패 시도의 error_kind 를 중복 없이 모은 것입니다"},"prev_build_status":{"type":["string","null"],"enum":["passed","failed",null],"description":"직전 빌드 실행에서 그 케이스의 마지막 시도 status 입니다"},"failed_case_pct":{"type":["number","null"],"minimum":0,"maximum":100,"description":"scope 가 실행일 때, 건너뛴 케이스를 뺀 실행 케이스 가운데 마지막 시도가 failed 인 케이스의 비율입니다. 결과가 한 줄도 없는 실행은 null 입니다"},"evidence":{"type":"array","items":{"type":"object","properties":{"table":{"type":"string","enum":["test_runs","test_results"]},"field":{"type":"string"},"agg":{"type":["string","null"],"enum":["count","sum","avg","min","max",null]},"value":{}},"required":["table","field","value"]}},"summary":{"type":"string"},"qa_note":{"type":["string","null"],"description":"클로드가 판정 행과 error_message 로 쓴 전달 문안입니다. 결함 의심은 고객사 QA 담당자에게 넘길 결함 보고 초안이고, 나머지는 FDE 엔지니어가 직접 할 일입니다. 통과면 null 입니다"}},"required":["build_id","prev_build_id","scope","run_id","case_id","verdict","runs_counted","attempts_failed","attempts_total","error_kinds","prev_build_status","failed_case_pct","evidence","summary","qa_note"]}[
{
"build_id": "4.12.0",
"prev_build_id": "4.11.3",
"scope": "케이스",
"run_id": null,
"case_id": "checkout/coupon-apply.spec.ts",
"verdict": "결함 의심",
"runs_counted": 2,
"attempts_failed": 4,
"attempts_total": 4,
"error_kinds": [
"assertion"
],
"prev_build_status": "passed",
"failed_case_pct": null,
"evidence": [
{
"table": "test_results",
"field": "case_id",
"agg": null,
"value": "checkout/coupon-apply.spec.ts"
},
{
"table": "test_results",
"field": "error_kind",
"agg": null,
"value": "assertion"
},
{
"table": "test_results",
"field": "error_message",
"agg": null,
"value": "expect(locator('.discount-amount')).toHaveText('3,000원') · Received: '0원'"
},
{
"table": "test_results",
"field": "status",
"agg": null,
"value": "failed"
},
{
"table": "test_runs",
"field": "run_id",
"agg": null,
"value": "R-1002-3"
},
{
"table": "test_results",
"field": "attempt",
"agg": "count",
"value": 4
}
],
"summary": "4.11.3 에서는 통과했는데 4.12.0 의 두 실행에서 시도 네 번이 모두 할인액 기대값 불일치로 실패했습니다. 케이스 2/4 실패로 50% 에 그친 R-1002-3 은 정상 실행으로 보고 판정에 넣었습니다.",
"qa_note": "[4.12.0] 쿠폰 적용 후 할인액이 표시되지 않습니다. 재현: 장바구니에 상품을 담고 쿠폰 WELCOME3000 을 적용합니다. 기대: 할인액 3,000원 / 실제: 0원. 4.11.3 에서는 같은 절차로 3,000원이 표시됐습니다."
},
{
"build_id": "4.12.0",
"prev_build_id": "4.11.3",
"scope": "케이스",
"run_id": null,
"case_id": "cart/qty-change.spec.ts",
"verdict": "불안정한 스크립트",
"runs_counted": 2,
"attempts_failed": 2,
"attempts_total": 4,
"error_kinds": [
"assertion",
"network"
],
"prev_build_status": "passed",
"failed_case_pct": null,
"evidence": [
{
"table": "test_results",
"field": "case_id",
"agg": null,
"value": "cart/qty-change.spec.ts"
},
{
"table": "test_results",
"field": "attempt",
"agg": null,
"value": 1
},
{
"table": "test_results",
"field": "status",
"agg": null,
"value": "failed"
},
{
"table": "test_results",
"field": "attempt",
"agg": null,
"value": 2
},
{
"table": "test_results",
"field": "status",
"agg": null,
"value": "passed"
},
{
"table": "test_runs",
"field": "run_id",
"agg": null,
"value": "R-1002-3"
}
],
"summary": "R-1002-1 과 R-1002-3 모두 첫 시도는 실패하고 재시도에서 통과해, 같은 실행 안에서 결과가 뒤바뀌었습니다. 수량 변경 뒤 합계가 갱신되기 전에 값을 읽는 대기 문제로 보입니다.",
"qa_note": "FDE 할 일: cart/qty-change.spec.ts 의 합계 확인 앞에 응답 대기를 넣고, 결함으로 올리지 않습니다."
},
{
"build_id": "4.12.0",
"prev_build_id": "4.11.3",
"scope": "케이스",
"run_id": null,
"case_id": "mypage/order-list.spec.ts",
"verdict": "요소를 못 찾음",
"runs_counted": 2,
"attempts_failed": 4,
"attempts_total": 4,
"error_kinds": [
"locator_timeout"
],
"prev_build_status": "passed",
"failed_case_pct": null,
"evidence": [
{
"table": "test_results",
"field": "case_id",
"agg": null,
"value": "mypage/order-list.spec.ts"
},
{
"table": "test_results",
"field": "error_kind",
"agg": null,
"value": "locator_timeout"
},
{
"table": "test_results",
"field": "duration_ms",
"agg": null,
"value": 30000
},
{
"table": "test_results",
"field": "error_message",
"agg": null,
"value": "locator('#orderTable') 대기 30000ms 초과"
}
],
"summary": "4.11.3 에서 통과한 케이스가 4.12.0 에서 #orderTable 을 30초 동안 찾지 못해 네 번 모두 실패했습니다. 주문 목록 화면 구조가 바뀌어 선택자를 고쳐야 하는 경우로 봅니다.",
"qa_note": "FDE 할 일: 4.12.0 주문 목록 화면에서 새 표 요소를 확인해 locator('#orderTable') 를 역할 기반 선택자로 바꿉니다. 화면 자체가 비어 있으면 결함으로 다시 분류합니다."
},
{
"build_id": "4.12.0",
"prev_build_id": "4.11.3",
"scope": "케이스",
"run_id": null,
"case_id": "auth/login.spec.ts",
"verdict": "통과",
"runs_counted": 2,
"attempts_failed": 0,
"attempts_total": 2,
"error_kinds": [],
"prev_build_status": "passed",
"failed_case_pct": null,
"evidence": [
{
"table": "test_results",
"field": "case_id",
"agg": null,
"value": "auth/login.spec.ts"
},
{
"table": "test_results",
"field": "status",
"agg": null,
"value": "passed"
},
{
"table": "test_results",
"field": "attempt",
"agg": "count",
"value": 2
}
],
"summary": "4.12.0 정상 실행 두 번 모두 첫 시도에 통과했습니다.",
"qa_note": null
},
{
"build_id": "4.12.0",
"prev_build_id": "4.11.3",
"scope": "케이스",
"run_id": null,
"case_id": "search/keyword.spec.ts",
"verdict": "통과",
"runs_counted": 1,
"attempts_failed": 0,
"attempts_total": 1,
"error_kinds": [],
"prev_build_status": "passed",
"failed_case_pct": null,
"evidence": [
{
"table": "test_results",
"field": "case_id",
"agg": null,
"value": "search/keyword.spec.ts"
},
{
"table": "test_results",
"field": "status",
"agg": null,
"value": "skipped"
},
{
"table": "test_results",
"field": "error_kind",
"agg": null,
"value": "network"
},
{
"table": "test_runs",
"field": "run_id",
"agg": null,
"value": "R-1002-2"
}
],
"summary": "R-1002-1 에서 통과했고, R-1002-3 에서는 건너뛰어 세지 않았으며, R-1002-2 의 네트워크 실패는 실행 환경 문제로 판정된 실행이라 케이스 판정에서 뺐습니다.",
"qa_note": null
},
{
"build_id": "4.12.0",
"prev_build_id": "4.11.3",
"scope": "실행",
"run_id": "R-1002-2",
"case_id": null,
"verdict": "실행 환경 문제",
"runs_counted": null,
"attempts_failed": 4,
"attempts_total": 5,
"error_kinds": [
"network",
"browser"
],
"prev_build_status": null,
"failed_case_pct": 80,
"evidence": [
{
"table": "test_runs",
"field": "run_id",
"agg": null,
"value": "R-1002-2"
},
{
"table": "test_results",
"field": "case_id",
"agg": "count",
"value": 4
},
{
"table": "test_results",
"field": "case_id",
"agg": "count",
"value": 5
},
{
"table": "test_results",
"field": "error_kind",
"agg": null,
"value": "network"
},
{
"table": "test_results",
"field": "duration_ms",
"agg": null,
"value": null
}
],
"summary": "09:40 실행은 케이스 5개 중 4개가 실패했고 실패가 모두 연결 끊김과 브라우저 종료였습니다. 브라우저가 중간에 죽은 order-list 는 걸린 시간도 남지 않았습니다.",
"qa_note": "FDE 할 일: 09:40 전후 고객사 스테이징 서버 연결 기록을 확인하고, 이 실행의 실패는 케이스별 리포트에 넣지 않습니다."
},
{
"build_id": "4.12.0",
"prev_build_id": "4.11.3",
"scope": "실행",
"run_id": "R-1002-4",
"case_id": null,
"verdict": "실행 환경 문제",
"runs_counted": null,
"attempts_failed": 0,
"attempts_total": 0,
"error_kinds": [],
"prev_build_status": null,
"failed_case_pct": null,
"evidence": [
{
"table": "test_runs",
"field": "run_id",
"agg": null,
"value": "R-1002-4"
},
{
"table": "test_runs",
"field": "started_at",
"agg": null,
"value": "2026-10-02 13:05"
},
{
"table": "test_results",
"field": "run_id",
"agg": "count",
"value": 0
}
],
"summary": "13:05 실행은 시작 기록만 있고 결과가 한 줄도 없어, 케이스가 돌기 전에 실행이 멈춘 것으로 봅니다.",
"qa_note": "FDE 할 일: 고객사 실행 서버의 Playwright 브라우저 설치 상태와 디스크 여유를 확인한 뒤 수동으로 다시 돌립니다."
}
][자동 검증 · 코드가 판정합니다] 1. verdict 가 통과·결함 의심·불안정한 스크립트·요소를 못 찾음·실행 환경 문제 다섯 값 중 하나인지 확인합니다. 2. 필수 필드 build_id·prev_build_id·scope·run_id·case_id·verdict·runs_counted·attempts_failed·attempts_total·error_kinds·prev_build_status·failed_case_pct·evidence·summary·qa_note 가 모두 존재하는지 확인합니다. 3. prev_build_id 를 다시 계산합니다. 이번 빌드 첫 실행 R-1002-1 의 started_at 2026-10-02 08:10 보다 앞선 마지막 실행 R-1001-1 의 build_id 4.11.3 과 같은지 봅니다. 4. failed_case_pct 를 다시 계산합니다. 같은 run_id 에서 status 가 skipped 인 케이스를 빼고, 케이스마다 attempt 가 가장 큰 줄의 status 가 failed 인 케이스 수를 나눠 백분율로 둡니다. R-1002-2 는 4/5 로 80.0, R-1002-3 은 2/4 로 50.0 입니다. 5. failed_case_pct 가 50 보다 크고 그 실패들의 error_kind 가 network 나 browser 뿐이면 그 실행의 verdict 는 실행 환경 문제입니다. 50 과 같은 R-1002-3 은 정상 실행으로 둡니다. test_results 에 줄이 하나도 없는 실행도 실행 환경 문제이고 failed_case_pct 는 null 입니다(R-1002-4). 6. 케이스 판정은 이번 빌드의 실행 가운데 5에서 실행 환경 문제로 판정된 실행을 빼고, 그 케이스가 skipped 인 실행도 빼고 셉니다. runs_counted 가 그렇게 센 실행 수와 같은지 다시 계산합니다. search/keyword.spec.ts 는 R-1002-1 하나라 1입니다. 7. 같은 run_id·case_id 에서 attempt 별 status 가 failed 와 passed 로 갈리거나, 이번 빌드 최근 10회 실행의 마지막 시도 결과가 실행 순서대로 3번 넘게 뒤바뀌면 verdict 는 불안정한 스크립트입니다. 8. 7에 걸리지 않고 남은 시도가 모두 failed 이며 그 가운데 error_kind 가 assertion 인 시도가 있으면 verdict 는 결함 의심입니다. prev_build_status 가 passed 면 이번 빌드에서 새로 생긴 실패입니다. 직전 빌드에서도 실패했거나(failed) 직전 빌드에 없던 새 케이스(null)도 같은 판정을 내리되, summary 에 새로 생긴 실패가 아니거나 견줄 직전 결과가 없다고 적어 고객사에 넘길지 FDE 가 한 번 더 봅니다. 9. 7·8에 걸리지 않고 남은 시도가 모두 failed 이며 error_kind 가 locator_timeout 이면 verdict 는 요소를 못 찾음입니다. 직전 빌드에서 통과했으면 화면이 바뀐 경우이고, 직전 빌드에서도 같은 선택자로 실패했으면 summary 에 오래된 선택자 문제라고 적습니다. 10. 7~9에 걸리지 않고 남은 시도가 모두 failed 이며 error_kind 가 network 나 browser 뿐이면 그 케이스의 verdict 는 실행 환경 문제입니다. 11. 남은 시도의 마지막 status 가 모두 passed 면 verdict 는 통과이고 qa_note 는 null 입니다. 12. attempts_failed 가 attempts_total 이하이고 둘 다 0 이상인지, failed_case_pct 가 0~100 범위인지 봅니다. 13. evidence 의 table·field 가 test_runs·test_results 에 실재하는 컬럼인지 대조합니다. 14. agg 가 없는 evidence 의 value 가 그 컬럼에 수록된 행의 값 원문과 같은지 확인합니다. 15. summary 와 qa_note 가 존댓말로 끝나는지 확인합니다. [수동 검증 · 사람이 봅니다] 1. 결함 의심으로 넘길 케이스가 실제 결함인지는 고객사 QA 담당자가 재현 절차를 따라 확인합니다. 2. 요소를 못 찾음이 화면 변경 때문인지 화면이 아예 안 뜨는 결함인지는 FDE 엔지니어가 4.12.0 화면을 열어 가립니다. [평가 러너 · 2주차] 1. 직접 만든 데모 쇼핑몰의 두 빌드 사이에 일부러 넣은 변경 12건(할인 계산 오류, 선택자 변경, 느린 응답, 서버 끊김 등)이 판정 다섯 갈래로 맞게 나뉘는지 셉니다. 2. 같은 빌드를 20회 돌렸을 때 판정이 실행마다 같은 케이스가 몇 개인지 셉니다.