Debugging August 5, 2026 15 min read

How to Debug Playwright Tests: The Complete Debugging Guide for 2026

A failing test with no clear diagnosis wastes more engineering time than writing the test in the first place. This guide covers every Playwright debugging tool — Inspector, Trace Viewer, page.pause(), VS Code, and AI-assisted fixing — so you can go from red pipeline to root cause in minutes, not hours.

You wrote the test. It passed three times. You merged it. Now it fails in CI and nobody knows why. Or worse: it fails randomly, passes on retry, and slowly erodes the team's trust in the entire suite.

Debugging Playwright tests is a skill that separates productive test engineers from those stuck in a cycle of console.log, guess, and re-run. Playwright ships with world-class debugging tools — most teams just never learn to use them properly.

This guide walks through every tool in the Playwright debugging arsenal, from the basics to advanced CI diagnosis and AI-assisted auto-fixing. By the end, you'll have a systematic approach for any failure type.


The 5 Most Common Playwright Failures

Before reaching for debugging tools, it helps to recognize the failure pattern. Roughly 90% of Playwright test failures fall into five categories:

1. Timeout Error

The most frequent failure. Playwright waited for an element or navigation, exceeded the configured timeout, and threw TimeoutError. This usually means the element never appeared, the page didn't navigate, or a network request hung.

Typical timeout error
TimeoutError: locator.click: Timeout 30000ms exceeded.
Call log:
  - waiting for getByRole('button', { name: 'Submit' })
  -   locator resolved to 0 elements
  -   waiting for locator

Root cause: The selector doesn't match anything on the page. Either the element hasn't rendered yet, or the locator is wrong.

2. Selector Not Found

Similar to timeouts, but specifically when a locator resolves to zero elements. This often happens after a UI redesign where button text, labels, or component structure changed.

3. Assertion Failed

The element exists, but its state doesn't match your expectation. expect(page).toHaveURL() got a different URL. expect(locator).toHaveText() got different text. These are logic errors — the test expected one thing, the app did another.

Assertion failure example
Error: expect(received).toHaveText(expected)

Expected: "Welcome back, Alice"
Received: "Welcome back, Bob"

Call log:
  - expect.toHaveText with timeout 5000ms
  - waiting for getByRole('heading', { level: 1 })

4. Navigation Error

Pages that fail to load, redirect loops, or net::ERR_CONNECTION_REFUSED. Common in CI when the dev server hasn't fully started, or when the base URL is misconfigured.

5. Flaky Test

The test passes sometimes and fails other times with no code change. This is the hardest to debug because it's non-deterministic. Usually caused by race conditions, shared state between tests, animation timing, or slow network responses.

Before you debug: Read the full error message and call log. Playwright's error output is exceptionally detailed — it tells you exactly which locator it was waiting for, how many elements matched, and what it tried. Most engineers skip straight to adding console.log without reading what Playwright already told them.


Tool 1: Playwright Inspector

The Playwright Inspector is an interactive GUI debugger that lets you step through test actions one at a time, inspect selectors against the live page, and see exactly what Playwright sees at each step. It's the single most useful debugging tool Playwright ships with.

Launch with --debug

Terminal
# Debug all tests
npx playwright test --debug

# Debug a specific test file
npx playwright test tests/login.spec.ts --debug

# Debug a specific test by title
npx playwright test -g "should login with valid credentials" --debug

This opens two windows: the browser and the Inspector panel. The test pauses before the first action and waits for you to click Step Over to advance.

Step Through Actions

Each step in the Inspector shows:

  • The action — click, fill, navigate, expect
  • The locator — the exact selector being used
  • The target element — highlighted on the page with a blue overlay
  • The result — success, pending, or error

You can click Step Over to execute one action, Resume to run until the next breakpoint (or the end), and Record to generate new locators by clicking elements on the page.

Inspect Selectors Live

The most powerful Inspector feature: type any locator into the Explore field and see it highlighted on the page in real time. This lets you test alternative selectors without modifying code.

Inspector explore field
// Type these into the Explore box to test
getByRole('button', { name: 'Submit' })     // Matches 1 element
getByRole('button', { name: 'Send' })       // Matches 0 elements
getByLabel('Email address')                  // Matches 1 element
getByTestId('login-form')                   // Matches 1 element

