# 트레이스·디버깅·리트라이로 원인 추적

> 테스트가 CI에서만 깨질 때, 무엇을 봐야 할까요? Playwright의 트레이스 뷰어와 디버깅 도구로 실패의 진짜 원인을 추적합니다.

**Published:** 2026-07-22 | **Updated:** 2026-07-22

---


<!--
「프론트엔드 테스트 제대로 하기」 시리즈 초안. 본문 코드는 companion repo 실코드 기준으로 확정됨.
- 계획: _notes/series/frontend-testing-series-plan.md
- 이미지 가이드: _notes/image-guides/frontend-testing-playwright-debug-images.md
- repo 태그(발행 시 고정): step-14
- 발행 전: 이미지 제작, OG 배경, /translate-post 로 en 번역
-->

> 「프론트엔드 테스트 제대로 하기」 시리즈의 **14편**입니다. [전체 목차 보기](/series/프론트엔드-테스트-제대로-하기/) · [용어집](/posts/frontend-testing-glossary/)

"내 컴퓨터에선 되는데 CI에선 빨간불." E2E를 운영하는 모든 개발자의 숙제이자, `console.log`를 도배하게 만드는 주범이죠. Playwright에는 이 미스터리를 **증거 기반**으로 푸는 도구가 셋 내장돼 있습니다 — 실패 순간을 통째로 되감는 트레이스, 시간여행하듯 보는 UI 모드, 그리고 신호로 쓰는 리트라이. 이 글에서 셋 다 챙깁니다.

시리즈 독자라면 지금까지 만든 E2E가 언젠가 깨질 때를 위한 보험이고, 검색으로 오셨다면 지금 깨져 있는 그 테스트를 위한 응급 키트예요. 예제는 자급자족합니다.

> 실습 코드: 이 편의 상태가 [`step-14` 태그](https://github.com/IsaacEryn/frontend-testing-lab/tree/step-14)에 담겨 있습니다(트레이스·디버깅은 이미 있던 설정과 CLI 도구를 쓰는 편이라 새 코드는 없어요 — 픽스처까지 갖춘 [13편](/posts/frontend-testing-playwright-fixtures/)과 같은 상태입니다). [StackBlitz에서 바로 열기](https://stackblitz.com/github/IsaacEryn/frontend-testing-lab/tree/step-14)도 됩니다.

실패를 추적하는 세 가지 도구를 챙깁니다.

- 트레이스 뷰어로 실패 순간 되감기
- UI 모드·디버그 모드
- 리트라이를 '해결'이 아니라 '신호'로 쓰기

{{< img src="images/contents/trace-viewer.png" alt="Playwright 트레이스 뷰어에서 실패한 테스트의 단계별 스냅샷을 되감아 보는 화면" >}}

---

## 왜 하필 CI에서만 깨질까

미스터리부터 걷어냅시다. CI 머신은 여러분 노트북과 **다른 환경**입니다 — 대개 더 느리고(로딩이 늦어 타이밍 이슈가 잘 드러남), 병렬로 돌고, 화면 없는 헤드리스 모드에, 타임존·로케일도 다를 수 있어요. 그러니까 "CI에서만 깨진다"는 초자연 현상이 아니라, **로컬에선 운 좋게 안 걸리던 문제가 더 가혹한 조건에서 걸린 것**에 가깝습니다. [로케이터 편](/posts/frontend-testing-playwright-locators/)의 flaky 원인들이 CI에서 유독 잘 터지는 이유죠.

문제는 재현이에요. 내 컴퓨터에선 안 깨지니 들여다볼 수가 없죠. 그래서 필요한 게 — 실패 순간의 **블랙박스**입니다.

---

## 트레이스 뷰어

repo 설정엔 이미 `trace: 'on-first-retry'`가 들어 있어요 — **첫 재시도부터 트레이스를 기록**합니다. 실패한 테스트의 모든 단계를 스냅샷·네트워크·콘솔과 함께 되감아 볼 수 있죠.

```bash
# 항상 기록하고 싶을 때
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 스냅샷), 그때 오간 **네트워크 요청**, **콘솔 출력**까지 통째로 재생됩니다. 실제 수사는 이렇게 흘러가요.

1. CI가 빨간불 — 리포트를 아티팩트로 업로드해두면(다음 장에서 다룹니다) `trace.zip`을 내려받습니다
2. `show-trace`로 열고, **빨갛게 표시된 실패 액션**을 클릭
3. 그 순간의 화면을 봅니다 — "아, 아직 로딩 스피너네" 혹은 "모달이 버튼을 가리고 있네"
4. 네트워크 탭 확인 — 응답이 늦었나? 500이었나?
5. 원인 확정. 추측이 아니라 **목격**으로요

{{< img src="images/contents/debug-workflow.png" alt="CI 실패에서 원인 확정까지의 트레이스 수사 흐름 다이어그램 - CI 빨간불에서 trace.zip 아티팩트를 내려받아 트레이스 뷰어로 열고, 실패한 액션을 클릭해 그 순간의 DOM 스냅샷과 네트워크, 콘솔을 확인한 뒤 원인을 확정하는 다섯 단계입니다. 로컬에서 재현하지 못해도 증거로 원인을 목격한다는 설명이 붙어 있습니다" >}}

로컬 재현이 안 되는 실패를 **재현 없이** 푸는 것 — 이게 트레이스의 존재 이유입니다.

---

## UI 모드와 디버그 모드

```bash
npx playwright test --ui       # 타임라인 보며 실행·재실행
npx playwright test --debug    # 한 스텝씩 멈추며 진행
```

UI 모드는 각 액션 시점의 DOM 스냅샷을 시간여행하듯 보여줘요. 어디서 멈췄는지 눈으로 확인하면 디버깅 속도가 확 달라집니다.

---

## 리트라이는 신호다

```ts
// playwright.config.ts (repo 설정에 포함)
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
```

리트라이로 통과하면 어떻게 보이는지, 일부러 첫 시도에만 실패하는 테스트를 만들어 돌려봤습니다.

```bash
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는 "한 번 실패했었다"는 사실을 덮어주지 않아요. 그리고 그 딱지가 바로 신호입니다: **리트라이로 통과한 테스트는 이미 아픈 테스트**예요. 방치하지 말고 [로케이터 편](/posts/frontend-testing-playwright-locators/)의 원칙으로 돌아가 근본 원인을 잡는 게 맞습니다.

덤으로 `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로 스캔

> 낯선 용어가 있었다면 — [용어집](/posts/frontend-testing-glossary/)에 전부 한 줄씩 정리돼 있어요.

