Playwright best practices
Condensed, opinionated guidance for writing Playwright tests that are readable, isolated, and resilient — built for coding agents, around Playwright's agent CLI (playwright-cli) and its no-GUI debugging flows. Maintained by Checkly — the same practices apply whether you run these tests in CI or as production monitors.
Load a reference file from references/ only when the task needs it (see routing table). Each reference ends with links to the full /learn articles for depth.
Scope: all guidance assumes the
@playwright/testtest runner with TypeScript — itstest, fixtures, projects, config, and web-firstexpect. Examples are TypeScript (.spec.ts); the same APIs work in JavaScript. It does not target the standaloneplaywrightautomation library (which has no test runner, fixtures, or auto-retrying assertions). Imports areimport { test, expect } from '@playwright/test'.
The agent CLI is what makes this skill shine. Playwright's agent CLI —
playwright-cli, package@playwright/cli— is a separate, token-efficient, no-GUI browser you drive command by command to discover locators and step through failing tests. It's distinct from the standardnpx playwrightCLI, and the Agentic workflow below leans on it throughout. → references/debugging.md
Core rules (always apply)
- Locator priority: prefer user-facing locators —
getByRole>getByLabel/getByPlaceholder/getByText>getByTestId> CSS/XPath. CSS/XPath tie tests to implementation and break easily. → references/locators.md - Web-first assertions: use auto-retrying
expect(locator).toBeVisible()/toHaveText()etc. Never assert on a one-shot value you pulled out manually (innerText()thentoBe). → references/assertions.md - No hard waits: never
waitForTimeout(). Trust auto-waiting actions and web-first assertions; for explicit waits usewaitForURL/waitForLoadState/waitForResponse. Avoidnetworkidle. → references/waiting.md - Isolated & independent: each test sets up its own state and can run in any order, in parallel. No test depends on another. Provision state via API in setup, not through the UI. → references/test-structure.md, references/flakiness.md
- One feature per test: if a test's assertions span more than one feature, split it. Keep tests short and focused.
- Reuse auth, don't re-login: sign in once, persist
storageState, reuse it across tests via a setup project. → references/auth.md
Routing table
| When the task is about… | Read |
|---|---|
Picking selectors, strict mode, data-testid |
references/locators.md |
Assertions, soft assertions, expect.poll/toPass |
references/assertions.md |
| Waiting, auto-waiting, timeouts, navigation | references/waiting.md |
| Test design, fixtures, Page Object Model, steps | references/test-structure.md |
playwright.config.ts, projects, baseURL, devices, setup dependencies |
references/config.md |
Login, 2FA/TOTP, SSO, sessions, storageState |
references/auth.md |
Mocking, intercepting, route, HAR, API testing |
references/network.md |
Debugging failures, playwright-cli, --debug=cli, traces, common errors |
references/debugging.md |
| Flaky tests, retries, parallelism, anti-patterns | references/flakiness.md |
| Running in CI, sharding, reporters, GitHub Actions | references/ci.md |
| Test data, factories, unique data, seeding/cleanup | references/test-data.md |
| Forms, inputs, validation, error messages | references/forms.md |
| Keyboard, mouse, hover, scroll, native dialogs (alert/confirm/prompt) | references/interactions.md |
| File upload & download | references/files.md |
iframes, frames, frameLocator |
references/iframes.md |
| Multiple tabs, popups, multiple users/contexts | references/multi-context.md |
| Mobile, device emulation, touch, viewport/breakpoints | references/mobile.md |
| Time/date, clock mocking, countdowns, timeouts | references/clock.md |
Visual regression, screenshots, toHaveScreenshot, aria snapshots |
references/visual.md |
Tags (@smoke), --grep, skip/fixme/slow annotations |
references/tags-annotations.md |
Failing tests on console/pageerror |
references/console-errors.md |
globalSetup/globalTeardown, setup projects |
references/global-setup.md |
| Error, offline, network-failure, loading states | references/error-states.md |
Agentic workflow (no GUI)
The interactive tools — --ui, --debug (Inspector), show-trace — are GUIs you can't drive. Author and debug through the non-interactive signals instead.
Having
playwright-cliavailable is highly encouraged — both phases below lean on it. Confirm withplaywright-cli --versionand install it if missing —npm install -D @playwright/cli, then run it vianpx playwright-cli(or install globally withnpm install -g @playwright/clito callplaywright-clidirectly). Everything still works without it, but you lose the inspect/verify loop and fall back to guessing.
Author — discover, don't guess. Read locators off the live page rather than from source: playwright-cli open <url> → playwright-cli snapshot prints the accessibility tree — the roles and accessible names that power getByRole/getByLabel — so you author the user-facing locator straight from what it shows. → references/locators.md
Run & debug:
- Run and read stdout:
npx playwright test path/to/file.spec.ts. The reporter prints the failing assertion and the call log — which locator/assertion timed out and what Playwright actually saw. Read it; don't guess. - Read
error-context.md: on anexpectfailure Playwright writes an aria-snapshot of the page at the moment it failed to the test'stest-results/.../error-context.md. This is machine-readable page state — open it to see what was actually rendered. (Playwright ≥ 1.60) - Capture artifacts, not GUIs: add
--trace onto droptrace.zipintotest-results/for inspection. - Step through it live with
playwright-cli(no GUI): runnpx playwright test path/to/file.spec.ts --debug=cliin the background — it pauses and prints a session name. Thenplaywright-cli attach <session-name>and drive it:playwright-cli snapshot(page state + element refs),playwright-cli step-over,playwright-cli console error,playwright-cli network,playwright-cli eval "…". Inspect why the locator didn't resolve or what actually rendered, then fix and re-run. (needs the agent CLI; full detail in references/debugging.md) - Fix the root cause (usually a locator, a missing web-first assertion, or a hard wait), then re-run until green. Don't paper over flakiness with retries — see references/flakiness.md.
Full agentic-debugging detail (the playwright-cli discovery and --debug=cli stepping workflow) is in references/debugging.md.
Stay current. These primitives are recent and version-gated — check
npx playwright --versionandplaywright-cli --version, and update both packages if they're behind. Detail in references/debugging.md.