Pro tip: When a test fails because a selector matches 0 elements, open the Inspector, navigate to the failing page state, and use the Explore field to find the correct selector. Then copy it directly into your test. This is faster than guessing and re-running.


Tool 2: Trace Viewer

The Trace Viewer is Playwright's post-mortem debugger. While the Inspector is for live debugging, Trace Viewer lets you replay a test execution after it's finished — including tests that failed in CI where you can't open a browser window.

Enable Traces in Config

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

export default defineConfig({
  use: {
    // 'on-first-retry' — only captures trace on first retry (recommended)
    // 'on' — captures trace for every test (large files)
    // 'retain-on-failure' — keeps trace only for failed tests
    trace: 'on-first-retry',
  },
  retries: 2,
});

The recommended setting is 'on-first-retry' with at least 1 retry configured. This captures a trace only when a test fails and is retried, keeping file sizes manageable while ensuring you have diagnostic data for every failure.

Open a Trace File

Terminal
# Open a specific trace file
npx playwright show-trace test-results/login-test/trace.zip

# Or open the HTML report, then click "Trace" on any failed test
npx playwright show-report

Read the Timeline & DOM Snapshots

Trace Viewer gives you four panels:

  • Timeline — a horizontal bar showing every action with duration. Click any action to jump to that point.
  • DOM Snapshot — the page state before and after each action. You can inspect the full DOM, including elements that were hidden or removed.
  • Network — every HTTP request with status, timing, headers, and response body. Essential for diagnosing API-dependent test failures.
  • Console — browser console output at each action. Shows JavaScript errors, warnings, and your console.log statements.

Key workflow: When a test fails in CI, download the trace artifact, open it with npx playwright show-trace, click the failing action in the timeline, then toggle between "Before" and "After" snapshots to see exactly what state the page was in when the action was attempted.

Trace Viewer for Assertion Failures

For assertion failures, the trace shows you the element's actual text/state at the moment the assertion was evaluated. Combined with the "Before" snapshot, you can see whether the page hadn't finished loading, showed stale data, or rendered something unexpected.

