Quality
11 min read

Playwright trace viewer: set up, open, and debug a failed run

Turn on tracing, open a trace with npx playwright show-trace, read the timeline, and debug a failed run in CI.

Wei-Wei Wu
CEO, Momentic
Oct 10, 2025

The Playwright Trace Viewer is a GUI that replays a recorded test run: each action, a DOM snapshot before and after it, the network requests, the console output and the test source. Set trace: 'retain-on-failure' in playwright.config.ts, open trace.zip with npx playwright show-trace or at trace.playwright.dev, and click the failed action to see the page state.

It's Monday morning. Your weekend deployment is being rolled back because a critical test is failing in production that previously passed perfectly in staging. The test works on your machine. It worked on your colleague's machine. But somewhere between your local environment and the CI runner, something went wrong.

You add console.log statements. You increase timeouts. You run the test 50 times locally, hoping to catch it failing just once. Hours pass. You're debugging blind.

This scenario plays out thousands of times daily across engineering teams worldwide. The fundamental problem? Traditional testing gives you a binary result (pass/fail) from a complex, multi-dimensional execution. It's like receiving a "no" to a marriage proposal via text, with no context, explanation, or ability to understand what went wrong.

The Playwright Trace Viewer completely changes this paradigm. Instead of a snapshot of failure, you get a complete recording of everything that happened during your test. Every network request, every DOM mutation, every millisecond of execution is captured in a forensic-quality record that you can analyze offline, share with teammates, and use to debug with surgical precision.

This guide will transform you from someone who fears test failures to someone who welcomes them as opportunities for learning. By the end, you'll not only fix bugs faster, but you'll understand your application's behavior at a level previously impossible.

What Makes Playwright Traces So Powerful?

A trace is powerful because it records the full context of the run, not just the assertion that failed: you can inspect the DOM, the network and the console at the exact action where the test broke, on a run that happened on another machine.

When you enable tracing, Playwright captures everything at multiple levels:

  • Protocol Level: Every interaction between your test and the browser
  • Network Layer: All HTTP traffic with headers and payloads
  • DOM Snapshots: Complete DOM state before and after each action
  • Console Output: Every log, warning, and error
  • Source Mapping: Direct links from actions to your test code

All compressed into a single .zip file (typically 1-50MB) that you can open, share, and analyze anywhere.

How Do You Set Up Complete Tracing From Zero?

Set the trace option in the use block of playwright.config.ts; 'retain-on-failure' records every test and keeps the trace only when a test fails, which is the right default for CI.

What's the Initial Trace Viewer Configuration?

Start with the recommended configuration that balances information capture with performance:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  retries: process.env.CI ? 2 : 0,
  use: {
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure',
    video: 'off',
  },
});

Which Trace Mode Should You Use?

Each trace mode serves a specific purpose:

  • 'off': No tracing (for production smoke tests)
  • 'on': Always trace (for new test development, can generate 1GB+ for large suites)
  • 'retain-on-failure': The sweet spot for CI/CD (minimal overhead for passing tests)
  • 'on-first-retry': Smart for flaky tests (skips first run, captures retry)

How Do You Enable Debugging with Programmatic Tracing?

Sometimes you need to trace a specific section of a complex test:

import { test } from '@playwright/test';

test('checkout', async ({ page, context }) => {
  await page.goto('/cart');
  await context.tracing.start({ screenshots: true, snapshots: true, sources: true });
  await page.getByRole('button', { name: 'Pay now' }).click();
  await context.tracing.stop({ path: 'test-results/checkout-trace.zip' });
});

How Can You Automate Trace Analysis?

For teams serious about quality, automate trace analysis to catch issues early:

# fail the job when a trace grows past 50 MB
find test-results -name 'trace.zip' -size +50M -print -quit | grep . && exit 1 || true

This helps you proactively identify performance regressions and bloated traces before they become problems.

How Do You Understand the Trace Viewer Interface?

Open a trace with npx playwright show-trace test-results/<test-name>/trace.zip, or drop the zip file on trace.playwright.dev; the viewer shows the actions on the left, the timeline at the top, the DOM snapshot in the centre and the Source, Console, Network and Log tabs at the bottom.

When you open a trace, either locally with npx playwright show-trace or at trace.playwright.dev , you're greeted with the debugging interface. Let's walk through a real debugging session with a TODO app test .

# open one trace
npx playwright show-trace test-results/todo-filters-chromium/trace.zip

# or open the HTML report and click the trace icon of a failed test
npx playwright show-report

Following the Test Story Through Actions

The left sidebar reveals the complete story of your test. In our TODO app example, "Before Hooks" dominates at 644ms - nearly two-thirds of the total test time.

