Playwright: Debugging, Tracing & Codegen
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
A failing browser test is only useful if you can see why it failed. Playwright ships four complementary answers: tools that let you watch and step through a test while you write it (UI Mode, the Inspector, the VS Code extension), a recording of everything that happened that you can open later (the trace), a recorder that writes the first draft of a test for you (codegen) and reporters that turn a run into a terminal log, an HTML page, a JSON or JUnit file, or an annotation on a pull request. This page covers all of them. It is written for Playwright 1.64.1 (Node.js) and was checked on 25 October 2026; the Python, Java and .NET bindings expose the same tools, and their examples elsewhere in this section were run at 1.63 (see Playwright with Python, Java and .NET). Every command-line and TypeScript example below was run against the Bookshelf application in headless Chromium, and the output excerpts are real. The tools that need a window (UI Mode, the Inspector, the VS Code debugger, the interactive part of codegen) were documented but not run, because the machine that produced these examples had no display to drive; each such place says so.
The examples assume the configuration and fixtures described in Configuration, Fixtures & Projects; retries and workers are covered in Parallelism, Sharding & Retries, and screenshots and videos next to traces in Screenshots, Videos & Traces.
The debug loop
Most debugging sessions follow the same cycle, whichever tool you pick. A test fails (locally or on CI), you decide whether
you already have a recording of the failure, read it, change the test or the application and run again. The tools differ in
how early they let you intervene: the Inspector and UI Mode stop a live run, the trace is a recording you read afterwards,
and DEBUG=pw:api writes a log you can search.
The example project
Two files drive the examples. The configuration records a trace only for failing runs, runs one worker (every test resets the one shared Bookshelf server) and reads the application address from the environment:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: '.',
outputDir: './test-results',
testIgnore: process.env.SHOW_FAILURES ? [] : ['**/failing/**'],
workers: 1, // every test resets the one shared Bookshelf server
retries: process.env.RETRIES ? Number(process.env.RETRIES) : 0,
reporter: [['list']],
use: {
baseURL: process.env.BASE_URL,
testIdAttribute: 'data-testid',
trace: 'retain-on-failure',
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});
Source: Playwright: Test configuration
The spec has a few passing tests and one that is deliberately flaky: it asserts the wrong title on the first attempt and the right one on the retry. That gives every reporter and trace mode something to show.
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ request }) => {
await request.post('/api/test/reset');
});
test('search narrows the catalogue', async ({ page }) => {
await test.step('open the catalogue', async () => {
await page.goto('/books');
await expect(page.getByTestId('result-count')).toHaveText('24 books found');
});
await test.step('search for a title', async () => {
await page.getByTestId('search-input').fill('quiet');
await page.getByRole('button', { name: 'Search' }).click();
await expect(page.getByTestId('book-title')).toHaveText('The Quiet Compiler');
});
});
test('a reader can post a review', async ({ page }) => {
await page.goto('/signin');
await page.getByLabel('Username').fill('reader');
await page.getByLabel('Password').fill(process.env.BOOKSHELF_READER_PASSWORD ?? 'reader-local-only');
await page.getByTestId('signin-submit').click();
await page.goto('/books/3');
await page.getByLabel('Rating').selectOption('5');
await page.getByLabel('Your review').fill('Worth every page of it.');
await page.getByTestId('review-submit').click();
await expect(page.getByTestId('review').filter({ hasText: 'Worth every page' })).toHaveAttribute('data-state', 'saved');
});
test('the theme toggle switches to dark', async ({ page }) => {
await page.goto('/');
await page.getByTestId('theme-toggle').click();
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
});
// Fails on the first attempt and passes on the retry, to show flaky handling and on-first-retry traces.
test('a flaky check passes on retry', async ({ page }) => {
await page.goto('/books/1');
const expected = test.info().retry === 0 ? 'Not the real title' : 'The Quiet Compiler';
await expect(page.getByRole('heading', { level: 1 })).toHaveText(expected, { timeout: 1_000 });
});
Source: Playwright: Reporters
The runs below use BASE_URL=http://localhost:3917 (a Bookshelf started with PORT=3917 node server.js) and
RETRIES=1. The reader password is read from BOOKSHELF_READER_PASSWORD; the fallback is the application’s local-only
default.
UI Mode
UI Mode is the quickest way to develop a test: a window with the test tree on the left, a watch mode, and a time-travel view
of every run. Start it with the --ui flag:
npx playwright test --ui
Source: Playwright: UI Mode
UI Mode lists every test file in a sidebar. You run everything, one file, one describe block or one test with the
triangle next to its name, and filter by text, by @tag, by project or by status (passed, failed, skipped). If your suite
uses project dependencies, run the setup tests first by hand: UI Mode does not take setup projects into account on its own.
The panels for a selected run are the same ones the trace viewer has:
| Panel | What it shows |
|---|---|
Timeline |
A coloured strip of the run. Hover to preview the page at that moment; drag to select a range, which filters the actions, console and network panels to that range. |
Actions |
Every locator and action with its duration, with Before and After DOM snapshots. The snapshot can be popped out into its own window to open the browser developer tools on it. |
Pick locator |
Hover over the snapshot to see the locator Playwright would write, edit it in the locator playground and copy it. |
Source, Call, Log |
The line of the test that issued the action, the parameters and strictness of the call, and the actionability log (scrolled into view, visible, enabled, stable). |
Errors, Console, Network |
The failure with its code frame, console output from the page and from the test, and every request with headers and bodies. |
Attachments, Metadata |
Screenshot diffs for visual tests (with a slider to compare expected and actual) and facts about the run such as browser, viewport and duration. |
The eye icon next to a test turns on watch mode: the test re-runs whenever its source changes, so the edit-run-look cycle
needs no terminal. UI Mode records a trace of every test automatically, whatever the trace option says, and can show only
the tests affected by source changes (added in 1.59).
UI Mode normally opens its own window. To use it from a container or a remote machine, serve it over HTTP instead:
npx playwright test --ui-host=0.0.0.0
npx playwright test --ui-port=8080 --ui-host=0.0.0.0
Source: Playwright: UI Mode
Binding to 0.0.0.0 makes UI Mode, with its traces and any passwords or tokens in them, reachable from other machines on the
network; in GitHub Codespaces the forwarded port is private to your account by default. UI Mode was not run for this page
because it needs a window to open and click in.
The Playwright Inspector
The Inspector is a separate window that steps through a test action by action while the browser is visible. It has a toolbar to resume, pause and step over, the test code with the current action highlighted, a Pick locator button with a live-editable locator field, and the actionability log of the paused action.
| Way to start it | Effect |
|---|---|
|
Opens the Inspector and runs tests one at a time. It is a shortcut for |
|
Debug the single test on line 10, in every project of the configuration. |
|
Debug on one project (browser or device profile). Combine with a file and line number to debug one test in one browser. |
|
The environment-variable form of |
|
Does not open the Inspector; it only exposes a |
|
Added in 1.59 for coding agents: it prints a session name and waits for |
To stop at a particular line instead of stepping from the start, add page.pause() to the test. Run with --debug, press
Resume and the test runs until it reaches the pause.
import { test, expect } from '@playwright/test';
test('inspect the catalogue before asserting', async ({ page }) => {
await page.goto('/books');
await page.pause(); // opens the Inspector at this point when running headed or with --debug
await expect(page.getByTestId('book-card')).toHaveCount(6);
});
Source: Playwright: Debugging tests
Run headless, page.pause() does nothing: a two-line test with a page.pause() call passed in headless Chromium without
stopping. That is convenient (a forgotten pause does not hang CI) and also the reason the call only helps in a headed run
(--headed, --debug or headless: false). The Inspector itself was not run here because it needs a window.
Live locator editing. While paused, the field next to Pick locator shows the locator of the current action. Edit it and the matching elements are highlighted in the page, with a count, so a strictness violation (more than one match) is visible before you change the code. Pick locator lets you hover over the page and prefers role, text and test-id locators, adding filters when several elements match.
Actionability log. Paused on a click, the log already lists the checks Playwright ran: locator resolved, element visible, enabled, stable, receives events, scrolled into view. An action that cannot pass its checks stays marked as pending, which points at the real cause of a timeout.
Browser developer tools and the console API
With PWDEBUG=console and a page.pause() in the test, Playwright adds a playwright object to the page’s developer-tools
console. It uses the real selector engine, so you can try locators without touching the test:
| Call | Does |
|---|---|
|
The first element matching the Playwright selector. |
|
All matching elements. |
|
Reveals the element in the Elements panel. |
|
Creates a locator and shows its matching elements. |
|
Generates a selector for an element, for example |
In WebKit, opening the WebKit Inspector during the run stops the Playwright script from executing further and resets the configured user agent and device emulation, so use the Playwright Inspector there.
Headed mode and slow motion
Playwright is headless by default. --headed (or use: { headless: false }) shows the browser, and in library mode
chromium.launch({ headless: false, slowMo: 100 }) slows every operation by 100 ms so you can follow along. In the test
runner, slowMo is set through launchOptions in use. Headed runs are for local debugging; on CI, a trace gives you the
same information without a display.
Verbose logs with DEBUG=pw:api
Set the DEBUG environment variable to pw:api to log every Playwright call with its result and the timing since the last
line. It is the cheapest way to see where time goes, or what the runner actually did, with no GUI at all:
DEBUG=pw:api npx playwright test -c ex-debugging/playwright.config.ts --reporter=list -g theme
pw:api => apiRequestContext.post started +2ms
pw:api → POST http://localhost:3917/api/test/reset +6ms
pw:api ← 200 OK +3ms
pw:api <= apiRequestContext.post succeeded +1ms
pw:api => browserType.launch started +1ms
pw:api <= browserType.launch succeeded +87ms
pw:api => browserContext.newPage started +1ms
pw:api <= browserContext.newPage succeeded +51ms
pw:api => page.goto started +0ms
pw:api navigating to "http://localhost:3917/", waiting until "load" +1ms
pw:api "commit" event fired +4ms
pw:api "domcontentloaded" event fired +13ms
pw:api "load" event fired +0ms
pw:api <= page.goto succeeded +0ms
Source: Playwright: Verbose API logs
(The excerpt drops the request and response header lines the log also prints.) DEBUG=pw:api also covers API requests, so
a flaky request.post shows up with its status. On Windows PowerShell set $env:DEBUG="pw:api" first; in cmd use
set DEBUG=pw:api.
Debugging in VS Code
The Playwright extension for VS Code brings the same tools into the editor:
-
Failures are shown in the editor with the expected and received values and the full call log.
-
Show Browser keeps a browser open between runs; click a locator in the code and it is highlighted in the page (the extension also tells you when several elements match), and edit the locator to see the highlight change live.
-
Pick locator in the testing sidebar copies the best locator for the element you click, with the same role-text-test-id preference as the Inspector.
-
A red dot in the gutter sets a breakpoint; running the test with the Debug Test action stops there and gives you the usual step, resume and variables view.
-
Choosing Run Test with Show Browser checked, instead of Debug Test, keeps the browser session reusable so you can open Chrome DevTools on it.
-
The default debug profile is Chromium; Select Default Profile on the debug icon switches the browser (any project in your configuration) used for debugging.
-
Record new and Record at cursor start the code generator (see below) and write into the file at the cursor.
As of Chrome 136 the default Chrome user data directory cannot be driven by automation, so a debug profile that reuses your daily browser profile needs a separate user data directory. The extension was not run for this page; the behaviour is taken from the official guides.
Traces
A trace is a zip archive with everything needed to replay a run after the fact: every action with its parameters and log, screenshots of the page, DOM snapshots before, during and after each action, the console, every network request with bodies, the source of the test and its errors. It is the tool for failures you cannot reproduce on your machine, because the CI job that failed can attach it and you can read it as if you had been watching.
Recording a trace with the test runner
Set trace in use (or pass --trace on the command line). Playwright creates one trace per test run and keeps it
according to the mode. "First run" is the initial attempt of a test and "retries" are the attempts caused by retries:
| Mode | Records and keeps |
|---|---|
|
No trace (the default). |
|
Records and keeps a trace for every run. Heavy; use it only while debugging locally. |
|
Records and keeps a trace only for the first retry of a test. The recommended CI setting: passing tests pay nothing and a failure leaves a trace on the retry. |
|
Records and keeps a trace for every retry. |
|
Records every run, keeps only the traces of failed runs, even if a later retry passes. Works without retries. |
|
Records only the first run of each test (not retries) and keeps it if it failed (added in 1.43). |
|
Records every run and keeps the traces of failed runs and of retries, so you can compare a passing trace with a failing one of a flaky test (added in 1.59). |
Which runs end up with a trace file is easy to verify. The deliberately broken test in failing/broken.spec.ts fails on
every attempt, with one retry allowed. After each run, the output directory contains:
--trace retain-on-first-failure
failing-broken-a-broken-expectation-chromium: error-context.md trace.zip
failing-broken-a-broken-expectation-chromium-retry1: error-context.md
--trace retain-on-failure
failing-broken-a-broken-expectation-chromium: error-context.md trace.zip
failing-broken-a-broken-expectation-chromium-retry1: error-context.md trace.zip
--trace on-all-retries
failing-broken-a-broken-expectation-chromium: error-context.md
failing-broken-a-broken-expectation-chromium-retry1: error-context.md trace.zip
Source: Playwright: Recording a trace
A passing test with --trace on leaves a trace.zip in its directory as well. On the command line,
npx playwright test --trace on forces the mode for one run and wins over the configuration; locally, UI Mode records
traces automatically, so trace: 'on-first-retry' is mainly a CI setting. The trace of a failed test is also attached to
the test result, so the list reporter prints the show-trace command to open it and the HTML report links it.
For more control, trace accepts an object. The mode takes any of the values above and the other keys choose what is
captured:
| Key | Default | Meaning |
|---|---|---|
|
none |
One of the modes in the table above. |
|
|
Records a screencast and renders it as the film strip in the timeline. |
|
|
|
|
|
Includes the test source files so the viewer can show the code of each action. |
|
|
Includes the test’s attachments in the trace. |
A configuration that keeps richer traces for failures only (it extends the base config shown earlier):
import { defineConfig } from '@playwright/test';
import base from './playwright.config';
export default defineConfig(base, {
use: {
trace: {
mode: 'retain-on-failure',
screenshots: true,
snapshots: { dom: true, aria: true, screen: true },
sources: true,
attachments: true,
},
},
});
Source: Playwright: testOptions.trace
With this configuration the failing test’s trace.zip contained aria/ entries (one JSON aria snapshot per action phase),
screenshots/ and screencast/ images, the test source under src/ and the attachments, in addition to the usual
trace.trace and trace.network files. The extra snapshots make the archive larger, so enable them where you need them,
not everywhere.
The context.tracing API
Outside the test runner (library mode, or a test that manages its own context), use context.tracing. Its main methods are:
| Method | Purpose |
|---|---|
|
Starts recording. Options: |
|
Stops recording and writes the zip to |
|
Split one tracing session into several trace files on the same context. |
|
Group the calls in between under a heading in the viewer; inside tests prefer |
|
Record a HAR file for the context (added in 1.60). |
The runner already traces every context it creates when trace is on, and starting a second recording on such a context
throws tracing.start: Tracing has been already started. A test that wants to drive tracing itself therefore turns the
runner’s tracing off for that file:
import { test, expect } from '@playwright/test';
// The runner already traces every context it creates when `trace` is on, and tracing.start() throws
// "Tracing has been already started" in that case. Turn the runner's tracing off for this file.
test.use({ trace: 'off' });
test('context.tracing records a chunk of a session', async ({ browser, baseURL }, testInfo) => {
const context = await browser.newContext({ baseURL });
await context.tracing.start({
title: 'Bookshelf search',
screenshots: true,
snapshots: { dom: true, aria: true, screen: true },
sources: true,
});
const page = await context.newPage();
await page.goto('/books');
await context.tracing.group('Search for a book');
await page.getByTestId('search-input').fill('quiet');
await page.getByRole('button', { name: 'Search' }).click();
await expect(page.getByTestId('book-title')).toHaveText('The Quiet Compiler');
await context.tracing.groupEnd();
await context.tracing.stop({ path: testInfo.outputPath('manual-trace.zip') });
await context.close();
});
Source: Playwright: Tracing
The official guidance is to prefer the configuration option in tests, because traces made with context.tracing do not
record the test’s expect calls; the manual API is for scripts and for contexts that you create yourself. In this run the
trace of the example contained the Search for a book group with the fill, click and a toHaveText expectation under
it.
Opening a trace
npx playwright show-trace path/to/trace.zip
npx playwright show-trace https://example.com/trace.zip
npx playwright show-report
show-trace opens the viewer on a local file or a URL, so a trace stored as a CI artifact can be opened without
downloading it first. show-report serves the last HTML report, where every test that recorded a trace has a trace icon next
to it (and a Traces tab in the detail view). The viewer is also hosted at https://trace.playwright.dev/, a static
progressive web app: drop a trace.zip on it or use the Select file button. The page loads the trace entirely in your
browser and transmits nothing externally. To open a remote trace there, pass its URL as a query parameter, for example
https://trace.playwright.dev/?trace=https://example.com/trace.zip; the usual CORS rules apply to the server that hosts the
file.
The viewer shows the same panels as UI Mode (a trace is exactly what UI Mode shows live). For each selected action:
-
Actions, with a Before, Action and After DOM snapshot. The Action snapshot is taken at the moment of the input and highlights the exact point where Playwright clicked, which is the quickest way to see that a click landed on an overlay. The snapshots are live DOM: you can open the browser developer tools on them and inspect HTML and CSS.
-
Source, Call and Log: the line of the test, the call parameters, and the actionability and retry log.
-
Errors, Console and Network: double-click an action to filter console and network to that action, or drag over the timeline to filter to a range.
-
Metadata and Attachments.
-
Display Aria (added in 1.63): when a trace has
ariaandscreensnapshots, the viewer shows the action screenshot next to the aria snapshot, and hovering an aria node highlights it on the screenshot. This is the view to use when the test is written with role locators, because it shows the accessibility tree the locators match against.
The trace command line
Since 1.59, npx playwright trace explores a trace from the terminal, which is useful over SSH, in CI logs and for coding
agents that cannot open a window. The installed 1.64.1 lists these subcommands:
| Subcommand | Purpose |
|---|---|
|
Extracts the trace for inspection and prints its metadata. The other subcommands work on the opened trace. |
|
Removes the extracted data. |
|
Lists actions. Options: |
|
Shows the details of one action: parameters, error and call log. |
|
Lists network requests. Options: |
|
Shows one request with its headers. |
|
Lists console messages. Options: |
|
Shows errors with their stack traces. |
|
Prints the DOM snapshot of an action as an aria snapshot. Options: |
|
Saves the screencast frame of an action; |
|
Lists the trace’s attachments, or extracts one. |
|
Installs a |
Running them on the trace of the flaky test’s first attempt (the one that failed):
npx playwright trace open ex-debugging/test-results/catalogue-a-flaky-check-passes-on-retry-chromium/trace.zip
npx playwright trace actions
Playwright: 1.64.1
Title: catalogue.spec.ts:38 › a flaky check passes on retry
Duration: 1.1s
Actions: 28
Pages: 1
Network: 5 requests
Errors: 1
Attachments: 1
# Time Action Duration
──── ───────── ─────────────────────────────────────────────────────── ────────
1. 0:00.000 Before Hooks 35ms
2. 0:00.000 beforeEach hook 7ms
5. 0:00.006 POST 1ms
/api/test/reset
10. 0:00.035 Navigate 14ms
/books/1
11. 0:00.050 Expect "toHaveText" 1.0s ✗
getByRole('heading', { level: 1 })
12. 0:01.054 After Hooks 14ms
(Rows 3, 4 and 6 to 9, the fixture setup lines, were dropped from the excerpt.) The failing action is marked with the cross,
and --errors-only lists just that row. Then ask for the action’s details, the requests, and the page at that moment:
npx playwright trace action 11
npx playwright trace requests
npx playwright trace snapshot 11 --phase before
npx playwright trace close
Expect "toHaveText" getByRole('heading', { level: 1 })
Time
start: 0:00.050
duration: 1.0s
Parameters
selector: "internal:role=heading[level=1]"
expression: "to.have.text"
expectedText: [{"string":"Not the real title","normalizeWhiteSpace":true}]
timeout: 1000
Error
Error: expect(locator).toHaveText(expected) failed
Locator: getByRole('heading', { level: 1 })
Expected: "Not the real title"
Received: "The Quiet Compiler"
Timeout: 1000ms
# Start Method Status Name Duration Size
──── ───────── ──────── ──────── ─────────── ────────── ────────
1. 0:00.006 POST 200 reset 1ms 24
2. 0:00.037 GET 200 1 2ms 2.2K
3. 0:00.040 GET 200 app.css 2ms 2.4K
4. 0:00.040 GET 200 app.js 4ms 3.2K
5. 0:00.040 GET 200 1.svg 4ms 782
### Page
- Page Title: The Quiet Compiler - Bookshelf
### Snapshot
- heading "The Quiet Compiler" [level=1] [ref=f1e14]
- paragraph [ref=f1e15]: by Mara Velde
Source: Playwright: Trace viewer
The listing of requests omits the Route column and the snapshot excerpt shows only part of the aria tree. One difference
to note: the release notes for 1.59 pass the phase as trace snapshot 9 --name after, but the 1.64.1 command takes
--phase before|action|after, as its --help shows; --name failed with an error. Trust npx playwright trace snapshot
--help for the installed version. A trace recorded with context.tracing and the aria option opens the same way, and its
snapshot output is the aria tree of the page at that action.
Reading a trace: a short routine
-
Start at the red action in the timeline or
trace actions --errors-onlyand read the error: expected and received values, and the call log with the number of retries. -
Look at the Before snapshot: was the element there, covered, disabled, or still loading?
-
Check Network for failed or slow requests and Console for page errors around the same time.
-
Use the Action snapshot to see where a click or fill actually landed.
-
If the test is flaky, compare the trace of the failing attempt with a passing one (
retain-on-failure-and-retrieskeeps both).
Generating tests with codegen
Codegen records your interaction with a page and writes Playwright code for it. Playwright inspects the rendered page and picks the most resilient locator, preferring role, text and test-id locators, and making it more specific when several elements match. Generated code is a first draft: it rarely contains the waits-by-assertion and the page-object structure that a maintainable suite needs (see Test Design & Page Objects), so review and tidy it.
npx playwright codegen localhost:3917/books
Source: Playwright: Generating tests
The command opens two windows: a browser, where you click and type, and the Playwright Inspector, which shows the code as it is generated. The URL is optional; you can type it into the browser instead. In the Inspector:
-
Record toggles recording. Stop it to use Pick locator, which highlights the locator under the cursor and puts it in a field you can edit and copy.
-
The toolbar generates assertions. Click an icon and then an element on the page: assert visibility adds
toBeVisible(), assert text adds a text assertion and assert value checks the current value of an input. -
Copy copies the code, Clear starts over, and the language selector switches the output among the test runner and the Python, Java and .NET flavours. On the command line,
--targetdoes the same. -
Closing the Inspector window, or stopping the command in the terminal, finishes the session.
An actual recording of the Bookshelf search (click the search box, type "quiet", press Search, then use the three assertion tools) produces code of this shape. This file was written by hand in the style codegen generates it, using the locators and assertions codegen would choose for these elements, and was then run as a normal test (it passed); the recording itself was not run for this page because it needs a display:
import { test, expect } from '@playwright/test';
// Written in the style `npx playwright codegen --test-id-attribute data-testid localhost:3000/books` produces:
// role, label and test id locators, plus the three generated assertion kinds.
test('test', async ({ page }) => {
await page.goto('/books');
await page.getByRole('searchbox', { name: 'Search books' }).click();
await page.getByRole('searchbox', { name: 'Search books' }).fill('quiet');
await page.getByRole('button', { name: 'Search' }).click();
await expect(page.getByTestId('book-title')).toBeVisible(); // assert visibility
await expect(page.getByTestId('book-title')).toContainText('The Quiet Compiler'); // assert text
await expect(page.getByRole('searchbox', { name: 'Search books' })).toHaveValue('quiet'); // assert value
});
Source: Playwright: Test generator
Pass --test-id-attribute data-testid when the application uses data-testid (Playwright’s default test-id attribute is
data-testid, so this matters only when your attribute has another name, as in an application that uses data-test).
Codegen options
npx playwright codegen --help in 1.64.1 lists these options:
| Option | Effect |
|---|---|
|
Save the generated script to a file. |
|
|
|
|
|
A Chromium distribution channel such as |
|
Emulate a device, for example |
|
Viewport in pixels, for example |
|
|
|
Emulate location, time zone and locale, for example |
|
At the end of the session, save cookies, |
|
Start from a storage state saved earlier, so you begin signed in. |
|
Use a fixed browser profile. Since Chrome 136 the default profile cannot be used by automation, so create a separate directory. |
|
HTTP Basic authentication. Unlike credentials in the URL they are sent to every origin that asks, and appear in the generated code (the option was added in 1.63). |
|
Route the session through a proxy and skip it for some domains. |
|
Accept invalid certificates, override the user agent, block service workers, and set a timeout for actions (there is none by default). |
|
Save a HAR of the session, optionally filtered by URL glob. |
|
The attribute used for generated test-id locators. |
Source: Playwright: Emulation in codegen
Examples of the emulation flags from the official guide, for the Bookshelf:
npx playwright codegen --device="iPhone 13" localhost:3917
npx playwright codegen --viewport-size="800,600" localhost:3917
npx playwright codegen --color-scheme=dark localhost:3917
npx playwright codegen --timezone="Europe/Rome" --geolocation="41.890221,12.492348" --lang="it-IT" localhost:3917
Source: Playwright: Emulation in codegen
Note the viewport option’s name: the guide’s text calls it --viewport, but the command accepts --viewport-size, as the help
output above shows.
Recording behind a sign-in
Record the sign-in once, save the storage state, and load it in later sessions so you do not record the login again:
npx playwright codegen localhost:3917/signin --save-storage=auth.json
npx playwright codegen localhost:3917/books --load-storage=auth.json
The first command saves cookies, localStorage and IndexedDB when you close the browser; the second restores them so the
recording starts signed in. The file contains session credentials, so keep it out of version control (add it to
.gitignore) and delete it when you finish. The same file works as the storageState of a test project, as described in
Authentication & Test Isolation. These commands open a
browser and so were not run for this page.
Codegen in a custom setup
When the recording needs a context you configure yourself (for example browserContext.route() mocks), start a headed
browser from a script and call page.pause(); the paused page opens the same Inspector with the codegen controls:
const { chromium } = require('@playwright/test');
(async () => {
// Make sure to run headed.
const browser = await chromium.launch({ headless: false });
// Setup context however you like.
const context = await browser.newContext({ /* pass any options */ });
await context.route('**/*', route => route.continue());
// Pause the page, and start recording manually.
const page = await context.newPage();
await page.pause();
})();
This script needs a display and was not run for this page. Two further sources of generated tests exist: the Record new and Record at cursor buttons in VS Code (which use the same generator and insert the code at your cursor) and, since 1.56, the generator agent described in Playwright Test Agents.
Reporters
A reporter turns a run into output. Choose one with --reporter on the command line or reporter in the configuration, and
use several at once with an array. --reporter replaces the configured list, whereas --add-reporter (added in 1.63)
appends to it:
import { defineConfig } from '@playwright/test';
export default defineConfig({
// 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot'
// default 'list' when running locally
reporter: process.env.CI ? [['github'], ['dot'], ['html', { open: 'never' }]] : [['list']],
});
Source: Playwright: Multiple reporters
The default is list locally and dot when the CI environment variable is set. The built-in reporters in 1.64.1 are
list, line, dot, json, junit, null, github, html, blob, perfetto and coverage, plus any reporter
module you point to. Every built-in console reporter shows the full failure details; they differ in how much they print for
passing tests. One behaviour is worth knowing: when none of the configured reporters writes to the terminal (json or junit
with an output file, html, blob, perfetto, null), Playwright adds the line reporter (or dot on CI) so you still see
progress. A run with --reporter=null printed the line reporter’s output and the failure details; null does not mean
silent, it means "no extra reporter" (the exit code still reports failure).
list, line and dot
list prints one line per test as it finishes, with a tick or cross, the project, file, title and duration; retries appear as
separate lines marked (retry #1). The excerpt below is the real output for the example project with one retry:
npx playwright test --reporter=list
Running 5 tests using 1 worker
✓ 1 [chromium] › ex-debugging/catalogue.spec.ts:7:5 › search narrows the catalogue (252ms)
✓ 2 [chromium] › ex-debugging/catalogue.spec.ts:19:5 › a reader can post a review (991ms)
✓ 3 [chromium] › ex-debugging/catalogue.spec.ts:31:5 › the theme toggle switches to dark (99ms)
✘ 4 [chromium] › ex-debugging/catalogue.spec.ts:38:5 › a flaky check passes on retry (1.1s)
✓ 5 [chromium] › ex-debugging/catalogue.spec.ts:38:5 › a flaky check passes on retry (retry #1) (109ms)
✓ 6 [chromium] › ex-debugging/tracing-api.spec.ts:7:5 › context.tracing records a chunk of a session (403ms)
Source: Playwright: List reporter
(The failure details are printed after the list.) Its options are printSteps (print every test.step on its own line),
printFailuresInline (show the failure right after the failed test instead of at the end) and omitTags (hide tags added to
titles); each also has an environment variable (PLAYWRIGHT_LIST_PRINT_STEPS, PLAYWRIGHT_LIST_PRINT_FAILURES_INLINE,
PLAYWRIGHT_LIST_OMIT_TAGS).
line is for large suites: it keeps a single, updating line with the last finished test and prints failures as they occur.
When not attached to a terminal (as in the capture below) every progress line is printed:
Running 5 tests using 1 worker
[1/5] [chromium] › ex-debugging/catalogue.spec.ts:7:5 › search narrows the catalogue
[2/5] [chromium] › ex-debugging/catalogue.spec.ts:19:5 › a reader can post a review
[3/5] [chromium] › ex-debugging/catalogue.spec.ts:31:5 › the theme toggle switches to dark
[4/5] [chromium] › ex-debugging/catalogue.spec.ts:38:5 › a flaky check passes on retry
[5/5] (retries) [chromium] › ex-debugging/catalogue.spec.ts:38:5 › a flaky check passes on retry (retry #1)
Source: Playwright: Line reporter
dot prints one character per test run, which keeps CI logs short. The symbols are · passed, F failed, × failed or
timed out and will be retried, ± passed on retry (flaky), T timed out and ° skipped. The run above printed:
Running 5 tests using 1 worker
···×±·
1 flaky
[chromium] › ex-debugging/catalogue.spec.ts:38:5 › a flaky check passes on retry ───────────────
4 passed (3.6s)
Source: Playwright: Dot reporter
The cross followed by the plus-minus sign is the flaky test: it failed once and passed on the retry. The summary classifies
it as flaky rather than passed or failed, which is exactly the information to watch in CI (see
Running in CI/CD). --fail-on-flaky-tests (or failOnFlakyTests in the
configuration) turns a flaky run into a failing one.
html
The HTML reporter writes a self-contained report folder. It lists every test by file and project with filters for passed,
failed, flaky and skipped, shows each test’s errors, steps, attachments and trace, and, for tests that were retried, lists every attempt. By default it
opens in the browser when a test failed (open: 'on-failure'); in CI set open: 'never'.
PLAYWRIGHT_HTML_OPEN=never PLAYWRIGHT_HTML_OUTPUT_DIR=ex-debugging/my-report PLAYWRIGHT_HTML_TITLE="Bookshelf debug run" \
npx playwright test --reporter=html
npx playwright show-report ex-debugging/my-report
Source: Playwright: HTML reporter
The run wrote index.html, a data folder with attachments and a trace folder with the trace viewer assets to
ex-debugging/my-report. show-report serves a report folder (or, since 1.60, a .zip archive of one, such as a CI
artifact) on a local port. Options and their environment variables:
| Option | Environment variable | Meaning |
|---|---|---|
|
|
Where to write the report; default |
|
|
|
|
|
Where |
|
|
Title shown in the report. |
|
|
Where the |
|
|
Hide the "Copy prompt" button for errors, or the code snippets in the action log. |
|
|
Write scripts, styles and data as separate files, for a strict Content Security Policy. |
|
|
Group tests by their top-level |
Treat a report folder like the traces inside it: it can contain page content, request bodies and screenshots, so publish it only where the same people may see the application’s data.
json and junit
json writes one object with the configuration, the suites, specs and per-attempt results, and the aggregate statistics.
junit writes JUnit-style XML for build servers that parse it. Both print to standard output unless you set a file:
PLAYWRIGHT_JSON_OUTPUT_NAME=results.json npx playwright test --reporter=json
PLAYWRIGHT_JUNIT_OUTPUT_NAME=results.xml npx playwright test --reporter=junit
Source: Playwright: JSON reporter
Or in the configuration, reporter: [['json', { outputFile: 'results.json' }], ['junit', { outputFile: 'results.xml' }]].
The relative path is resolved against the configuration’s directory. The stats object of the JSON file for the run above reported 4 expected, 0 unexpected, 0 skipped and 1 flaky test in a
duration of about 3.8 seconds, and the flaky spec inside the file has status: "flaky" with two results, the first failed (retry 0) and the second
passed (retry 1). The head of the JUnit file:
<testsuites id="" name="" tests="5" failures="0" skipped="0" errors="0" time="4.471997">
<testsuite name="catalogue.spec.ts" timestamp="2026-10-25T19:57:43.975Z" hostname="chromium" tests="4" failures="0" skipped="0" time="2.529" errors="0">
<testcase name="search narrows the catalogue" classname="catalogue.spec.ts" time="0.187">
</testcase>
...
<testcase name="a flaky check passes on retry" classname="catalogue.spec.ts" time="1.255">
<system-out>
<![CDATA[
[[ATTACHMENT|test-results/catalogue-a-flaky-check-passes-on-retry-chromium/error-context.md]]
[[ATTACHMENT|test-results/catalogue-a-flaky-check-passes-on-retry-chromium-retry1/trace.zip]]
]]>
</system-out>
</testcase>
Source: Playwright: JUnit reporter
A flaky test is reported as passed (it has no failure element) with its attachments in system-out, so a JUnit-based
dashboard does not show flakiness; use the JSON or HTML report for that. Since 1.59 the JUnit reporter distinguishes types of
errors, so failures that used to be failure elements may now be error elements. The JUnit options are outputFile,
stripANSIControlSequences, includeProjectInTestName, omitTags, plus the environment variables
PLAYWRIGHT_JUNIT_OUTPUT_DIR, PLAYWRIGHT_JUNIT_OUTPUT_NAME, PLAYWRIGHT_JUNIT_OUTPUT_FILE,
PLAYWRIGHT_JUNIT_SUITE_ID and PLAYWRIGHT_JUNIT_SUITE_NAME. For JSON, PLAYWRIGHT_JSON_OUTPUT_DIR,
PLAYWRIGHT_JSON_OUTPUT_NAME and PLAYWRIGHT_JSON_OUTPUT_FILE do the same job; the full-file-path variable wins over the other two.
blob
The blob reporter writes a zip with every detail of the run so that reports from several machines can be merged into any other report later. It exists for sharded runs:
npx playwright test --reporter=blob
npx playwright merge-reports --reporter html ./all-blob-reports
Source: Playwright: Blob reporter
The run wrote blob-report/report.zip (about 37 KB for this suite) in the package directory. With sharding the file is named
report-<hash>-<shard>.zip; the hash comes from --grep, --project and file filters so different invocations do not clash.
Options: outputDir (PLAYWRIGHT_BLOB_OUTPUT_DIR; existing content is deleted before writing), fileName
(PLAYWRIGHT_BLOB_OUTPUT_NAME) and outputFile (PLAYWRIGHT_BLOB_OUTPUT_FILE, which overrides the other two). When you run
the same tests in different environments, set the top-level tag option (for example from a CI variable) so each merged
report is labelled. The merge itself, and the CI recipe for it, are in
Parallelism, Sharding & Retries; the merge-reports
command above was not run on this page.
github, null and perfetto
-
githubcreates failure annotations on the files of a pull request when it runs in GitHub Actions. Other reporters work there too, but onlygithubannotates; avoid it with a matrix strategy, because the failures multiply. It acceptsomitTags(orPLAYWRIGHT_GITHUB_OMIT_TAGS). Run outside GitHub Actions it printed the line reporter’s output, and withGITHUB_ACTIONS=trueset locally the output was the same, so the annotation format was not captured here; it is documented from the official page. -
nullproduces no report of its own (see the note on added reporters above). -
perfetto(added in 1.63) writes a Trace Event Format file,test-results/perfetto.jsonby default, that opens in the Perfetto UI orchrome://tracing: one lane per worker, every test a slice with its hooks, fixtures and steps nested inside. An output file name ending in.gzis compressed on the fly. The run produced a 19 KB file for this suite; it was not opened in the Perfetto UI. Environment variables:PLAYWRIGHT_PERFETTO_OUTPUT_DIR,PLAYWRIGHT_PERFETTO_OUTPUT_NAMEandPLAYWRIGHT_PERFETTO_OUTPUT_FILE. -
coverageappears in the 1.64.1 help text but is not described on the reporters page, so it is not covered here.
Custom reporters
A custom reporter is a class that implements some of the Reporter methods, exported as the default export of a module:
import type {
FullConfig, FullResult, Reporter, Suite, TestCase, TestResult,
} from '@playwright/test/reporter';
class MyReporter implements Reporter {
onBegin(config: FullConfig, suite: Suite) {
console.log(`Starting the run with ${suite.allTests().length} tests`);
}
onTestBegin(test: TestCase, result: TestResult) {
console.log(`Starting test ${test.title}`);
}
onTestEnd(test: TestCase, result: TestResult) {
console.log(`Finished test ${test.title}: ${result.status}`);
}
onEnd(result: FullResult) {
console.log(`Finished the run: ${result.status}`);
}
}
export default MyReporter;
Source: Playwright: Custom reporters
Use it by path, in the configuration (reporter: './my-reporter.ts') or on the command line. Run alone, it printed:
npx playwright test --reporter=./ex-debugging/my-reporter.ts
Starting the run with 5 tests
Starting test search narrows the catalogue
Finished test search narrows the catalogue: passed
...
Starting test a flaky check passes on retry
Finished test a flaky check passes on retry: failed
Starting test a flaky check passes on retry
Finished test a flaky check passes on retry: passed
Starting test context.tracing records a chunk of a session
Finished test context.tracing records a chunk of a session: passed
Finished the run: passed
Source: Playwright: Reporter API
Note that onTestEnd fires once per attempt (the flaky test appears twice) and that status is the status of that attempt;
the test’s overall outcome is test.outcome(). A custom reporter prints nothing unless it logs, and Playwright assumes it
prints to the terminal, so no line reporter is added. To keep the usual console output and add your reporter on top, use
--add-reporter:
npx playwright test --reporter=dot --add-reporter=./ex-debugging/my-reporter.ts
Running 5 tests using 1 worker
Starting the run with 5 tests
Starting test search narrows the catalogue
·Finished test search narrows the catalogue: passed
...
×Finished test a flaky check passes on retry: failed
Starting test a flaky check passes on retry
±Finished test a flaky check passes on retry: passed
Source: Playwright: Custom reporters
Other Reporter hooks exist for steps (onStepBegin, onStepEnd), standard output (onStdOut, onStdErr), errors outside
tests (onError), printsToStdio() and, since 1.62, a preprocess() hook that runs before onBegin and can
mark tests as skipped, excluded, fixed or failing. Steps carry subtitle and params (added in 1.63), which the trace viewer and the HTML
report show next to the step title. Community reporters worth reading as examples include the Allure, GitHub Actions, Mail,
ReportPortal and Monocart reporters.
Debugging flags at a glance
| Flag or variable | Use |
|---|---|
|
Force a trace for every run of this command, overriding the configuration. Open the result from the HTML report or with |
|
Inspector, headed, one worker, no timeout, stop at the first failure. |
|
Debug over the CLI for agents (1.59+). |
|
|
|
Log every Playwright call and its timing. |
|
Per-test timeout (default 30000; |
|
Visible browser, UI Mode, serial execution to remove interference from parallel tests. |
|
Stop after the first n failures so you debug one thing at a time. |
|
Re-run only the failures of the last run, or the test files changed since the last commit ( |
|
Retry failed tests, which also triggers |
|
Narrow the run to one browser or device profile. |
The last-failed list lives in .last-run.json under the output directory. A re-run with --last-failed in the example project
ran only the failing test and its retry.
What changed recently
-
1.64: no change to the debugging tools; the baseline for this page.
-
1.63: aria and screen snapshots in traces (
snapshots: { dom, aria, screen }) and the Display Aria mode of the trace viewer; theperfettoreporter;--add-reporter; theomitTagsoption forlist,line,dot,githubandjunit;codegen --http-credentials;subtitleandparamsontest.step. -
1.62: the HTML reporter’s
mergeFilesoption can be set from the configuration, and the newreporter.preprocess()hook lets a reporter mark tests as skipped, excluded or expected to fail before the run starts. -
1.61: HAR and trace recordings include WebSocket requests; video modes match the trace modes.
-
1.60:
tracing.startHar();npx playwright show-reportaccepts a.ziparchive. -
1.59: the
npx playwright tracecommand line, theretain-on-failure-and-retriestrace mode,tracing.start({ live }),npx playwright test --debug=cli, and a UI Mode option that shows only the tests affected by source changes. -
1.58 and 1.57: a Speedboard tab (1.57) and a timeline (1.58) in the HTML report for merged runs.
-
1.56: Test Agents, including a generator that writes tests, and an
htmlreporter option that turns off the Copy prompt button (the button itself, which copies an error with context for an AI assistant, appeared in the HTML report, trace viewer and UI Mode in 1.51). -
1.46:
--only-changed. -
1.43: the
retain-on-first-failuretrace mode.