playwright.config.ts — full trace setup for CI
export default defineConfig({
  use: {
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  retries: process.env.CI ? 2 : 0,
});

Tool 3: page.pause()

Sometimes you need to pause the test at a specific point in execution — not at the beginning, but after login, after navigating to a page, or after triggering a specific action. That's what page.pause() is for.

Using page.pause() in a test
import { test, expect } from '@playwright/test';

test('debug checkout flow', async ({ page }) => {
  await page.goto('https://shop.example.com');
  await page.getByRole('button', { name: 'Add to Cart' }).click();
  await page.getByRole('link', { name: 'Checkout' }).click();

  // Pause here — inspect the checkout page state
  await page.pause();

  // These lines won't execute until you resume in the Inspector
  await page.getByLabel('Card number').fill('4242424242424242');
  await page.getByRole('button', { name: 'Pay' }).click();
});

When execution hits page.pause(), the Playwright Inspector opens (if it isn't already) and pauses. You can:

  • Inspect the DOM — use browser DevTools or the Inspector's Explore field
  • Test selectors — type locators into Explore to verify they match
  • Check network state — verify pending requests in DevTools
  • Step through remaining actions — click Step Over to execute one action at a time

Don't commit page.pause(): Remove all page.pause() calls before committing. If one slips into CI, the test will hang indefinitely waiting for a human to click Resume. Consider adding a lint rule to catch it: no-restricted-syntax for page.pause().

Conditional Pausing

For debugging intermittent issues, you can pause only when a specific condition is met:

Conditional pause
const cartCount = await page.getByTestId('cart-count').textContent();

// Only pause if the cart is unexpectedly empty
if (cartCount === '0') {
  console.log('Cart is empty — pausing for inspection');
  await page.pause();
}

Tool 4: Console Logs & Screenshots

Sometimes the simplest tools are the most effective. Capturing browser console output and taking screenshots at strategic points can reveal issues that other tools miss.

Capture Browser Console Output

Listening to console events
test('track console errors during checkout', async ({ page }) => {
  // Collect all console messages
  const logs: string[] = [];
  page.on('console', (msg) => {
    if (msg.type() === 'error') {
      logs.push(`[ERROR] ${msg.text()}`);
    }
  });

  // Also catch uncaught exceptions
  page.on('pageerror', (err) => {
    logs.push(`[UNCAUGHT] ${err.message}`);
  });

  await page.goto('/checkout');
  await page.getByRole('button', { name: 'Pay' }).click();

  // Assert no console errors occurred
  expect(logs).toEqual([]);
});

This pattern is especially useful for catching JavaScript errors that silently break functionality without causing visible failures — like a payment SDK failing to load or a tracking pixel throwing CORS errors.

Screenshot on Failure

Playwright can automatically capture screenshots when tests fail — this is also the foundation of visual regression testing. Configure it globally:

playwright.config.ts
export default defineConfig({
  use: {
    screenshot: 'only-on-failure',  // auto-capture on failure
  },
});

For strategic debugging, take manual screenshots at specific points:

Manual screenshots
// Full page screenshot
await page.screenshot({ path: 'debug-checkout.png', fullPage: true });

// Screenshot of a specific element
await page.getByTestId('order-summary').screenshot({
  path: 'debug-order-summary.png'
});

Video Recording

For complex multi-step failures, video can be more revealing than screenshots:

playwright.config.ts
export default defineConfig({
  use: {
    video: 'retain-on-failure',  // keeps video only for failed tests
  },
});

Trace vs. video vs. screenshot: Use trace for deep debugging (DOM snapshots + network + console). Use video for quick visual confirmation of what went wrong. Use screenshot when you just need the final page state. In CI, trace: 'on-first-retry' + screenshot: 'only-on-failure' is the best default. For more on output options, see our test reporting tools guide.


Tool 5: VS Code Extension

The official Playwright Test for VS Code extension turns your editor into a full debugging environment. It's the most ergonomic way to debug Playwright tests for day-to-day development.

Install & Setup

Install "Playwright Test for VS Code" from the extensions marketplace (publisher: Microsoft). It auto-detects your playwright.config.ts and populates the Test Explorer sidebar.

Set Breakpoints

Click the gutter next to any line in your test file to set a breakpoint. Then right-click the test in the Test Explorer and choose "Debug Test". The test runs until it hits your breakpoint, then pauses with the full VS Code debugger — variables panel, call stack, watch expressions, and the live browser.

Debugging with breakpoints
test('login flow', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('alice@example.com');
  await page.getByLabel('Password').fill('secret123');
  await page.getByRole('button', { name: 'Sign In' }).click();

  // Set breakpoint here ← to inspect page state after login
  await expect(page).toHaveURL('/dashboard');    // ← breakpoint
  await expect(page.getByRole('heading')).toHaveText('Welcome');
});

Run Individual Tests

The extension adds green play buttons next to each test() block. Click to run that single test. Hold Shift+click to run in debug mode. This eliminates the need to type long CLI commands with -g grep filters.

Watch Mode

Enable Watch Mode in the Test Explorer to auto-rerun tests when you save file changes. Combined with the "Show Browser" checkbox, you get a live preview of your test running in a real browser every time you save.

VS Code workflow
1. Show Browser = checked
2. Set breakpoint on failing line
3. Click "Debug Test" play button
4. Inspect variables + page state
Slow workflow
1. Add console.log() everywhere
2. Run full suite from terminal
3. Read terminal output
4. Repeat 20 times

Debugging Flaky Tests

Flaky tests are the most demoralizing debugging challenge. They pass in isolation, fail in parallel. They pass locally, fail in CI. They pass 9 times, fail on the 10th. Our Playwright best practices guide covers prevention strategies, but when flaky tests slip through, here's a systematic approach to nail them down.

Step 1: Reproduce Reliably

Terminal
# Run the test 50 times — if it fails even once, you've confirmed the flake
npx playwright test tests/checkout.spec.ts --repeat-each=50

# Run with retries to see how often it fails
npx playwright test tests/checkout.spec.ts --retries=5 --repeat-each=20

If the test fails 2 out of 50 runs, you've confirmed a 4% flake rate. Now you need to capture a trace on the failing run.

Step 2: Capture the Failure

Terminal
# Enable trace on first retry + repeat 50 times
npx playwright test tests/checkout.spec.ts \
  --repeat-each=50 \
  --retries=1 \
  --trace=on-first-retry

When the test fails and retries, Playwright captures a trace of the retry. Open it with npx playwright show-report and click the failed test to examine the trace.

Step 3: Identify the Race Condition

The most common causes of flaky Playwright tests:

  • Animation timing — An element is animating (sliding, fading) when Playwright tries to click it. Fix: page.emulateMedia({ reducedMotion: 'reduce' }) or wait for animation completion.
  • Network race — The test acts before an API response arrives. Fix: await page.waitForResponse('**/api/cart') before asserting.
  • Shared test data — Two parallel tests use the same user/record. Fix: Create unique data per test using custom fixtures.
  • Stale DOM reference — The DOM re-renders between locator resolution and action execution. Fix: Use Playwright's built-in auto-waiting locators instead of elementHandle.
  • Timing-dependent assertions — Asserting on time-sensitive values (timestamps, counters). Fix: Use toHaveText with regex patterns or toContainText.

Never fix flaky tests with retries alone. Setting retries: 3 masks the problem. Use retries as a safety net, but always investigate and fix the root cause. A test that needs 3 retries to pass is telling you something is wrong.


Debugging in CI

"It works on my machine" is the most expensive sentence in test automation. When tests pass locally but fail in CI, follow this systematic diagnosis.

The CI Debugging Checklist

  1. Check the timeout: CI machines are typically 2–5x slower than your laptop. Increase actionTimeout and navigationTimeout for CI.
  2. Check the browser: Are you running the same browser version locally and in CI? Run npx playwright install in your CI pipeline.
  3. Check the viewport: Set explicit viewport: { width: 1280, height: 720 } in config. CI headless browsers may default to different sizes.
  4. Check the base URL: Is the dev server fully started before tests run? Use webServer config with reuseExistingServer.
  5. Check environment variables: Timezone, locale, API keys — anything that differs between local and CI.
playwright.config.ts — CI-safe configuration
export default defineConfig({
  timeout: process.env.CI ? 60_000 : 30_000,
  expect: {
    timeout: process.env.CI ? 10_000 : 5_000,
  },
  retries: process.env.CI ? 2 : 0,
  use: {
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    viewport: { width: 1280, height: 720 },
  },
  webServer: {
    command: 'npm run start',
    port: 3000,
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,  // give server 2 min to start in CI
  },
});

Upload Artifacts in CI

GitHub Actions — upload traces and reports
- name: Run Playwright tests
  run: npx playwright test

- name: Upload test results
  if: always()  # Upload even when tests fail
  uses: actions/upload-artifact@v4
  with:
    name: playwright-report
    path: playwright-report/
    retention-days: 14

- name: Upload traces
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: test-traces
    path: test-results/
    retention-days: 14

Use the Playwright Docker image: mcr.microsoft.com/playwright:v1.50.0-noble includes all system dependencies pre-installed. This eliminates the #1 cause of "works locally, fails in CI" — missing system libraries for Chromium, Firefox, or WebKit rendering.

Debugging Parallel Failures in CI

If tests only fail when running in parallel (but pass with --workers=1), the problem is shared state:

  • Shared database records — Two tests modify the same user/order. Fix: Each test creates its own data with unique identifiers.
  • Shared files — Two tests write to the same file. Fix: Use temp directories per worker.
  • Port conflicts — Two tests start servers on the same port. Fix: Use dynamic port allocation.
  • Global state — Tests modify a singleton or environment variable. Fix: Reset state in beforeEach.
Isolate by reducing workers
# Run with 1 worker to confirm it's a parallelism issue
npx playwright test --workers=1

# Then gradually increase to find the breaking point
npx playwright test --workers=2
npx playwright test --workers=4

AI-Assisted Debugging

In 2026, AI isn't just for writing tests — it's a powerful debugging tool. Claude AI with the Playwright MCP Server can diagnose failures and auto-fix broken tests by inspecting the live page state.

Paste the Failure Into Claude

When a test fails, copy the full error output (including the call log) and paste it into Claude. A well-structured prompt looks like this:

Prompt for Claude
This Playwright test is failing. Here's the error:

TimeoutError: locator.click: Timeout 30000ms exceeded.
  waiting for getByRole('button', { name: 'Submit Order' })
  locator resolved to 0 elements

The button text was recently changed to "Place Order".
Fix the test selector.

Claude will identify that the button text changed and suggest updating the locator from getByRole('button', { name: 'Submit Order' }) to getByRole('button', { name: 'Place Order' }).

MCP Server for Live DOM Inspection

The Playwright MCP Server gives Claude direct access to your browser. Instead of guessing what the page looks like, Claude can:

  • Navigate to the page and inspect the actual DOM
  • Find the correct selector by examining live elements
  • Verify the fix by running the updated test in the connected browser
  • Handle complex scenarios like dynamic content, shadow DOM, and iframes
AI-assisted debugging workflow
// 1. Claude navigates to the page via MCP
// 2. Claude inspects the DOM and finds the button
// 3. Claude generates the correct locator:

// Before (broken):
await page.getByRole('button', { name: 'Submit Order' }).click();

// After (fixed by Claude + MCP):
await page.getByRole('button', { name: 'Place Order' }).click();

Speed comparison: Manual debugging of a broken selector typically takes 10–30 minutes (find the page, inspect the element, update the test, re-run). With Claude + MCP, the same fix takes under 2 minutes. For teams with 500+ tests, this translates to hours saved per sprint.

Auto-Fix Patterns

AI-assisted debugging works best for these failure types:

  • Broken selectors after UI redesigns — Claude finds the new selector from the live DOM
  • Assertion mismatches — Claude compares expected vs. actual and identifies the root cause
  • Missing waits — Claude recognizes race condition patterns and adds appropriate waits
  • Configuration issues — Claude analyzes config and error together to spot mismatches

Debugging Checklist

Use this quick-reference when a test fails. Work through it top to bottom — most issues resolve within the first few items.

First Response (under 2 minutes)

  • Read the full error message and call log
  • Check which locator failed and how many elements matched
  • Verify the test ran against the correct URL
  • Check if other tests in the same file also fail
  • Look for recent UI changes in git log
  • Run the test in headed mode to see visually

Deep Diagnosis (5-15 minutes)

  • Run with --debug to step through actions
  • Open Trace Viewer for DOM snapshots
  • Check network tab for failed/slow requests
  • Check console for JavaScript errors
  • Test selectors in Inspector Explore field
  • Use page.pause() at the failing step
  • Run with --repeat-each=20 for flake check
  • Run with --workers=1 for parallel issues

CI-Specific Debugging

  • Download trace artifacts from CI
  • Compare CI timeout vs. local timeout
  • Check viewport size in CI config
  • Verify browser version matches local
  • Check webServer startup timeout
  • Review environment variable differences
  • Use Docker image for consistency
  • Run same test locally with --workers=N

Frequently Asked Questions

How do I debug a failing Playwright test?

Start by running the test with --debug flag to open the Playwright Inspector. Step through each action, inspect selectors live, and see the browser state at every point. For tests that already failed (especially in CI), use Trace Viewer to replay the execution with DOM snapshots and network logs.

What is Playwright Trace Viewer and how do I use it?

Trace Viewer is a GUI tool that replays test execution step-by-step. Enable it with use: { trace: 'on-first-retry' } in your config. After a failure, open the trace with npx playwright show-trace trace.zip. You get DOM snapshots, network requests, console logs, and action timing for every step.

Why do my Playwright tests pass locally but fail in CI?

Common causes: (1) CI machines are slower — increase timeouts, (2) Missing system dependencies — use the official Playwright Docker image, (3) Different viewport sizes — set explicit dimensions in config, (4) Dev server not ready — use webServer config with adequate timeout, (5) Environment variable differences — set timezone and locale explicitly.

How do I fix flaky Playwright tests?

First, reproduce the flake with --repeat-each=50. Enable trace recording to capture the failure. Common fixes: replace waitForTimeout with web-first assertions, ensure test isolation with no shared state, handle animations with reducedMotion, and wait for specific network responses before asserting.

Can I use AI to debug Playwright tests?

Yes. With Claude AI and the Playwright MCP Server, you can paste a test failure and get an auto-fix within minutes. The MCP Server gives Claude live access to your browser DOM, so it can inspect the actual page state and generate correct selectors — especially powerful after UI redesigns break multiple tests.


Asim Noaman - Playwright and Claude AI course instructor

Asim Noaman

Senior QA Automation Engineer & AI Testing Specialist

With years of hands-on experience building test automation frameworks for production applications, Asim specializes in combining traditional QA methodologies with cutting-edge AI tools. He has helped teams adopt Playwright and AI-driven testing workflows to ship faster with fewer bugs.

Udemy Instructor Published course author
Playwright + AI Expert Specialized in AI-powered QA
Production Experience Enterprise-grade frameworks
Connect on LinkedIn