A CI test failure that doesn't reproduce locally is hard to debug with only a stack trace. Trace Viewer records every action, network request, and DOM snapshot during the test and lets you open a timeline replay in the browser afterward. The Before snapshot at the failing action shows what Playwright actually saw: which element was highlighted, whether an overlay was covering it, whether the API response was still in flight when the click happened.
What Trace Viewer actually is
Trace Viewer is a local web app that records everything that happened during a test run: every action, every network request, every DOM snapshot at every step. You open it after the run, scrub through a timeline, and see exactly what the browser was showing when the test failed.
It's the difference between reading a stack trace and watching a replay.
Enabling traces
By default, traces are off. Turn them on in playwright.config.ts:
use: {
trace: 'on-first-retry',
},The four options:
| Value | When to use |
|---|---|
| 'off' | Default, no traces generated |
| 'on' | Every run, every test. Useful locally, too slow for large CI suites |
| 'on-first-retry' | Only when a test retries. Best for CI: minimal overhead, captures failures |
| 'retain-on-failure' | Keeps traces only for failed tests. Good balance for local debugging |
For CI, 'on-first-retry' is the standard choice. For local debugging of a specific test, switch to 'on' temporarily.
Opening a trace
After a failed run, Playwright creates zip files inside test-results/. To open one:
npx playwright show-trace test-results/my-test-chromium/trace.zipOr if you ran tests with the HTML reporter, open playwright-report/index.html and click the trace icon next to a failed test. The trace opens automatically.
Reading the timeline
The Trace Viewer UI has three panels:
Timeline (top): A horizontal bar showing the test's full duration. Each action is marked as a segment. Click any segment to jump to that moment. Action list (left): Every step in order:page.goto, locator.click, expect, network calls. Each has a duration. Slow actions stand out immediately.
Snapshots (right): The DOM state at the selected action. Two tabs: Before (what the page looked like when Playwright started the action) and After (what it looked like when the action completed).
Below the snapshots: Network tab (all HTTP requests at that moment), Console tab (any console.log output), Source tab (which line of your test triggered this action).
The workflow for debugging a failure
1. Find the failing action in the action list; it's marked red.
2. Click it. Look at the Before snapshot.
3. Ask: is the element visible? Is it disabled? Is there an overlay blocking it?
4. If the element looks fine but the action failed, check Network: was there a slow API call still in flight?
5. Check Console: any JavaScript errors that explain the state?
Most failures become obvious within 30 seconds of opening the trace. The DOM snapshot tells you what Playwright was actually seeing, which is almost always different from what you assumed it was seeing.
Common failure patterns and what they look like in traces
Timing issues: The action list shows apage.waitForURL or locator.waitFor that timed out. The Before snapshot shows the element doesn't exist yet. Solution: add an explicit wait for the element that signals readiness, not just a timeout.
Strict mode violations: You used a locator that matched multiple elements. The Before snapshot shows you: hover over highlighted elements to count them.
State from previous test: The Before snapshot of your first action shows you're already logged in, or there's a toast notification covering a button. Your test assumed a clean state. Solution: add proper test isolation.
Animation or transition blocking the click: The Before snapshot shows the element, the After snapshot shows nothing happened. A CSS transition was mid-flight when Playwright clicked. Solution: wait for animation: none or use locator.click({ force: true }) as a temporary debug (never in final tests).
Wrong element selected: The Before snapshot shows Playwright highlighted a completely different element than you expected. Your selector is matching something else on the page. Fix the locator.
Traces in CI
When running in GitHub Actions, add a step to upload the trace artifact:
- name: Run tests
run: npx playwright test
- name: Upload traces
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-traces
path: test-results/
retention-days: 7Download the artifact after a failed run, unzip it, and open with npx playwright show-trace. You're looking at the same replay you'd see locally: the CI environment's browser state at the moment of failure.
Combining traces with screenshots and video
Playwright can also capture screenshots on failure and full video recordings:
use: {
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry',
},Use all three for debugging tricky failures. Screenshots give you a static image; video gives you the full run; trace gives you the full run plus every DOM state plus network calls. They complement each other: a screenshot misses what happened five seconds before the failure; a trace has it all.
For routine CI, trace only is enough. Video adds significant overhead and is usually only worth it for intermittent failures you can't reproduce locally.
When traces aren't enough
Trace Viewer shows you browser state. It doesn't show you server state. If your test fails because the API returned unexpected data, the trace will show you the bad response in the Network tab, but it won't tell you why the backend returned it.
For those failures, combine trace data (to see what the response was) with backend logs (to understand why). The trace gets you to the right question; the logs answer it.
→ See also: Debugging Flaky Tests: A Practical Guide | How to Read Playwright Error Messages | Playwright Test Reports: Built-in HTML vs Allure