All posts

15 Days of Playwright - Day 11: Debugging with Trace Viewer and UI Mode

Turn every test failure into an open book. Learn how Playwright's Trace Viewer and UI Mode give you step-by-step replay, network logs, and interactive test exploration — all without adding a single console.log.

Jhonatas Matos

Jhonatas Matos

15 Days of Playwright - Day 11: Debugging with Trace Viewer and UI Mode

Every automation engineer has stared at a failing CI job with no clue what went wrong. Fixed timeouts, added logs, re-ran it three times — and finally gave up calling it "flaky." Today, that ends.

Playwright ships two debugging superpowers that most engineers underuse: Trace Viewer and UI Mode. Together they replace console.log debugging with a full timeline replay of every action, network call, screenshot, and DOM snapshot your test ever produced.

What Is the Trace Viewer?

The Playwright Trace Viewer is an offline, interactive report that records everything that happened during a test run:

  • Every action (click, fill, goto) with before/after screenshots
  • Full network request/response logs
  • Console messages
  • The DOM snapshot at the moment of each action
  • Test durations and failure stack traces

It is stored as a .zip file and opened in a browser. No server, no infrastructure — just a local file.

Enabling Traces in playwright.config.js

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

export default defineConfig({
  use: {
    // Collect a trace on the first retry of a failed test (recommended for CI)
    trace: 'on-first-retry',
  }
});

The four trace modes:

| Mode | When to use | |---|---| | 'off' | Production CI where disk space is limited | | 'on' | Always trace — great for local debugging | | 'retain-on-failure' | Only keep traces for failing tests | | 'on-first-retry' | Recommended: trace only on retry (balances cost vs. insight) |

Enabling Traces Inline

You can also enable tracing for a specific test block without changing the global config:

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

test('checkout flow', async ({ page, context }) => {
  await context.tracing.start({ screenshots: true, snapshots: true });

  await page.goto('https://bugbank.netlify.app');
  // ... test actions ...

  await context.tracing.stop({ path: 'trace.zip' });
});

Opening the Trace Viewer

After a run, Playwright saves traces to the test-results/ directory. Open any trace with:

npx playwright show-trace test-results/my-test/trace.zip

Or point it at the HTML report directory:

npx playwright show-report

The report opens in your browser and lists every test. Click any failing test to open its trace.

Navigating the Trace Viewer

The interface has five panels:

  1. Timeline — scrub through time; thumbnails show the page at every moment
  2. Actions — left sidebar listing every Playwright call with its duration
  3. Before/After screenshots — side-by-side of page state before and after each action
  4. Network — every HTTP request with status, timing, headers, and body
  5. Console — all console.log, console.error, and warnings

Reading a Failure in the Trace Viewer

When a test fails, Playwright marks the failing action in red. Click it to see:

  • The exact selector that timed out
  • The DOM snapshot at the moment of failure (was the element missing? hidden? disabled?)
  • Network requests fired right before the failure (did an API call return 500?)
  • The assertion error message

This gives you the full story without re-running the test.

UI Mode — Interactive Test Development

UI Mode (npx playwright test --ui) launches a desktop-style interface for running and debugging tests interactively. Think of it as a test browser with a built-in inspector.

npx playwright test --ui

What UI Mode Gives You

  • Test tree — browse all your test files and describe blocks
  • Run individual tests with one click
  • Live watch mode — tests re-run automatically when you save a file
  • Pick locator — hover over any element in the preview pane and get the recommended Playwright locator
  • Full trace — every run inside UI Mode automatically records a trace you can inspect immediately

The Locator Picker

One of the most useful features: click the crosshair icon, then hover over any element in the live browser preview. UI Mode suggests the best locator for that element:

Recommended: page.getByRole('button', { name: 'Cadastrar' })
Also valid:  page.locator('#register-btn')

This ends the guessing game of "which selector should I use?"

Comparing: --debug vs Trace Viewer vs UI Mode

┌─────────────────┬──────────────────────────────────────────────┐
│ Tool            │ Best for                                     │
├─────────────────┼──────────────────────────────────────────────┤
│ --debug         │ Stepping through a test line by line (local) │
│ Trace Viewer    │ Post-mortem analysis of CI failures          │
│ UI Mode         │ Writing and iterating on new tests           │
└─────────────────┴──────────────────────────────────────────────┘

Playwright Inspector (--debug)

npx playwright test tests/day11.test.js --debug

Opens a step-by-step debugger. Use the "Step Over" button to advance one action at a time and inspect the page live. Great for authoring, not for CI failures.

Real-World Debugging Workflow

Here is the exact workflow I use when a CI test fails:

  1. Reproduce locally — run with --headed to see the browser:

    npx playwright test tests/checkout.spec.js --headed
    
  2. Enable trace for that test — add trace: 'on' to the test's use block or run with --trace on:

    npx playwright test tests/checkout.spec.js --trace on
    
  3. Open the trace — find the .zip in test-results/ and open it:

    npx playwright show-trace test-results/checkout-spec/trace.zip
    
  4. Identify the failing action — click the red action in the timeline; check:

    • Was the element present in the DOM snapshot?
    • Did a network call fail right before?
    • Was there a console error?
  5. Fix + verify in UI Mode:

    npx playwright test --ui
    

Practical Example: Debugging a Flaky Test

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

test('balance loads after login', async ({ page, context }) => {
  // Enable trace for this specific test
  await context.tracing.start({ screenshots: true, snapshots: true });

  await page.goto('https://bugbank.netlify.app');
  await page.getByText('Acessar').click();
  await page.getByPlaceholder('Informe seu e-mail').fill('user@example.com');
  await page.getByPlaceholder('Informe sua senha').fill('password123');
  await page.getByRole('button', { name: 'Acessar' }).click();

  // This assertion was flaky — the trace will show us exactly why
  await expect(page.locator('.balance-amount')).toBeVisible({ timeout: 10000 });

  await context.tracing.stop({ path: 'trace-balance.zip' });
});

Open trace-balance.zip after a failure: if the network panel shows the balance API returned 401, that's your bug — not a timing issue. The trace tells the truth.

Best Practices

Always Enable Traces in CI

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

Keep Traces Small

Traces record screenshots at every action. For long tests, use snapshots: false if you only need network logs:

await context.tracing.start({ screenshots: false, snapshots: false });

Use UI Mode for New Test Development

Never start a new test with npx playwright test. Start with npx playwright test --ui. You'll write better tests, faster, because the locator picker and live preview eliminate guesswork.

Link to GitHub projectGitHub Octocat

Conclusion

Debugging isn't about adding more logs — it's about having the right tools. With Trace Viewer and UI Mode you now have:

  • A full timeline replay of every test failure, including screenshots and network logs
  • An interactive development environment for writing tests without guessing at locators
  • A step-by-step inspector for tracing complex flow issues locally

Stop flying blind in CI. Enable traces, and let the evidence speak for itself.


Thank you for reading and see you in the next lesson! ☕💜