TL;DR

Commit the Playwright MCP config to .cursor/mcp.json at the repository root, where Cursor shares it with everyone who works in the repository, and pin @playwright/mcp to an exact version instead of @latest. Then check every generated spec the way CI runs it: headless, repeated, and alongside the existing suite.

  1. Put each agent's instructions, taken from Playwright's agent definitions, in its own Cursor project rule. Run the agents one at a time: plan, then generate, then heal.

Adding Playwright MCP to Cursor takes one entry. You open Cursor Settings → MCP → Add new MCP Server, choose the command type and enter npx @playwright/mcp@latest, and Cursor's agent can drive a browser. But that entry only exists on your machine. By default, Playwright MCP runs the browser in headed mode, and its default persistent profile preserves login state and cookies between sessions. By default, the latest tag is used by npm to identify the current version of a package. Playwright MCP stores the persistent profile in ms-playwright/mcp-{channel}-{workspace-hash} in your platform's cache directory.

Playwright comes with three Test Agents out of the box: planner, generator and healer. When a user couldn't get the agents working in Cursor, a maintainer replied that it was a question for Cursor and would be doable once Cursor supports agent definitions in a workspace, then closed the issue (#37739).

CI finds the last gap. The generator agent verifies selectors and assertions live as it performs the scenarios. By default, Playwright Test runs tests headless, and by default test files run in parallel. In Playwright Test, each test has its own local storage, session storage and cookies, and a test that depends on another test's side effects works when tests run in order but breaks the moment they run in parallel or in a different order.

The setup below gives the whole team the same Cursor agent, and gives you generated specs that have already passed the way CI will run them.

When Playwright MCP fits

Playwright MCP lets an LLM drive a browser through structured accessibility snapshots. Its tools cover common browser interactions such as navigation, clicking and typing, screenshots, dialogs and tabs. Each tool call returns a structured snapshot of the page's accessibility tree, with the elements, their roles and their text. Cursor's agent works from that structure rather than from pixels, so it needs no vision model.

That suits authoring work: exploring a page nobody has automated yet, finding the element a step needs, drafting a spec. CI has a different job. It runs the saved specs with npx playwright test. We'd keep every step of that run free of agents.

Anti-pattern

Chat as the test suite

Someone asks the agent to "check that checkout still works", watches it click through the flow and calls the feature tested. Nothing lands in the repo. The next commit gets no check, and neither does the one after it, until someone remembers to ask again.

Fix

End every session that proves something with a spec file. Ask the agent to write the test instead of performing it, and let CI run the file on every commit.

MCP or the CLI

The Playwright CLI is another way to give a coding agent a browser. Its docs call it token-efficient, because its short output avoids loading large tool schemas into the model's context. Prefer the CLI when the agent spends a long session reading a large codebase and only touches the browser now and then. Pick MCP when driving the live page is the job. Writing a new spec is exactly that job. Our comparison of MCP and the Playwright CLI for coding agents goes further into that trade-off.

Playwright MCP also works with VS Code, Windsurf, Claude Desktop and any other MCP client (Playwright MCP docs).

A shared .cursor/mcp.json

A server that one engineer added through Cursor's settings is a personal tool. A team's server belongs in the repository, pinned and reviewed like any other dependency.

Anti-pattern

Per-developer @latest entries

Each developer adds the server under Cursor Settings → MCP with npx @playwright/mcp@latest, or pastes the same block into a personal config:

// Anti-pattern: unpinned, set up per machine, and on the default persistent profile
{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp@latest"]
    }
  }
}

Every machine resolves @latest on its own schedule, so two teammates can run different server versions in the same week. The entry also keeps the default persistent profile, which preserves login state and cookies between sessions, so each browser carries whatever earlier sessions left in it. In #40419, a user reported that @playwright/mcp stdio clients such as Claude Code, Codex and Cursor hit a "Browser is already in use" error mid-session on the default configuration. The proposal to make isolated sessions the default was closed as not planned; a contributor replied that the default user-data-dir depends on the workspace, so the clash shouldn't happen when there's only one MCP per workspace.

Fix

Commit the config at the repository root. Cursor reads .cursor/mcp.json there and shares it with everyone who works in the repository, while ~/.cursor/mcp.json applies to every project of one user.

mkdir -p .cursor

Then add the server with a pinned version and two extra arguments:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@<version-your-team-tested>",
        "--isolated",
        "--storage-state=playwright/.auth/user.json"
      ]
    }
  }
}

