Engineering

Playwright MCP for AI-Powered UI Testing

Use Playwright MCP to drive a real browser with an AI assistant, check real user journeys, and turn the ones worth keeping into Playwright tests.

Aarav Sharma

Aarav Sharma

October 31, 202610 min read
Share
Three stacked cards naming what Playwright MCP gives an assistant: navigating pages, filling forms and clicking, and reading page structure as roles and names

You ship a sign-in form. You open the browser, type an email and password, select Log in, and watch the dashboard appear. It works, so you move on.

Tomorrow you rename a button and click through it again. Next week a colleague changes the login redirect and nobody notices sign-in now lands on a blank page, because nobody opened it.

An end-to-end test does that clicking on every push. Most teams never write the first one because it is fiddly: before you can tell a test to click something, you must find out what the page calls it. This post hands that part to an assistant that can open the page and read it.

Quick Answer

Playwright drives a real browser from code so you can test your app the way a person uses it. Playwright MCP is a server that exposes those browser tools to an AI assistant, which uses them to open your page, read its structure, walk a user journey and report what it found — the server supplies the hands, not the judgement. What makes this work is that the assistant reads an accessibility snapshot, listing the elements the accessibility tree exposes with their roles and names, not a screenshot. That is the same information Playwright's recommended locators are built from, so a journey worth keeping can become a test whose locators match the page. You still review that test, run it repeatedly, and keep the assistant out of your pipeline.

What Is Playwright MCP?

Start with the words; this corner of testing is full of them.

TermWhat it means in plain language
End-to-end testA script that drives a real browser through a real user journey
PlaywrightMicrosoft's library for writing those scripts, plus a runner for them
LocatorHow a test finds one element, such as the button named Save
MCPModel Context Protocol, a standard way to give an AI assistant extra tools
Playwright MCPAn MCP server that offers browser tools to your assistant
Accessibility snapshotA text list of the elements the accessibility tree exposes, each with its role, name and ref

Hold on to the last one. Playwright MCP describes itself as letting a model interact with web pages through structured accessibility snapshots, with no vision model required. Here is roughly what your assistant receives after opening a page:

- heading "todos" [level=1] [ref=e3]
- textbox "What needs to be done?" [ref=e5]
- list [ref=e12]:
  - listitem [ref=e13]:
    - checkbox "Toggle Todo" [ref=e14]
    - text: Buy milk
- button "Clear completed" [ref=e20]

No pixels, no colours, no class names. Most lines are an element the accessibility tree exposes, with its role, its accessible name and a ref such as e5; some, like the todo text above, are plain text with nothing to click. When the assistant wants to type in that box, it sends a command aimed at e5, and Playwright performs it in the real browser.

The same sign-in form as a screenshot and as an accessibility outline, showing which one carries element roles and names

A ref is a handle on the current snapshot, not a permanent id. Playwright's snapshots reference says a ref stays valid until the page changes, and a stale one fails with a message telling the assistant to take a new snapshot. That is why you watch it re-read the page after a navigation.

The server exposes tools across navigation, forms, network mocking, storage and tracing, with only the core group on by default.

The Problem

There are two ordinary ways to end up with no tests, or bad ones.

The first is writing them by hand. You open devtools, hover over the email field, find a div four levels deep with a generated class name like css-1x9fj2, and paste that into your test. It passes until your styling library updates, the class becomes css-7b2kq1, and the test fails for a reason that has nothing to do with your app.

A check tied to a generated class name failing after a style update, beside a check tied to the button's visible name that still passes

The second is asking an assistant for the test without giving it the page. It has never seen your app, so it invents something plausible. The code looks right and is fiction, and you debug somebody else's guess about your HTML.

An assistant that can see a screenshot is only half-helped: a picture shows a button reading Log in, but not whether that is a button, an a, or a div with a click handler — exactly what a locator needs.

Both failures are the same: whoever writes the test cannot see what the page really exposes.

The Solution

Close that gap: give the assistant the live page in the form that locators are made of. Steps 1 to 3 run on Playwright's public demo, so you can follow them without a project of your own.

Step 1: Add the MCP server to your assistant