Click through the actions: locator.check (28ms) checks a TODO item. Then page.waitForFunction (3ms) waits for the UI to update. The highlighted "Showing all items" (99ms) is where we are now. That duration suggests an API call is being made behind the scenes.

Each action is a breadcrumb. "Showing active items" (27ms), "Showing completed items" (33ms) - The test cycles through the TODO filters, testing each view.

Decoding Performance Patterns in the Timeline

Decoding Performance Patterns in the Timeline

The timeline makes performance issues impossible to miss. That massive bar eating up the first 613ms? That's your setup time before anything is loaded. Dead air for users. The actual test actions cluster together afterward, looking tiny in comparison. No overlapping bars means everything runs sequentially, which is another optimization opportunity.

Those small gaps between actions? That's Playwright's auto-waiting in action, ensuring elements are ready before interacting with them.

The DOM Time Machine

The DOM Time Machine

The center panel is an interactive DOM snapshot. Our TODO app shows three items: "buy some cheese", "feed the cat", and "book a doctors appointment".

Click "Before" - all checkboxes unchecked. Click "After" - "buy some cheese" is now checked. Hover over any element to see the exact selector Playwright would use, e.g., [data-testid="todo-item-1"].

This is invaluable for debugging. Element not found? The DOM snapshot shows you precisely what was there instead.

Understanding Playwright's Decision Process

Understanding Playwright's Decision Process

The logs tab reveals Playwright's thinking:

  • waiting for getByRole('link', { name: 'All' })
  • locator resolved to <a href="#/" class="selected">All</a>
  • attempting click action
  • waiting for element to be visible, enable, and stable
  • element is visible, enabled and stable
  • scrolling into view if needed
  • done scrolling
  • performing click action
  • click action done
  • waiting for scheduled navigations to finish
  • navigations have finished

Eleven distinct checks explain why "Showing all items" took 94ms. This isn't slowness, it's Playwright being thorough.

Network Traffic Reveals Hidden Conversations

Network Traffic Reveals Hidden Conversations

The Network tab's requests tell another story. You can see:

  • Initial page loads (HTML, CSS, JavaScript)
  • Any API calls for TODO operations
  • Failed requests highlighted in red

Each request shows timing, size, and status. Click on any request to view the full headers and payloads.

Putting It All Together

Each panel tells part of the story. Actions show what your test did. Timeline reveals where time went. DOM proves the UI state. Logs explain Playwright's waits. Console captures app behavior. Network uncovers API interactions.

Combined, they give you insight into your test execution. That 644ms setup time? The TODO app initializing. The 99ms filter change? DOM updates and re-rendering.

This is the Trace Viewer's power: complete visibility into every aspect of your test.

How Do You Eliminate Flaky Tests with Traces?

Set trace: 'on-first-retry' or 'retain-on-failure', run the flaky test until you hold one passing and one failing trace, and compare the two at the last action they share; the network timing, the DOM snapshot or the log lines show which condition differs.

Flaky tests waste more engineering time than any other testing problem. Traces provide the evidence needed to fix them systematically.

Capturing Both Success and Failure States

Run your flaky test multiple times until you capture both passing and failing traces:

npx playwright test tests/checkout.spec.ts --repeat-each=20 --trace=on --workers=1

Open a passing and failing trace in separate browser tabs. Focus on the moments just before divergence - often the last successful action before the failure.

Common Flakiness Patterns and Fixes

Race Condition with Network Requests

In the failing trace, you'll see the Network tab shows an API call completing after a UI interaction. In the passing trace, it completes before. The Metadata logs confirm Playwright clicked before data arrived.

Fix:

await Promise.all([
  page.waitForResponse((r) => r.url().includes('/api/items') && r.ok()),
  page.getByRole('button', { name: 'Refresh' }).click(),
]);

Element Not Stable

The Timeline shows longer durations for the same action between runs. The Metadata logs reveal multiple "element is not stable" messages before success. This indicates animation or dynamic positioning.

Fix:

const item = page.getByTestId('todo-item-1');
await expect(item).toBeVisible();
await item.click(); // auto-wait handles the animation; avoid { force: true }

Timing-Dependent State

The DOM snapshots show different initial states between runs. One trace starts with cached data visible, another with a loading spinner. Your test assumes consistent starting conditions.

Fix:

await page.goto('/');
await expect(page.getByTestId('loading')).toBeHidden();
await expect(page.getByRole('listitem')).toHaveCount(3);

Traces show you precisely what differs between passing and failing runs. Instead of guessing, you see the precise moment and reason for divergence.

What Advanced Debugging Patterns Should You Know?