Use @playwright/mcp, the package the Playwright docs name, and not a package with a similar name. Pin an exact version. Replace the one above with the release your team has tried, and bump it in the same pull request as @playwright/test so every upgrade shows up as a diff someone reviews. With --isolated, each session starts fresh, and --storage-state loads a known logged-in state into that session.

Each repository keeps its own copy, so a repo that lags behind on Playwright can pin an older server.

Check the connection

If the tools do not show up, open the Output panel and select MCP Logs; the logs show the server start, the tool calls and the errors. Then give the agent a one-line prompt with an obvious answer: "Open and tell me the main heading." Saved storage state expires: the Playwright authentication guide notes that you need to delete the stored state when it expires.

Agent-ready playwright.config

The agent writes the specs your config allows. Without a baseURL, every page.goto needs a host. Without shared auth state, every test has to log in.

Anti-pattern

Hardcoded host and UI login

We'd expect an agent left to itself to write what it saw in the browser: the full staging URL and the login form.

// Anti-pattern: the host is baked in, and the test logs in through the UI on every run
import { test, expect } from '@playwright/test';

test('user can open billing settings', async ({ page }) => {
  await page.goto('https://staging.example.com/login');
  await page.getByLabel('Email').fill('qa@example.com');
  await page.getByLabel('Password').fill(process.env.QA_PASSWORD!);
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.goto('https://staging.example.com/settings/billing');
  await expect(page.getByRole('heading', { name: 'Billing' })).toBeVisible();
});

That spec only runs against staging, and it logs in differently from the rest of the suite.

Fix

Set baseURL. Playwright's auth docs say all testing projects should use the authenticated state through the storageState option.

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  use: {
    baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
  },
  projects: [
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',
      },
    },
  ],
});

The test options reference says baseURL is "Unset by default." Once you set it, page.goto() builds its URL from it with the URL() constructor, and so do page.route(), page.waitForURL(), page.waitForRequest() and page.waitForResponse(). With baseURL set, a spec can navigate by using just the path, for example page.goto('/settings').

Anti-pattern

Committing the auth file

Playwright MCP can load initial state with --storage-state when it runs --isolated. Committing playwright/.auth/user.json then looks like a shortcut that makes the shared config work on every machine without setup. But the browser state file can hold cookies and headers that someone could use to impersonate you or the test account, and Playwright strongly discourages checking it into any repository, private or public.

Fix

Ignore the file, and let each person generate it locally with the suite's existing auth setup (if you don't have one yet, start with a storageState setup project). The committed config references the path, and the file stays on each machine.

echo "playwright/.auth/" >> .gitignore

Cursor project rules

A config the agent never reads doesn't help it. A rule is worth its tokens when it tells the agent something the page can't: where this repo keeps its config, fixtures and auth. Without that, the agent writes standalone tests that ignore all three.

Anti-pattern

A rulebook of generic advice

A rules file grows into a page of general Playwright advice (prefer role locators, avoid fixed waits, keep tests independent) and never names the repo's own config, fixtures or auth setup. The agent already knows the general advice. It doesn't know that tests/fixtures.ts exists, so it imports from @playwright/test, sets up its own state and skips the hooks the suite depends on.

Fix

Keep the rule short and specific to the repo. Every line should be a fact about this project that the agent can't infer:

# Playwright tests in this repo

- Config: `playwright.config.ts`. It sets `baseURL` and gives every project the logged-in `storageState`.
- Import `test` and `expect` from `tests/fixtures.ts`, never from `@playwright/test` directly.
- Tests start logged in. Never log in through the UI unless the test is about login.
- Navigate with relative paths only: `page.goto('/settings/billing')`. Never write a host.
- Put new specs in `tests/generated/`, one file per feature.
- Follow `tests/seed.spec.ts` for structure and assertions.

The relative-path line matters most: baseURL lets tests navigate by using just the path, for example page.goto('/settings'). Generic best-practice lists push those project facts out of the rule, so leave the general advice to the model. When the rules start to come from your own CI failures instead, writing AGENTS.md rules from CI failures shows how to pick them.

The seed test

The last line of that rule points at one file, and the planner treats that file as both setup and style guide. It runs the seed test to do all the setup a test needs, including global setup, project dependencies, fixtures and hooks. It also uses the seed test as the example for every test it generates.