You need Node.js 20 or newer, as the installation guide specifies. In Claude Code the setup is one command, with syntax from the Claude Code MCP guide:

claude mcp add --scope project playwright -- npx -y @playwright/mcp@latest

Project scope writes a .mcp.json at your project root, so teammates get the same setup. Start Claude Code, run /mcp, and check playwright is connected; a project server sits at pending approval until you trust it.

In Cursor, create .cursor/mcp.json in the project root instead, following the Cursor MCP guide:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest"]
    }
  }
}

If the file exists, add the playwright entry to its mcpServers object. Then open Settings, find Tools and MCP, and enable it. You do not install a browser; Playwright MCP downloads one on first launch.

Step 2: Drive the app from a sentence

Start with the opening prompt from Playwright's getting started guide:

Navigate to https://demo.playwright.dev/todomvc and add a few todo items.

A browser window opens and you watch it type. That loop is the whole mechanism: snapshot, pick an element by its ref, act, snapshot again, read what changed.

Nothing has been written to disk, and that is the point: you have a browser you can drive in plain English, enough to walk a flow you would otherwise click by hand.

Step 3: Check a real user journey

Testing starts when you give it something to verify: name the journey, say what should happen, and ask for evidence back:

Use the Playwright MCP tools to check the todo flow at
https://demo.playwright.dev/todomvc.

Check that:
- the page loads and the new-todo input is visible
- adding two items shows both in the list
- completing the first leaves the counter reading "1 item left"

Report each check as passed, failed, or not checked, and describe what
you saw as evidence. Do not change any files, and do not call a check
passed if you could not verify it.

That last paragraph does the work: per-check verdicts and their evidence turn a chatty summary into something you can argue with, and forbidding file changes keeps the session exploratory.

The same shape covers most of what you check by hand:

  • Visibility: does the form, button or message appear at all?
  • Validation: what happens when a required field is empty?
  • Interaction: does completing an item update the counter?
  • Navigation: does submitting take the user where it should?
  • Error handling: does a failed action explain itself?
  • Responsive: is the interface usable at phone width? Set --device "iPhone 15" in the server's args, not the prompt.

Ask for one journey per prompt.

Three cautions. The assistant judges against the expectations you give it, so a vague requirement produces a vague check. Watch the browser while it works: the summary is its account of the run, not the run itself. And a snapshot says what is on the page and what it is called, never that the layout looks right — treat a green report as a lead, and pin what matters down with an assertion in code.

Step 4: Point it at your own app

The demo is public and logged out; your app is neither. Two changes get you there.

First, start your dev server and give the assistant its local address instead of the demo URL, such as http://localhost:5173.

Second, deal with the login. Playwright MCP keeps a persistent browser profile, so a session you sign into stays signed in. Making that repeatable needs the storage tools, which are not in the core group, so add --caps=storage to the server's args and restart.

Then sign in once and have the assistant save the result: browser_storage_state writes cookies and local storage to a file, browser_set_storage_state restores it, and --storage-state=./auth.json loads it at startup. That file holds a live session: keep it out of version control, and use a test account, not a real customer's.

Step 5: Turn a keeper into a test

A session proves a journey works now. To check it on every push it must become code, so scaffold Playwright and confirm the runner:

npm init playwright@latest
npx playwright test

Then ask for the test, naming the locator style:

Using the Playwright MCP tools, explore https://demo.playwright.dev/todomvc
and write a test for this flow: add two todo items, mark the first one
completed, and check the counter says "1 item left".

Explore the app with the tools before writing any code. Do not write steps
from assumption.

Prefer getByRole, getByLabel, and getByPlaceholder locators over CSS
selectors or XPath. Save it as tests/todo.spec.ts.

The middle paragraph stops it guessing; the last asks for locators tied to what a user can see:

import { test, expect } from "@playwright/test";

test("completing a todo updates the counter", async ({ page }) => {
  await page.goto("https://demo.playwright.dev/todomvc");

  const newTodo = page.getByPlaceholder("What needs to be done?");
  await newTodo.fill("Buy milk");
  await newTodo.press("Enter");
  await newTodo.fill("Walk the dog");
  await newTodo.press("Enter");

  await page
    .getByRole("listitem")
    .filter({ hasText: "Buy milk" })
    .getByRole("checkbox")
    .check();

  await expect(page.getByTestId("todo-count")).toHaveText("1 item left");
});