Three patterns cover most hard cases: trace only the suspect section of a long test with context.tracing.start and stop, compare a new trace with an old one to find a slow action, and read the CI trace when a test passes locally and fails in CI.

Binary Search for Long Tests

When a 5-minute test fails at an unknown point, use programmatic tracing to isolate the problem:

await context.tracing.start({ snapshots: true, screenshots: true });
await runSuspectSteps(page);
await context.tracing.stop({ path: `test-results/${test.info().title}-section.zip` });

This generates a focused trace of just the failing section, keeping file sizes manageable and analysis targeted.

Performance Regression Detection

Traces aren't just for failures. Use them to catch performance degradation:

The Timeline immediately shows which actions grew slower. The Network tab often reveals new, unoptimized API calls. The Metadata logs may show an increase in retry attempts.

Debugging CI-Only Failures

When tests pass locally but fail in CI, the trace becomes your remote debugger. Common discoveries:

  • Different viewport sizes: DOM snapshots show elements overlapping in CI's headless browser
  • Missing environment variables: Network tab shows 401 errors from missing auth tokens
  • Slower machines: Timeline shows timeouts that don't occur on fast developer machines
  • Different browser versions: Console tab reveals JavaScript errors from unsupported features

Always configure CI to preserve traces for failed tests. The 30MB trace file saves hours of "works on my machine" debugging.

How Do You Integrate Traces with CI/CD?

Keep trace: 'retain-on-failure' in the config, upload the test-results folder as a build artifact when the job fails, and open the downloaded trace.zip locally with npx playwright show-trace.

GitHub Actions Configuration

This workflow runs the tests and uploads the traces of the failed tests as an artifact:

name: e2e
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-traces
          path: test-results/
          retention-days: 7

Automated Failure Analysis

Create a simple reporter that processes failures and notifies your team:

Pull Request Integration

Add trace links directly to PR comments when tests fail:

This transforms your PR review process. Instead of vague "tests are failing" comments, reviewers gain direct access to traces that show exactly what broke.

Best Practices for Production Use

In production use, record traces only on failure, set a retention period for the artifacts, and make the trace file the standard attachment of a test bug report.

Storage Management

Trace file sizes vary widely depending on the app, assets, and configuration. Best practices:

  • Use retain-on-failure in CI (only failed tests generate traces)
  • Set retention limits: 7 days for feature branches, 30 days for main
  • Exclude screenshots when debugging logic issues: { screenshot: 'off' }
  • Archive important traces (production failures) to long-term storage

Team Adoption

Make traces part of your workflow:

  1. Bug Reports: Require trace files for all test failure reports
  2. Code Reviews: Check traces when reviewing test changes
  3. Debugging Sessions: Share traces in Slack instead of screenshots
  4. Post-Mortems: Use traces to understand cascade failures

The Playwright Trace Viewer transforms test debugging into knowledge - you no longer guess what happened - you see it, interact with it, and understand it completely.

Start with trace: 'retain-on-failure' today. When your next test fails, instead of adding console.log statements, open the trace. You'll find the root cause in minutes, not hours.

The investment in setting up proper tracing pays off immediately. Every mysterious failure becomes a learning opportunity. Every flaky test becomes fixable. Every "works on my machine" becomes "here's exactly what happened."

Master the Trace Viewer, and you master test debugging.

Playwright trace viewer

What is the trace viewer in Playwright?

The Playwright Trace Viewer is a GUI that replays a recorded test run. It shows each action, a DOM snapshot before and after it, the network requests, the console output, the log and the test source, so you can see why a step failed.

How do I open a Playwright trace?

Run npx playwright show-trace path/to/trace.zip, or open trace.playwright.dev in a browser and drop the zip file on the page. The HTML report also holds a trace link for each failed test.

How do I enable tracing in Playwright?

Set trace in the use block of playwright.config.ts, for example trace: 'retain-on-failure'. For one section of a test, call context.tracing.start() before it and context.tracing.stop({ path }) after it.

Which trace mode should I use in CI?

Use 'retain-on-failure' for most suites: every test is recorded and the trace is kept only when the test fails. Use 'on-first-retry' when you run retries and want a trace of the retry only, at a lower cost.

Where does Playwright save the trace file?

Playwright writes trace.zip into the test-results folder, in a subfolder named after the test file, the test title and the project. In CI, upload that folder as an artifact when the job fails.

Can I open a trace without installing Playwright?

Yes. trace.playwright.dev opens a trace.zip file in the browser. The file is processed locally in the browser tab, so it works for a trace a teammate sent you.

Still have additional questions?

Contact us

Close the feedback loop.