So write the seed the way you want every generated spec to look, and keep it small:

// tests/seed.spec.ts
import { test, expect } from './fixtures';

test('seed: signed-in user lands on the dashboard', async ({ page }) => {
  await page.goto('/');
  await expect(page.getByRole('navigation')).toBeVisible();
});

We'd expect a generated spec to copy each of the seed's three parts. The import comes from the project's fixtures, and Playwright Test sets up only the fixtures the test needs. The navigation is relative, so baseURL resolves it. The single assertion checks that the page's navigation is attached and visible.

That import is also the first place to look when the agents misbehave. In one report, editing mcp.json alone didn't help, and the agents found their browser tools again once the reporter changed both mcp.json and the seed test from a custom fixture back to the default test (#38176). A maintainer couldn't reproduce the problem at first. The issue was closed on 2025-11-24. If your agents lose their tools, switch the seed's import to import { test, expect } from '@playwright/test'; before you debug the MCP config.

Planner, generator and healer in Cursor

Playwright's init-agents command generates agent definitions for VS Code, Claude Code, Codex and OpenCode, for example with npx playwright init-agents --loop=vscode. Under the hood, an agent definition is a collection of instructions and MCP tools. Playwright's agent definitions call tools from a playwright-test MCP server, and the generated MCP config starts that server with the command npx and the args playwright and run-test-mcp-server.

Playwright ships three agents:

  • The planner runs the seed test, and the planner produces a Markdown plan.
  • The generator turns that plan into executable Playwright tests, checking selectors and assertions live as it performs the scenarios.
  • The healer fixes the errors generated tests may start with. Its output is a passing test, or a skipped test if it believes the functionality is broken.

Put each agent's instructions, taken from Playwright's agent definitions, in its own Cursor project rule. Then run the agents one at a time: plan, then generate, then heal.

The plan input

Give the planner a user story with acceptance criteria, and ask for one test per criterion. Playwright's docs describe generated tests as aligned one-to-one with specs wherever feasible.

Story: As a signed-in user, I can change my billing email.

Acceptance criteria:
1. The billing settings page shows the current billing email.
2. Saving a valid email shows a confirmation and the new email.
3. Saving an invalid email shows an error and keeps the old email.

Plan one test per criterion. Follow tests/seed.spec.ts and the Playwright project rule.

Anti-pattern

Generating without a plan

Someone opens the generator rule, types "write tests for billing" and accepts what comes back. Without a plan, nothing ties a test to a requirement. The planner uses the seed test as an example of all the generated tests.

Fix

First plan from the story, with the seed in place. Then generate from the plan, and heal whatever fails.

After a Playwright upgrade

Regenerate the agent definitions whenever you update Playwright, so they pick up new tools and instructions. Your Cursor rules are copies of those definitions, so refresh them in the same pull request that bumps @playwright/test and the pinned MCP server.

Checking generated specs before merge

Generated tests may include initial errors that the healer agent can fix automatically. Playwright Test runs tests headless by default, and the --headed option runs tests in headed browsers.

Anti-pattern

One headed pass

The agent finishes, someone reruns the new file once with the browser visible, and it merges:

# Anti-pattern: one headed run of one file says little about how CI will run it
npx playwright test tests/generated/billing.spec.ts --headed

That run was headed, ran each test once and ran the file alone. A test that leaks state through a module-level variable or depends on another test's side effects works when tests run in order, but breaks the moment they run in parallel or in a different order.

Fix

Check every generated spec the way CI runs it: headless, repeated, and alongside the existing suite.

# Repeat the new specs headless, and fail if any test is flagged as flaky
npx playwright test tests/generated --repeat-each=5 --fail-on-flaky-tests
# Then run them with the rest of the suite
npx playwright test --fail-on-flaky-tests

The --repeat-each option runs each test N times (the default is 1). The --fail-on-flaky-tests option, off by default, fails the run if any test is flagged as flaky. Use the same commands in the CI job that gates the pull request, so the check you run locally is the one that blocks the merge.

By default, Playwright Test runs test files in parallel. Tests in a single file run in order, in the same worker process. To run tests in a different order, add --shuffle. The option schedules tests in a random order: test files are shuffled, and so are individual tests in parallel mode.

Healer skips

Read the diff for skipped tests. The healer turns out a skipped test when it believes the functionality is broken. Treat a skip as a bug report: someone checks the feature before the spec merges.