Most of that test reads off the snapshot: the placeholder and checkbox are named on the page. The counter is the exception: getByTestId works because the app sets a data-testid, which the accessibility tree does not carry. Run it yourself before trusting it; your runner has your config, base URL and timeouts:

npx playwright test tests/todo.spec.ts

Step 6: Harden it before you commit

A generated test earns its place under the conditions your pipeline imposes, not the ones it was written under.

Run it many times; one pass proves little about timing. Currents recommends twenty-five runs before merging, since the default is one:

npx playwright test tests/todo.spec.ts --repeat-each=25

Twenty-five green runs is a check, not a guarantee: some flakiness appears only under CI's slower machines, so keep retries low enough to see it.

Read every locator; a snapshot does not prove an element is reachable on the journey a user takes, especially around collapsed sections and controls that appear only after an earlier action.

Check it tests one thing; generated tests drift toward doing everything at once.

Step 7: Keep MCP out of CI

Playwright MCP is an authoring tool: it belongs on your machine, in the conversation where the test is written.

What you commit is the spec file; your pipeline runs plain Playwright, with no assistant:

npx playwright test

Two reasons. Every push would otherwise repeat finished work at a cost. More importantly, your merge gate should be assertions a reviewer read, not an agent's account.

Authoring with MCP on a developer machine, a reviewed spec file committed to the repository, and CI running plain Playwright with no assistant

Where to go next

Playwright's test agents offer a more structured version of this workflow on the same tools:

npx playwright init-agents --loop=claude

The planner explores your app and writes a plan as Markdown, the generator turns it into test files, and the healer proposes a patch when the UI moves. The useful part is the plan in the middle: you review intent in English before any code exists.

A planner writing a Markdown plan, a generator turning the approved plan into test files, and a healer replaying a failure to propose a patch

Trade-offs

This is useful and not free. Four things to weigh before building a workflow on it.

It uses a lot of context: every snapshot and tool schema stays in the conversation, and Playwright's own coding agents page says MCP's token cost is higher than its CLI's. One published comparison puts an MCP session at roughly four times the tokens of the CLI route — one workload, not a universal benchmark. For a few tests a week that is irrelevant; for a team doing this all day it is a line item, and Playwright ships a CLI for frugal agents.

It has no opinion about what to test. MCP gives a model hands, not judgement. Ask for checkout tests and you get the happy path, not the expired card or the case filling your support inbox.

It is young: flags and tool names move quickly on a 0.0.x release, so check the docs against any tutorial, including this one.

It is not a security boundary, and its own documentation says so. browser_run_code_unsafe runs arbitrary JavaScript in the server process, and the docs call it RCE-equivalent, to be enabled only for clients you trust. It sits in the core group, which is always enabled, so leaving the optional capabilities off does not remove the risk. Origin lists and file-access limits are convenience defences rather than isolation, so point it at a local or staging environment with test data.

There is also a scoping limit: it tests what a browser can reach — not a background job that failed silently or a dropped column.

Conclusion

Start with one user journey you are tired of testing manually. Set up Playwright MCP, let the AI assistant explore the page before writing a test, and review the code it generates. Run the test yourself, repeat it to check for flakiness, and commit it only when you understand what it verifies. From there, let CI run the ordinary Playwright test on every push, without needing an AI assistant in the pipeline.

The goal is not to replace your judgement with AI. It is to spend less time figuring out how to interact with a page and more time deciding what your tests should protect. Let AI help write the tests, and let your assertions decide whether the application works.

Want help deciding which user journeys in your app are worth testing first? Contact us

Aarav Sharma

Aarav Sharma

Lead Software Engineer

Aarav leads product engineering at Matlab Infotech, where he has shipped mobile and web platforms across healthcare, fintech, and SaaS. He writes about pragmatic engineering and shipping fast without cutting corners.

Related articles

Let's Collaborate

Tell us about your project and we'll come back with a plan, a timeline, and a quote.

Project Type

Budget

Task Message

Your Contacts