Advanced Chapter 20 · 13 min read

Debugging & Tracing

Debug flaky tests with the Playwright Inspector, UI mode, and the Trace Viewer — your complete toolkit for fixing broken tests.

The Playwright Inspector

Setting the environment variable PWDEBUG=1 before running a test launches the Playwright Inspector alongside the browser. The Inspector shows the current test step highlighted, lets you pause and step through actions one at a time, and provides a live locator picker so you can identify the right selector by hovering over page elements. This is the fastest way to diagnose why a test is failing to find an element.

Tip: Insert await page.pause() (JavaScript) or page.pause() (Python) at any point in a test to pause execution and open the Inspector at that exact moment, even when PWDEBUG is not set.

UI Mode

Playwright's UI mode (npx playwright test --ui) provides a graphical test runner with a tree of all test files on the left and an interactive browser on the right. You can run individual tests, watch them execute step by step, and see DOM snapshots, network requests, and console output at each step — all without touching the command line again. UI mode is ideal during test development.

Tracing

Traces are recorded ZIP archives containing a complete timeline of a test run: every action, DOM snapshot, network request, and console message, along with a before/after screenshot for each step. Open a trace with npx playwright show-trace trace.zip or upload it to trace.playwright.dev for a shareable link. Configure tracing in playwright.config.ts with trace: 'on-first-retry' so you always have a trace when a flaky test fails in CI.

debug-commands.sh
# Launch the Inspector for a specific test
PWDEBUG=1 npx playwright test tests/login.spec.ts

# Run in headed slow-motion (500ms between actions)
npx playwright test --headed --slow-mo=500

# Open UI mode (graphical test runner)
npx playwright test --ui

# Generate test code by recording browser actions
npx playwright codegen http://localhost:3000

# View a trace file
npx playwright show-trace test-results/trace.zip
tracing.spec.js
import { test, expect } from '@playwright/test';

// Manual trace control — useful in complex scenarios
test('checkout flow with tracing', async ({ page, context }) => {
  // Start trace with all options
  await context.tracing.start({
    screenshots: true,
    snapshots: true,
    sources: true,
  });

  await page.goto('/cart');
  await page.getByRole('button', { name: 'Checkout' }).click();
  await page.getByLabel('Card number').fill('4111111111111111');

  // Pause here during development to inspect state
  // await page.pause();

  await page.getByRole('button', { name: 'Pay' }).click();
  await expect(page.getByText('Order confirmed')).toBeVisible();

  // Stop and save trace
  await context.tracing.stop({ path: 'test-results/checkout-trace.zip' });
});
TracingTest.java
import com.microsoft.playwright.*;
import java.nio.file.Paths;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;

class TracingTest extends BaseTest {

  @Test
  void checkoutWithTrace() {
    // Start tracing for this context
    context.tracing().start(new Tracing.StartOptions()
        .setScreenshots(true)
        .setSnapshots(true)
        .setSources(true));

    try {
      page.navigate("/cart");
      page.getByRole(AriaRole.BUTTON,
          new Page.GetByRoleOptions().setName("Checkout")).click();
      page.getByRole(AriaRole.BUTTON,
          new Page.GetByRoleOptions().setName("Pay")).click();
      assertThat(page.getByText("Order confirmed")).isVisible();
    } finally {
      // Always save trace, even on failure
      context.tracing().stop(new Tracing.StopOptions()
          .setPath(Paths.get("test-results/checkout-trace.zip")));
    }
  }
}
test_tracing.py
import pytest
from playwright.sync_api import Page, BrowserContext, expect

@pytest.fixture
def traced_context(context: BrowserContext):
    # Start trace before each test
    context.tracing.start(
        screenshots=True, snapshots=True, sources=True
    )
    yield context
    # Stop and save trace after each test
    context.tracing.stop(path="test-results/trace.zip")

def test_checkout_with_trace(traced_context: BrowserContext):
    page = traced_context.new_page()
    page.goto("/cart")
    page.get_by_role("button", name="Checkout").click()
    page.get_by_label("Card number").fill("4111111111111111")
    page.get_by_role("button", name="Pay").click()
    expect(page.get_by_text("Order confirmed")).to_be_visible()

# Debug commands:
# PWDEBUG=1 pytest test_tracing.py          # Launch Inspector
# pytest --headed --slowmo=500              # Slow motion headed
# pytest --tracing=on                       # Always record traces

Playwright Advanced Debugging & Tracing

Written by PV

© 2026 All Rights Reserved