Gotchas after setup

Two defaults cause trouble once the shared setup is running, and one core tool can't be turned off.

Headed by default

If you run Playwright MCP's headed browser on a system without a display, or Cursor starts it from IDE worker processes, the docs say to start the MCP server separately with HTTP transport.

The persistent profile

The --isolated flag only protects the machines that keep it.

Anti-pattern

Unsafe code in the shared config

browser_run_code_unsafe is a core Playwright MCP tool, and the docs describe core tools as always available and unable to be disabled. That tool runs arbitrary JavaScript in the Playwright server process, and the docs call it RCE-equivalent. Every MCP client connected to Playwright MCP has browser_run_code_unsafe.

Fix

Let only MCP clients you trust reach the shared Playwright MCP server; the docs say to enable browser_run_code_unsafe only for trusted MCP clients.

Where the healer stops

The pre-merge check keeps fragile specs out. A CI-only failure is where they sneak back in. Don't hand one to the healer with nothing but the log. An agent working from the CI log alone tends to guess: it adds a wait, loosens the assertion or retries until the test passes, and the bug stays in the code.

To fix the root cause instead of adding waits or retries, the agent needs the test's record across CI runs, not one failure.

Currents' Cursor integration goes in the same .cursor/mcp.json at the repository root. The file holds no credential: each person signs in as themselves, and the Cursor agent can then read the team's runs and test results. Currents' Fix failing CI tests workflow gives the agent the test's history (whether it is flaky, and the branch and commit where it started failing), and with that context the agent finds the cause in the app or the test, fixes it and runs the test again to check the fix.

The rule of thumb: use the healer for failures you can reproduce in Cursor, and give the agent CI history for the ones you can't.

Frequently asked questions

How do I add Playwright MCP to Cursor?

Open Cursor Settings → MCP → Add new MCP Server, choose the command type and enter npx @playwright/mcp@latest. For a team, commit the config at the repository root, since Cursor reads .cursor/mcp.json there and shares it with everyone who works in the repository, as A shared .cursor/mcp.json shows.

What should the Playwright MCP config in .cursor/mcp.json contain?

Use @playwright/mcp, the package the Playwright docs name, and pin an exact version. With --isolated, each session starts fresh, and --storage-state loads a known logged-in state into that session. Cursor reads .cursor/mcp.json at the repository root and shares it with everyone who works in the repository, as the shared config section explains.

How do I use Playwright planner, generator and healer agents in Cursor?

Put each agent's instructions, taken from Playwright's agent definitions, in its own Cursor project rule. Then run the agents one at a time: plan, then generate, then heal. Playwright's init-agents command generates agent definitions for VS Code, Claude Code, Codex and OpenCode, as Planner, generator and healer in Cursor describes.

Why can't Playwright agents find their browser tools?

Check the seed test's import first: if your agents lose their tools, switch the seed's import to import { test, expect } from '@playwright/test'; before you debug the MCP config. In one report, editing mcp.json alone didn't help, and the agents found their browser tools again once the reporter changed both mcp.json and the seed test from a custom fixture back to the default test, as The seed test covers.

How do I check AI-generated Playwright tests for flakiness?

Check every generated spec the way CI runs it: headless, repeated, and alongside the existing suite. Run npx playwright test tests/generated --repeat-each=5 --fail-on-flaky-tests, then npx playwright test --fail-on-flaky-tests. The --fail-on-flaky-tests option, off by default, fails the run if any test is flagged as flaky, as Checking generated specs before merge explains.

What does the Playwright healer agent do?

The healer fixes the errors generated tests may start with. Its output is a passing test, or a skipped test if it believes the functionality is broken. Treat a skip as a bug report, as Healer skips recommends: someone checks the feature before the spec merges.

Should I commit the Playwright storageState auth file?

No. The browser state file can hold cookies and headers that someone could use to impersonate you or the test account, and Playwright strongly discourages checking it into any repository, private or public. Ignore the file, and let each person generate it locally with the suite's existing auth setup, as Anti-pattern: Committing the auth file shows.

What should Cursor project rules for Playwright tests include?

Keep the rule short and specific to the repo: every line should be a fact about this project that the agent can't infer. A rule is worth its tokens when it tells the agent where this repo keeps its config, fixtures and auth. Generic best-practice lists push those project facts out of the rule, as Cursor project rules explains.