SoftwareTestPilot
Module 01 · Lesson 4beginner 10 min read Debugging

How to Read a Failing Playwright Test

Learn to diagnose failures in under a minute using Trace Viewer, screenshots, and error output. Distinguish real product bugs from flaky tests and locator drift like a senior SDET.

Quick answer

Playwright failure output has four parts: the error message, the call stack, the step log, and (if configured) a trace.zip. Open the trace with npx playwright show-trace and scrub — it shows the DOM, network, and console at every step. 9 out of 10 failures are diagnosed inside Trace Viewer.

1. Anatomy of a failure message

Here's a typical failure. Read it top-down:

  1) [chromium] › tests/todo.spec.ts:12:3 › adds a todo to the demo app

    Error: expect(locator).toHaveText(expected)

    Locator:  locator('[data-testid="todo-title"]')
    Expected: "Write my first Playwright test"
    Received: "Write my frist Playwright test"
    Timeout:  5000ms

    Call log:
      - expect.toHaveText with timeout 5000ms
      - waiting for locator('[data-testid="todo-title"]')
      -   locator resolved to <li data-testid="todo-title">…</li>
      -   unexpected value "Write my frist Playwright test"

      12 |    await input.press('Enter');
      13 |    const todo = page.getByTestId('todo-title');
    > 14 |    await expect(todo).toHaveText('Write my first Playwright test');
         |                       ^
      15 |  });

    attachment #1: screenshot (image/png) ────────────
    test-results/todo-adds-a-todo/test-failed-1.png
    attachment #2: trace (application/zip) ──────────
    test-results/todo-adds-a-todo/trace.zip
  • Line 1 — browser · file · line · title. Copy this into your ticket.
  • Expected vs Received — the actual diff. Here: a real typo in the app.
  • Call log — every retry Playwright made until timeout.
  • Code frame — the exact line, arrow-pointed.
  • Attachments — screenshot at failure + trace.zip for time-travel.

2. Trace Viewer — the fastest debug tool ever built

  1. 1

    Open the trace

    npx playwright show-trace test-results/todo-adds-a-todo/trace.zip
    # or from the HTML report
    npx playwright show-report
  2. 2

    Scrub the timeline

    Every action becomes a step in the top timeline. Click one and the browser snapshot on the right shows the exact DOM at that moment.

  3. 3

    Use Actions / Metadata / Console / Network tabs

    • Actions — what Playwright did.
    • Metadata — browser, viewport, run duration.
    • Console — page-side JS errors (often the smoking gun).
    • Network — every request/response with headers and payload.
    • Source — the test file with the failing line highlighted.
  4. 4

    Pick a locator against the snapshot

    Click the "Pick locator" button and hover the failing element in the snapshot. Playwright suggests a stable selector — copy it into the test.

3. Common error messages and what they mean

ErrorReal meaningFix
strict mode violation: resolved to 3 elementsLocator matched more than one nodeAdd getByRole + name, .first(), or .filter({ hasText })
Timeout of 30000ms exceededTest as a whole took too longSplit test or increase test timeout in config
locator resolved but action timed outElement exists but is not clickable (overlay / disabled / animating)Wait for toBeVisible + toBeEnabled first, or scroll into view
Target page, context or browser has been closedTest ended (probably threw) before assertion completedAdd missing await; check for early return
expect(received).toEqual(expected)Values differ — real assertion failureRead Expected vs Received diff carefully
net::ERR_CONNECTION_REFUSEDApp not running on that host/portStart the dev server or check baseURL

4. Bug vs flaky vs locator drift — a 30-second triage

  1. 1

    Re-run the failing test 3× locally

    npx playwright test --repeat-each=3 -g "test name". Fails every time → probably a real bug. Fails sometimes → flaky.

  2. 2

    Open the trace

    Real bug → the Expected/Received diff points at product behaviour. Flaky → the step succeeded on retry, or a network call was pending.

  3. 3

    Check the locator

    If the failing locator no longer exists in the DOM snapshot → locator drift. Redesign made the selector invalid. Pick a new stable locator.

Don't blame 'flaky'
Most 'flaky' tests are race conditions with clear fixes: replace sleeps with web-first assertions, wait for API responses, and disable animations in test.

5. Playwright config for great debug output

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

export default defineConfig({
  timeout: 30_000,
  expect: { timeout: 5_000 },
  retries: process.env.CI ? 1 : 0,
  reporter: [['html', { open: 'never' }], ['list']],
  use: {
    baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
    trace: 'on-first-retry',        // full trace only when needed
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
  ],
});
Upload artifacts on CI
On GitHub Actions, upload playwright-report/ and test-results/ as workflow artifacts. Every failure is then a one-click download for the whole team.

6. Hands-on task (10 minutes)

  1. 1

    Break a test on purpose

    Open your todo test from Lesson 3. Change the expected text to have a typo. Run it.

  2. 2

    Read the failure top-down

    Identify: Expected, Received, Call log, code frame. Write those four values down.

  3. 3

    Open the trace

    npx playwright show-report
  4. 4

    Fix it and re-run

    Restore the correct expected text and confirm green. Bonus: introduce a duplicate selector to reproduce the 'strict mode violation' error.

7. What's next

You can now read any failure with confidence. Module 2 starts with the single most important skill in Playwright: Playwright Locators — role, label and text.

Docs: Trace Viewer · Debugging

Frequently asked questions

1.What's the fastest way to see why a Playwright test failed?
Open the HTML report with `npx playwright show-report`, click the failing test, then click the trace icon. Trace Viewer lets you scrub every action, see network calls, DOM snapshots and console logs — usually diagnosing the failure in under a minute.
2.My test fails on CI but passes locally — is it a bug or flaky?
Neither, usually. It's environment drift: different viewport, timezone, animation speed, or a race condition that only shows under CI load. Enable `trace: 'on-first-retry'` and `screenshot: 'only-on-failure'` in playwright.config, then read the trace from CI.
3.How do I turn on Playwright Trace Viewer?
Set `use.trace: 'on-first-retry'` in playwright.config.ts, or run once with `--trace on`. After a failure, run `npx playwright show-trace path/to/trace.zip`.
4.What does 'strict mode violation: resolved to N elements' mean?
Your locator matched more than one element. Playwright refuses to guess which one you meant. Narrow the locator with `getByRole` + `name`, `.first()`, `.nth(i)`, or filter with `.filter({ hasText })`.
5.Should I add retries to hide flaky tests?
Only as a temporary safety net (`retries: 1` on CI) while you investigate. Retries hide bugs; the fix is to replace sleeps with web-first assertions and pin down race conditions.
6.How do I read the timeout error 'locator resolved but action timed out'?
The element exists but is not actionable — often covered by an overlay, disabled, or animating. Assert `toBeVisible` and `toBeEnabled` first, or use `{ force: true }` only when you deliberately want to bypass those checks.

Related lessons