Debugging with Playwright Inspector and UI Mode
The Trace Viewer explains a failure after it happened; the Inspector and UI mode let you debug a test while it runs. When you are authoring a flow or chasing a failure that only appears with a real browser open, you want to pause execution, step action by action, and try selectors against the live page until one sticks. Playwright ships two interactive tools for this: the Inspector, a step debugger driven by PWDEBUG=1 or --debug and page.pause(), and UI mode (--ui), a watch-mode dashboard with a built-in timeline, locator picker, and per-action snapshots. This walkthrough shows when to reach for each. It sits under Trace Viewer & Debugging, part of the Debugging & Test Observability guide.
Root cause: post-mortem debugging is slow for authoring
A trace is ideal for a failure you cannot reproduce, but it is a poor fit while you are still writing a flow. When you do not yet know the right selector, or you want to try an action and immediately see the result, the edit-run-read-trace loop is too slow. Interactive debugging collapses it: you pause the browser at a known point, experiment against the real DOM, copy a working locator straight out of the picker, and continue. The two tools below cover the spectrum — the Inspector for line-by-line stepping inside a running test, UI mode for a watch-driven dashboard you keep open across edits.
Minimal reproducible example
Drop a page.pause() into a flow you are building. When the test runs under a headed debug session, execution stops at that line and the Inspector opens.
import { test, expect } from '@playwright/test';
test('build the settings flow', async ({ page }) => {
await page.goto('/settings');
// Execution halts here; the Inspector opens so you can step and try locators.
await page.pause();
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('Saved')).toBeVisible();
});
Step-by-step fix
- Launch the Inspector. Run the test with
PWDEBUG=1 npx playwright test settings.spec.tsornpx playwright test settings.spec.ts --debug. Both force a headed browser and open the Inspector docked beside it, paused before the first action.PWDEBUG=1also disables action timeouts so the session never expires while you think. - Step through actions. Use the Inspector's Step Over button to execute one Playwright call at a time. Each step highlights the target element on the live page and logs the resolved locator, so you watch the test drive the browser action by action.
- Pin a precise breakpoint with page.pause(). Instead of stepping from the top, insert
await page.pause()at the exact line you care about. Running under debug halts there, letting you skip the setup and land on the problem step directly. - Build selectors with the locator picker. Click "Pick locator" in the Inspector (or in UI mode), then hover and click elements on the live page. Playwright generates a resilient locator — preferring
getByRole,getByLabel, andgetByText— that you copy straight into the test, following the guidance in Reliable Selector Strategies for Playwright. - Switch to UI mode for watch-driven work. Run
npx playwright test --uito open the UI mode dashboard. Pick a test to run it headed with a live timeline; toggle watch mode so the test re-runs automatically every time you save the spec, giving instant feedback while you iterate. - Inspect any action in the UI timeline. In UI mode, click an action on the timeline to see its before/after DOM snapshot, the source line, and the network and console panels — the same surfaces as a trace, but for the run you just triggered, so you confirm a fix without leaving the dashboard.
- Remove debug hooks before committing. Delete every
page.pause()and avoid committingPWDEBUG-dependent code. Apage.pause()left in a spec hangs CI forever because nothing clicks Resume; treat removing it as part of the fix.
UI mode is also the fastest place to confirm a config change behaves. The settings it honors — projects, base URL, timeouts — come from Playwright Config & Fixtures, so a quick UI run validates them against a real browser.
import { test, expect } from '@playwright/test';
test('verified flow after picking locators', async ({ page }) => {
await page.goto('/settings');
// Locator copied from the picker — role-based and resilient.
await page.getByRole('switch', { name: 'Email notifications' }).click();
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('Saved')).toBeVisible();
});
Troubleshooting variants
The Inspector never opens
You ran headless. PWDEBUG=1 and --debug force headed mode, but if you override with --headed=false or a config headless: true that takes precedence in some setups, the Inspector cannot attach. Drop the override and rerun. On a remote or containerized CI box there is no display, so interactive debugging belongs on a local machine — for headless environments use traces instead.
page.pause() seems to do nothing
page.pause() only opens the Inspector in a headed run. Under a plain npx playwright test (headless) it resolves immediately. Run with --debug or PWDEBUG=1 to make the pause interactive, or use UI mode where you can step without code changes.
UI mode does not pick up my new test
The file watcher may not be tracking the path, or the test is filtered out by a project or grep. Confirm the test appears in the UI mode tree, clear any active filter, and check that the spec matches your testMatch glob. Saving the file should trigger a re-run when watch mode is on.
Verification
Confirm interactive debugging did its job in three ways. First, the locator you copied from the picker resolves to exactly one element — run the finished test and watch it pass headed in UI mode. Second, run the same spec headless (npx playwright test settings.spec.ts) to prove it does not depend on a debug session. Third, grep the codebase for page.pause( before committing to guarantee no interactive breakpoint reaches CI, where it would hang the job indefinitely.
Frequently Asked Questions
What is the difference between the Inspector and UI mode?
The Inspector is a step debugger that attaches to a single running test via PWDEBUG=1, --debug, or page.pause(), letting you execute one action at a time. UI mode (--ui) is a standalone dashboard for browsing, running, and watching the whole suite, with a per-action timeline and snapshots. Use the Inspector to step through one test, UI mode to iterate across many.
Will PWDEBUG or page.pause() break my CI?
Yes if they reach CI. A page.pause() left in a spec halts the run forever because no human clicks Resume, and PWDEBUG forces headed mode that headless CI cannot provide. Remove pauses and keep debug flags out of committed code; rely on trace: 'on-first-retry' for CI evidence instead.
How do I get a good selector without writing it by hand?
Use the locator picker in the Inspector or UI mode. Click "Pick locator," hover the element on the live page, and Playwright generates a resilient locator favoring role, label, and text. Copy it directly into your test, which keeps selectors aligned with the accessibility-first approach recommended for stable suites.