“내 컴퓨터에선 되는데 CI에선 빨간불.” E2E를 운영하는 모든 개발자의 숙제이자, console.log를 도배하게 만드는 주범이죠. Playwright에는 이 미스터리를 증거 기반으로 푸는 도구가 셋 내장돼 있습니다 — 실패 순간을 통째로 되감는 트레이스, 시간여행하듯 보는 UI 모드, 그리고 신호로 쓰는 리트라이. 이 글에서 셋 다 챙깁니다.
시리즈 독자라면 지금까지 만든 E2E가 언젠가 깨질 때를 위한 보험이고, 검색으로 오셨다면 지금 깨져 있는 그 테스트를 위한 응급 키트예요. 예제는 자급자족합니다.
실습 코드: 이 편의 상태가
step-14태그에 담겨 있습니다(트레이스·디버깅은 이미 있던 설정과 CLI 도구를 쓰는 편이라 새 코드는 없어요 — 픽스처까지 갖춘 13편과 같은 상태입니다). StackBlitz에서 바로 열기도 됩니다.
실패를 추적하는 세 가지 도구를 챙깁니다.
- 트레이스 뷰어로 실패 순간 되감기
- UI 모드·디버그 모드
- 리트라이를 ‘해결’이 아니라 ‘신호’로 쓰기

왜 하필 CI에서만 깨질까#
미스터리부터 걷어냅시다. CI 머신은 여러분 노트북과 다른 환경입니다 — 대개 더 느리고(로딩이 늦어 타이밍 이슈가 잘 드러남), 병렬로 돌고, 화면 없는 헤드리스 모드에, 타임존·로케일도 다를 수 있어요. 그러니까 “CI에서만 깨진다"는 초자연 현상이 아니라, 로컬에선 운 좋게 안 걸리던 문제가 더 가혹한 조건에서 걸린 것에 가깝습니다. 로케이터 편의 flaky 원인들이 CI에서 유독 잘 터지는 이유죠.
문제는 재현이에요. 내 컴퓨터에선 안 깨지니 들여다볼 수가 없죠. 그래서 필요한 게 — 실패 순간의 블랙박스입니다.
트레이스 뷰어#
repo 설정엔 이미 trace: 'on-first-retry'가 들어 있어요 — 첫 재시도부터 트레이스를 기록합니다. 실패한 테스트의 모든 단계를 스냅샷·네트워크·콘솔과 함께 되감아 볼 수 있죠.
# 항상 기록하고 싶을 때
npx playwright test --trace on
# 기록된 트레이스 열기
npx playwright show-trace test-results/**/trace.zip실패한 테스트를 --trace on으로 돌리면 test-results/에 이런 실물이 남습니다.
test-results/
└── dashboard-대시보드에서-사용자를-검색한다-chromium/
├── trace.zip ← 블랙박스 본체
└── error-context.md ← 에러·코드 위치를 정리한 마크다운 (AI에게 바로 던질 수 있는 형태)error-context.md가 재미있어요 — 열어보면 “이 테스트가 왜 실패했는지 설명하고 수정 코드를 제안하라"는 지시문과 함께 실패 정보가 정리돼 있습니다. AI에게 붙여넣기 좋게 만들어둔 거예요(이 시리즈 후반의 AI 편들에서 다시 만납니다).
트레이스를 열면 뭐가 보이냐면 — 실행된 액션 목록(goto, fill, expect…)이 타임라인으로 늘어서 있고, 아무 액션이나 클릭하면 그 순간의 화면(전/후 DOM 스냅샷), 그때 오간 네트워크 요청, 콘솔 출력까지 통째로 재생됩니다. 실제 수사는 이렇게 흘러가요.
- CI가 빨간불 — 리포트를 아티팩트로 업로드해두면(다음 장에서 다룹니다)
trace.zip을 내려받습니다 show-trace로 열고, 빨갛게 표시된 실패 액션을 클릭- 그 순간의 화면을 봅니다 — “아, 아직 로딩 스피너네” 혹은 “모달이 버튼을 가리고 있네”
- 네트워크 탭 확인 — 응답이 늦었나? 500이었나?
- 원인 확정. 추측이 아니라 목격으로요

로컬 재현이 안 되는 실패를 재현 없이 푸는 것 — 이게 트레이스의 존재 이유입니다.
UI 모드와 디버그 모드#
npx playwright test --ui # 타임라인 보며 실행·재실행
npx playwright test --debug # 한 스텝씩 멈추며 진행UI 모드는 각 액션 시점의 DOM 스냅샷을 시간여행하듯 보여줘요. 어디서 멈췄는지 눈으로 확인하면 디버깅 속도가 확 달라집니다.
리트라이는 신호다#
// playwright.config.ts (repo 설정에 포함)
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,리트라이로 통과하면 어떻게 보이는지, 일부러 첫 시도에만 실패하는 테스트를 만들어 돌려봤습니다.
Running 1 test using 1 worker
✘ 1 [chromium] › e2e/tmp-flaky.spec.ts:2:1 › 가끔 실패하는 테스트 (337ms)
✓ 2 [chromium] › e2e/tmp-flaky.spec.ts:2:1 › 가끔 실패하는 테스트 (retry #1) (174ms)
1 flaky주목할 건 마지막 판정입니다 — 재시도로 결국 통과했는데도 1 passed가 아니라 1 flaky. Playwright는 “한 번 실패했었다"는 사실을 덮어주지 않아요. 그리고 그 딱지가 바로 신호입니다: 리트라이로 통과한 테스트는 이미 아픈 테스트예요. 방치하지 말고 로케이터 편의 원칙으로 돌아가 근본 원인을 잡는 게 맞습니다.
덤으로 forbidOnly — 디버깅하다 남긴 test.only가 CI로 흘러들어 “테스트 1개만 돌고 전부 초록불"이 되는 사고를 막아줍니다.
한 장 요약#
- 트레이스 = 실패한 테스트의 블랙박스 — 단계별 DOM 스냅샷·네트워크·콘솔을 되감아 봄 (
--trace on, 열기는show-trace) - repo 설정은
trace: 'on-first-retry'— 재시도가 시작되는 순간부터 자동 기록 - 로컬 디버깅은
--ui(타임라인 시간여행)와--debug(한 스텝씩) — console.log 도배와 작별 - 리트라이는 해결이 아니라 신호 — 리트라이로 통과한 테스트는 이미 아픈 테스트, 로케이터 원칙으로 근본 치료
forbidOnly로test.only가 CI에 흘러드는 사고 방지 — 리포트는 CI 아티팩트로 남겨 원격 부검
미스터리는 풀라고 있는 겁니다#
이제 실패를 두려워하지 않게 됐어요. 다음 편은 이 챕터의 접근성 하이라이트 — axe로 페이지를 통째로 스캔합니다.
트레이스 뷰어를 처음 열면 “이걸 왜 이제 알았지” 싶어요. console.log 도배로 디버깅하던 날들이여, 안녕.
레벨업: CI에서만 깨지는 테스트도 트레이스로 원인을 추적할 수 있습니다.
다음 편: 접근성 E2E — @axe-core/playwright로 스캔
낯선 용어가 있었다면 — 용어집에 전부 한 줄씩 정리돼 있어요.
