| name | playwright |
| description | write, debug, and maintain Playwright end-to-end tests for web applications. Use when working with |
Playwright
Core Workflow
- Analyze - Identify user flows and test scope
- Configure - Set up
playwright.config.ts (see references/configuration.md)
- Write tests - Use proper locators, auto-waiting, and assertions
- Organize - Apply fixtures, POM, parallelism (see references/test-organization.md)
- Debug - Use traces, UI mode (see references/debugging.md)
References
Load based on context:
| Topic | Reference | Load When |
|---|
| Locators & Actions | references/locators-and-actions.md | Writing selectors, filling forms, clicking, drag-and-drop |
| Test Organization | references/test-organization.md | Fixtures, parallel execution, retries, sharding, timeouts, annotations |
| Authentication | references/authentication.md | Login flows, multi-role tests, storageState |
| Network & Mocking | references/network-and-mocking.md | API mocking, route interception, HAR recording, API testing |
| Visual Testing | references/visual-testing.md | Screenshots, snapshots, ARIA snapshots, visual regression |
| Debugging | references/debugging.md | Flaky tests, trace viewer, UI mode, debug flags |
| Configuration | references/configuration.md | playwright.config.ts, projects, web server, CI/CD, reporters |
| Advanced | references/advanced.md | Clock mocking, evaluate, component testing, POM, accessibility |
Critical Rules
MUST DO
- Use
getByRole() > getByLabel() > getByTestId() > getByText() (in priority order)
- Use web-first assertions:
await expect(locator).toBeVisible() (auto-retries)
- Keep tests independent - no shared mutable state between tests
- Enable
trace: 'on-first-retry' for debugging failures
- Use
fullyParallel: true for speed
- Use
forbidOnly: !!process.env.CI to prevent .only leaking to CI
MUST NOT
- Use
waitForTimeout() — always use proper auto-waiting assertions
- Use CSS class selectors — they break on refactors
- Use
expect(await locator.isVisible()).toBe(true) — this does NOT auto-retry; use await expect(locator).toBeVisible() instead
- Share state between tests (each test gets a fresh
BrowserContext)
- Use
first()/nth() without narrowing first — filter or chain locators instead
Common Gotchas
- Assertion retrying: Only
expect(locator) retries. expect(await locator.something()) evaluates once.
has-text pseudo-class: Without another CSS specifier, matches everything including <body>. Always combine with an element selector.
getByText whitespace: Always normalizes whitespace, even with exact: true.
opacity: 0: Considered visible. Zero-size elements are NOT visible.
- Shadow DOM: All locators pierce Shadow DOM by default EXCEPT XPath.
fill() actionability: Checks Visible + Enabled + Editable only. Does NOT check Stable or Receives Events.
press()/pressSequentially(): NO actionability checks at all.
- Dialogs: Listener MUST handle (accept/dismiss) the dialog or the page action will stall permanently.
storageState: Covers cookies, localStorage, IndexedDB. Does NOT cover sessionStorage.
- TypeScript: Playwright does NOT type-check — it only transpiles. Run
tsc separately.
expect.toPass timeout: Defaults to 0 (no retry), NOT the expect timeout.
- Glob patterns:
* does not match /. ** matches everything. ? matches literal ? only.
- Serial mode retries: Retries ALL tests in the group, not just the failed one.
- Worker shutdown: Worker processes are always killed after a test failure.
- Drag events: For
dragover to fire in all browsers, issue TWO mouse.move() calls.
- Videos: Only available AFTER page/context is closed.
Quick Setup
npm init playwright@latest
npm i -D @playwright/test
npx playwright install
Minimal Config
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
Minimal Test
import { test, expect } from '@playwright/test';
test('homepage has title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/My App/);
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
});
CLI Quick Reference
npx playwright test
npx playwright test auth.spec.ts
npx playwright test --grep @smoke
npx playwright test --project=chromium
npx playwright test --debug
npx playwright test --ui
npx playwright show-report
npx playwright show-trace trace.zip
npx playwright codegen localhost:3000
Overview
Use this skill for the capability described in this document.
When to Use
Use this skill when the request matches the capability, constraints, and activation cues described below.
Examples
Use the examples and snippets already present in this document whenever they apply to the task.
Best Practices
Follow the constraints, conventions, and cautions documented below, and prefer the documented path over improvisation.