Debugging, traces & UI mode
Break things, debug them and analyze with the trace viewer.
You recorded your first tests in the previous lesson, but recorded code is rarely the code you ship. Selectors break, pages take time to render, and the network does something unexpected. And even though codegen is great, its results often need some fine-tuning.
Before we write any more tests, let's learn how to figure out why a test failed.
Playwright offers multiple debugging tools: UI mode for live development, --debug for terminal step-through, the VS Code extension for breakpoint debugging, and traces (which I like to call "time-travel snapshots") for runs that already happened (CI failures, a co-worker's machine, last night's run).
UI mode — this is the way
For years, the community begged the Playwright team for a "watch mode". It never came. Instead we got something much better: UI mode.

Open UI mode with the --ui CLI flag:
$ npx playwright test --ui
Inside UI mode you get everything you need in one window:
- Watch mode to re-run tests on every file save.
- DOM snapshots to understand the site's state when an action took place.
- Network waterfall to analyze every request the page made.
- Console + errors panel to discover JavaScript exceptions and Playwright errors.
- Locator picker (the button with the
◎) to generate good locators. - Built-in Trace Viewer to inspect every step.
Open it and inspect your existing test cases. You'll thank yourself once tests start failing.
You can also combine --ui with --headed, which is my preferred way of
using UI mode.
Debugging via the Playwright inspector
Using --debug
Sometimes you just want to quickly step through a test. The --debug flag opens the Playwright Inspector and pauses on every action.

$ npx playwright test --debug
$ npx playwright test tests/shop.spec.ts --debug
If you're curious, check all available test options via npx playwright test --help.
Using page.pause
If you run your Playwright tests in --headed mode, you can also set a custom "breakpoint" by using page.pause().
test.only("search", async ({ page }) => {
await page.goto("https://www.playwright-workshop.online/");
await page.pause();
// ...
});
page.pause() will open the inspector when the breakpoint is reached.
Traces — time travel for CI failures
The debugger and UI mode are great while you're writing tests. But what about a run that failed in CI or on your colleague's machine? You can't attach a debugger after the fact, but you can read a trace.

A trace is a snapshot recording of every action your test performed. It looks similar to UI mode and also includes console messages, network information, full HTML snapshots of every step, screenshots, and much more. It's time-travel debugging for your end-to-end tests.
By default, Playwright doesn't collect traces locally (they're heavy). To learn what they're about, flip them on for every run in playwright.config.ts:
export default defineConfig({ use: { trace: "on", }, // ...});
Now run your tests:
$ npx playwright test
trace: "on" now writes a trace.zip file for each test into your test-results/ directory.
There are three good ways to open them.
From the HTML report
Whenever a trace was recorded in a test run, the generated HTML report links to it.
Run your tests and open the HTML report with npx playwright show-report:
$ npx playwright testRunning 15 tests using 5 workers 15 passed (14.6s)To open last HTML report run: npx playwright show-report
Poke around. You'll find a link to the trace viewer at the bottom of every test case.

From the command line
You can also open the trace viewer straight from the command line.
$ npx playwright show-trace test-results/example-has-title-chromium/trace.zip
From the online viewer
You don't need Playwright installed at all to read a trace. Drag a trace.zip onto trace.playwright.dev and it opens in your browser.

You probably don't want trace: "on" (every run, every test generates a zip)
in your production CI/CD pipelines. The values you'll most often use are
"on-first-retry" and "retain-on-failure" so that traces are only collected
when something actually went wrong.
Playwright Version 1.63 (released two weeks ago) extended traces with ARIA and screen snapshots next to the existing DOM snapshots.
Learn more about the new ARIA snapshots
Change the trace option from "on" to a configuration object.
export default defineConfig({ use: { trace: { mode: "on", snapshots: { dom: true, aria: true, screen: true }, }, }, // ...});If you turn on dom, aria and screen snapshots, each test step comes in two modes: DOM mode and Display ARIA mode. Hit the cog in the top right corner to flip between them.

In ARIA mode, you can inspect the accessibility tree while the matching elements are highlighted on the screenshot.

Break it, then fix it
The fastest way to get fluent with the debugging tools is to break a working test on purpose and recover from it.
Fix the test
Here's a broken test. It contains multiple mistakes. Find out what's wrong and make it pass.
import test, { expect } from "@playwright/test";
test("completes a full shopping journey from search to cart", async ({
page,
}) => {
await page.goto("https://www.playwright-workshop.online");
await page.getByRole("link", { name: "Login" }).click();
await page.getByLabel("Name").fill("stefan");
await page.getByLabel("Your password").fill("12345678");
await page.getByRole("button", { name: "Login" }).click();
await page.waitForURL(/\/account/);
await expect(page.getByTestId("login-name")).toBeVisible();
await page.getByPlaceholder("Search for products...").fill("snowboard");
await page.getByPlaceholder("Search for products...").press("Enter");
await expect(page.getByTestId("search-item-grid")).toBeVisible();
await page.getByRole("link", { name: "Price: Low too high" }).click();
await expect(page).toHaveURL(/sort=asc/);
});
- Copy the test into your project.
- Debug it in your preferred way.
- Make it pass.
Analyze the trace
Here's a trace file from a previous CI/CD run. Try to understand why the test didn't pass.
- Open the trace file.
- Debug and understand what went wrong.