Use when a coding agent works with Playwright through playwright-cli, Playwright MCP or the planner, generator and healer Test Agents: setting them up, reviewing healer diffs before merging, debugging a failing test from its trace in a terminal with no display, and leaving a video receipt.
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Playwright ships three things built for coding agents, and they solve different problems:
| Tool | What it is | Use it for |
|---|---|---|
playwright-cli | A command-line browser driver: open, click e15, snapshot, screenshot | An agent checking a page, reproducing a bug or poking a flow while it also works in a large codebase |
| Playwright MCP | An MCP server that exposes the browser as tools and returns accessibility snapshots | Long agent loops that keep browser state and reason over page structure |
| Playwright Test Agents | Three agent definitions (planner, generator, healer) generated into your repo by npx playwright init-agents | Turning an app into a Markdown test plan, then into @playwright/test files, then keeping them green |
The official guidance on the first two
(getting-started-cli): "playwright-cli is best for coding
agents (Claude Code, GitHub Copilot, etc.) that favor token-efficient,
skill-based workflows", while "MCP is best for specialized agentic loops
that benefit from persistent state and iterative reasoning over page
structure".
This skill covers choosing between them, setting them up, and the
review a human still owes their output. Writing Playwright tests by hand
(locators, web-first assertions, config, sharding) is
playwright-testing; scaffolding a whole harness for a repo with no
suite is the automation-harness-bootstrapper agent; cleaning up a
recorded codegen spec is playwright-codegen-reviewer.
Facts below are checked against Playwright 1.65 docs and a local
npx playwright init-agents --loop=claude run on @playwright/test
1.65.0. Agent definitions change between releases, so re-check after an
upgrade.
Not a fit: hand-writing or refactoring specs (playwright-testing),
cross-browser matrix planning (browser-matrix-strategy-reference).
| Situation | Pick | Why |
|---|---|---|
| Claude Code works in a repo and needs to check a page now and then | playwright-cli | Commands avoid "loading large tool schemas and verbose accessibility trees into the model context" (pw-cli); each snapshot is written to a file the agent reads only when needed |
| An MCP-only client (Claude Desktop, Cursor, VS Code chat) or a long exploration loop | Playwright MCP | Tools and snapshots stay in the conversation; persistent profile by default (pw-mcp) |
| You want a test suite, not a one-off check | Test Agents | They write specs/*.md plans and tests/*.spec.ts files you keep and run in CI (pw-agents) |
| You need the agent to see your own signed-in browser tabs | Either, with the Playwright Extension: playwright-cli attach --extension or MCP --extension | Connects to existing tabs instead of a fresh browser |
Do not install all three "just in case": each MCP server adds its tool schemas to every conversation.
Per pw-cli (Node.js 20+ for the CLI itself):
npm install -g @playwright/cli@latest
playwright-cli --help
playwright-cli install --skills # skill files for the agent, this project
# or: playwright-cli install --skills -g (into ~/.claude/skills)Since 1.62, a project that already has @playwright/test can run the
bundled copy as npx playwright cli (pw-notes).
First run, headless by default:
playwright-cli open http://localhost:3000/login
playwright-cli snapshot # element refs like e15
playwright-cli fill e15 "user@example.com"
playwright-cli click "role=button[name=Sign in]"
playwright-cli screenshot --filename=after-login.png
playwright-cli console # browser console messages
playwright-cli requests # network since page load
playwright-cli closeSessions: -s=<name> runs several browsers side by side; list,
close-all, kill-all manage them; playwright-cli show opens a live
dashboard of every session. The profile lives in memory unless you pass
--persistent. A headless session closes itself after an hour without
commands; change that with open --idle-timeout=<ms>. Project defaults go
in .playwright/cli.config.json, loaded automatically.
Claude Code (pw-mcp):
claude mcp add playwright npx @playwright/mcp@latestOther clients take the standard config:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless", "--isolated"]
}
}
}| Flag | Effect (from pw-mcp) |
|---|---|
| (none) | Headed browser and a persistent profile per workspace: logins and cookies survive between sessions |
--headless | No visible window; needed on CI and in containers |
--isolated | Fresh profile every session; add --storage-state <file> to start signed in |
--browser=firefox | chrome, firefox, webkit or msedge |
--idle-timeout=<ms> | Close an idle headless browser sooner than the default hour; 0 disables |
--port 8931 | Standalone HTTP server, for a headed browser on a machine without a display |
--no-webmcp | Stop offering tools that the page itself registers via WebMCP (1.65, pw-notes) |
Prefer --isolated for test work: the default persistent profile keeps
yesterday's login and cart, which makes "it works" depend on leftover
state.
npm init playwright@latest # only if the repo has no Playwright yet
npx playwright init-agents --loop=claude(npm init playwright@latest is the standard installer, pw-intro.)
Other loops: --loop=vscode, --loop=codex, --loop=opencode
(pw-agents). With --loop=claude on 1.65.0 the command
writes:
| File | Purpose |
|---|---|
.claude/agents/playwright-test-planner.md | Explores the app, saves a Markdown plan to specs/ |
.claude/agents/playwright-test-generator.md | Turns one plan item into a test file, checking selectors live |
.claude/agents/playwright-test-healer.md | Runs failing tests, debugs them, edits the test until it passes |
.mcp.json | Server playwright-test: npx playwright run-test-mcp-server |
seed.spec.ts | Seed test the agents run first to get a ready page |
specs/README.md | The plans folder |
Then:
seed.spec.ts so it does the real setup: your custom fixtures,
sign-in, base URL. The planner runs it and "will also use this seed
test as an example of all the generated tests" (pw-agents).
A blank seed gives plans for the login page only..mcp.json and approve the
playwright-test server.init-agents in the same
PR as every @playwright/test bump.The agents run unattended, so each hand-off gets a human check.
| Stage | Prompt | Output | Review before moving on |
|---|---|---|---|
| Plan | "Use the playwright-test-planner agent to plan guest checkout. Seed: seed.spec.ts. PRD: docs/checkout.md" | specs/guest-checkout.md | Scenarios match the requirement, error paths present, nothing out of scope |
| Generate | "Use the playwright-test-generator agent for section 1 of specs/guest-checkout.md" | tests/.../*.spec.ts | Role or test-id locators, expect web-first assertions, each expected result from the plan is asserted |
| Run | npx playwright test | Pass or fail | A pass on the first run proves little: break the app or the assertion once and see it fail |
| Heal | "Use the playwright-test-healer agent on tests/checkout/apply-coupon.spec.ts" | Edited test, or test.fixme() | The diff review below |
The healer's own instructions (generated playwright-test-healer.md,
1.65) tell it to fix "assertions and expected values", to "Do not ask
user questions ... do the most reasonable thing possible to pass the
test", and, when it is confident the test is right, to "mark this test
as test.fixme()" with a comment. So a green healer run can hide a real
bug. Review its diff with this table:
| Diff shows | Accept? |
|---|---|
| Locator updated to a role, label or test id that matches the new UI | Yes |
| Missing wait replaced by a web-first assertion | Yes |
| Expected text, count or URL changed | Only if the spec or product changed on purpose. Otherwise it is the app's bug: revert, file it |
Assertion deleted or loosened (toHaveText to toBeVisible, exact to broad regex) | No. Restore it |
test.fixme() added | Treat the comment as a bug report: file it, link it, keep the test |
waitForTimeout or networkidle added | No. The healer is told "Never wait for networkidle"; send it back |
Run git diff tests/ after every healer session, and run the suite
again yourself before merging.
When a test fails on CI, the agent can read the trace without a GUI (1.59+, pw-notes):
npx playwright trace open test-results/checkout-chromium/trace.zip
npx playwright trace actions --grep="expect" # failing step and its number
npx playwright trace action 9 # error, expected vs received
npx playwright trace snapshot 9 --name after # DOM after that step
npx playwright trace closeTo step through a test live:
npx playwright test tests/checkout.spec.ts --debug=cli
# prints: Run "playwright-cli attach tw-87b59e" to attach to this test
playwright-cli attach tw-87b59e
playwright-cli --session tw-87b59e step-overFor flaky tests, set trace: 'retain-on-failure-and-retries' in the
config: it "retains all traces when an attempt fails", so a passing and
a failing attempt can be compared (1.59). PLAYWRIGHT_DASHBOARD=1 shows
@playwright/test browsers in the playwright-cli show dashboard.
An agent saying "I checked the page" is not evidence. Leave one of:
playwright-cli screenshot --filename=....page.screencast (1.59+, pw-notes),
which "serves as a receipt: chapter titles provide context, action
annotations highlight each interaction":await page.screencast.start({ path: 'receipt.webm' });
await page.screencast.showActions({ position: 'top-right' });
await page.screencast.showChapter('Verifying coupon', { description: 'SAVE20 applies 20%' });
await page.getByLabel('Coupon').fill('SAVE20');
await page.getByRole('button', { name: 'Apply' }).click();
await expect(page.getByTestId('discount')).toContainText('20%');
await page.screencast.stop();npx playwright test output, with counts.browser_run_code_unsafe "runs arbitrary JavaScript in the Playwright
server process and is RCE-equivalent", so the docs say to enable it only
for trusted MCP clients (pw-mcp). The generated planner definition lists
this tool, so the planner can run code on your machine.--no-webmcp when you
test sites you don't control.--isolated plus a test-account
--storage-state file, and keep that file out of git.| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Adding Playwright MCP to every project "in case" | Tool schemas cost context in every session | playwright-cli for occasional checks (Step 1) |
Leaving seed.spec.ts as generated | Planner explores a blank page; generated tests skip your fixtures | Real setup in the seed (Step 4) |
Upgrading @playwright/test without re-running init-agents | Agent files call tools that changed or no longer exist | Regenerate in the same PR (Step 4) |
| Merging healer output unread | Healer is told to fix expected values and not ask; real bugs become passing tests | Diff review table (Step 5) |
Counting test.fixme() as "healed" | The failing behavior is now silently skipped | File the bug, keep the test (Step 5) |
| Default persistent profile for tests | Results depend on leftover cookies and carts | --isolated + --storage-state (Step 3) |
| "Checked in the browser, looks fine" with no artifact | Nothing to review | Screenshot or screencast receipt (Step 7) |
@playwright/test code only (JS/TS); Python, Java
and .NET Playwright users get the CLI and MCP but not this loop.--skills,
commands, sessions, idle timeout, config file, CLI vs MCP, WebMCP.claude mcp add,
flags, profile modes, idle timeout, browser_run_code_unsafe.init-agents --loop, seed test, regenerate on update.--debug=cli, npx playwright trace,
retain-on-failure-and-retries, PLAYWRIGHT_DASHBOARD; 1.62 bundled
npx playwright cli / mcp; 1.65 --no-webmcp.npm init playwright@latest.npx playwright init-agents --loop=claude on
@playwright/test 1.65.0 (file list in Step 4, healer and planner
definitions quoted in Steps 5 and Security).