Playwright MCP: driving a browser with an AI agent
You ask an assistant to check whether the signup form on https://staging.example.com/signup still works. Ninety seconds later it reports that the email field accepts qa-user@example with no domain, the submit button stays disabled after a valid entry, and the console shows TypeError: Cannot read properties of undefined (reading 'plan').
It did not guess any of that. It opened a real Chrome window, typed into the real form, and read the real console. That is what Playwright MCP does.
Short version
- MCP is a standard way to give an AI assistant access to outside tools.
- Playwright MCP is a server that hands the assistant a real browser to drive.
- It works from the accessibility tree, not screenshots, so it reads pages as structured text.
- Setup is one config entry and one command. No test code required.
- It is strong at exploratory checks, reproducing bugs, and writing first-draft Playwright tests.
- Page snapshots are the token cost. A few flags cut that bill sharply.
What MCP is, in one paragraph
MCP stands for Model Context Protocol. It is an open standard that lets an AI assistant talk to external tools through a small server. The assistant asks the server what tools it has, the server replies with a list and the arguments each one takes, and the assistant then calls them and reads the results. Before MCP, every assistant needed custom code for every tool. With MCP, any assistant that speaks the protocol can use any server that speaks it. Playwright MCP is one such server, and the tool it offers is a browser.
What the agent gets
The server, published by Microsoft as @playwright/mcp, exposes a set of browser tools. The names are readable and map to what a tester does by hand.
| Tool | What it does |
|---|---|
browser_navigate | Go to a URL |
browser_snapshot | Return the page as a structured accessibility tree |
browser_click, browser_type, browser_hover | Interact with elements |
browser_fill_form | Fill several fields in one call |
browser_select_option, browser_press_key | Dropdowns and keyboard input |
browser_console_messages | Read console errors and warnings |
browser_network_requests | List the requests the page made |
browser_take_screenshot | Capture an image |
browser_wait_for | Wait for text to appear or disappear |
browser_tabs, browser_file_upload, browser_handle_dialog | Tabs, uploads, dialogs |
The important one is browser_snapshot. Instead of sending a picture and asking the model to look at it, the server sends the page's accessibility tree: roles, names, and states, with a reference for each element. The model reads something like button "Create account" [ref=e42] [disabled] and can then call browser_click with that reference.
This matters for two reasons. Text is far more reliable than vision for finding a button, and the accessibility tree is the same structure screen readers use, so the agent notices unlabelled controls as a side effect.
Setting it up
The server needs Node.js 18 or later. There is nothing to install ahead of time; npx fetches it on first run.
- Check it runs. In a terminal:
npx @playwright/mcp@latest --help. You should see the option list. - Add it to your assistant's MCP config. For most clients this is a JSON file with an
mcpServersobject:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}
- Or use your client's CLI. In Claude Code, for example:
claude mcp add playwright -- npx @playwright/mcp@latest. - Restart the assistant so it picks up the new server.
- Confirm the tools loaded. Ask it to list its tools, or just say "open https://staging.example.com and take a snapshot". A browser window should appear.
- Pick a browser and mode. Add arguments as needed:
--browser msedge,--headless,--device "iPhone 15",--viewport-size 1280x720. - Isolate the profile for clean runs.
--isolatedkeeps the browser profile in memory instead of on disk, so every session starts logged out.
By default the browser runs headed, which is what you want at first. Watching the agent click is how you learn to trust it, and how you spot it clicking the wrong thing.
For repeatable setups, put the options in a file and pass --config path/to/config.json. That file can set the browser, viewport, timeouts, allowed origins, and which capabilities are on.
What it does well
Exploratory passes on a changed page. Give it a URL and a goal in plain words: "sign up as a new user, then check the settings page loads". It will click through, and it reports console errors and failed requests without being asked. This is the highest-value use.
Reproducing a reported bug. Paste the steps from a bug report and let the agent follow them. It either reproduces the issue and tells you the exact element and error, or it fails at step three and tells you why, which is often the more useful outcome.
Writing a first-draft Playwright test. After a manual run, ask for the equivalent test file. The server can emit TypeScript code for the actions it performed, so you get real selectors instead of invented ones. Treat the result as a draft: it needs better assertions and stable locators, but it saves the boring part.
Accessibility spot checks. Because it reads the accessibility tree, missing labels stand out. A snapshot showing button [ref=e17] with no accessible name is a finding you can file immediately.
Cross-device checks. --device "iPhone 15" or --mobile gives a mobile viewport and user agent in one flag, which is quicker than resizing windows by hand.
Where it burns tokens
This is the part that surprises people. Cost here is not the browser. It is the size of what the browser sends back.
Every browser_snapshot returns the whole accessibility tree. On a simple form that is a few hundred tokens. On a dashboard with a data table, a sidebar, and a hundred rows, it can be twenty thousand or more. The server also returns a snapshot after most actions, so a ten-click journey can send that tree ten times. Two or three such pages can fill a context window on their own.
The other three cost sources:
- Screenshots. Images are expensive and, for most checks, the accessibility tree already told the agent what it needed.
- Console and network dumps. A chatty app logs hundreds of lines. All of them come back.
- Retries. When an element is not found, agents tend to snapshot again and try again, which doubles the cost of every mistake.
Flags that cut the bill:
| Flag | Effect |
|---|---|
--snapshot-mode none | Stop returning a snapshot after every action; request them explicitly |
--output-mode file | Write snapshots, console, and network to files instead of the response |
--image-responses omit | Do not send screenshot images back to the model |
--console-level error | Return only errors, not info and debug noise |
--mobile | Mobile pages are usually lighter, so the tree is smaller |
--blocked-origins | Block analytics and ad hosts so their requests never appear |
Three habits help as much as the flags. Give the agent a specific starting URL rather than making it navigate from the home page. Ask for one task per session and start a fresh one after. And prefer browser_find or a targeted wait over "take a snapshot and look around" when you already know what you are checking.
What it does not replace
Playwright MCP is a driver, not a test suite. It is not deterministic, it costs money per run, and it is slower than a compiled test. Nothing about it belongs in a CI pipeline that runs on every commit.
The sensible division: use the agent to explore, reproduce, and draft. Use ordinary Playwright test files, checked into your repository, for anything that must run the same way every time. The agent's output feeds the suite; it does not become the suite.
Two safety notes. The browser it drives is a real browser with real credentials if you give it any, so point it at staging and use a test account like [email protected]. And an agent reading a web page can read instructions hidden in that page, so treat any site you do not control as untrusted input and use --allowed-origins to fence it in.
Frequently asked questions
Do I need to know Playwright to use it?
No. You describe what to check in plain words and the agent picks the tools. Knowing Playwright helps when you start turning agent sessions into real test files.
Does it work headless?
Yes, add --headless. Start headed, though. Watching the browser is the fastest way to see when the agent misreads a page.
Why does it use the accessibility tree instead of screenshots?
Structured text is cheaper, more accurate for locating elements, and works without a vision model. Screenshots are available through --caps vision when you genuinely need to see layout.
Can it log into my app?
Yes, if you give it credentials or a saved session with --storage-state. Use a dedicated test account on staging, never a real user account on production.
How do I stop it filling my context window?
Combine --snapshot-mode none with --output-mode file and --image-responses omit, keep each session to one task, and give it a direct URL instead of asking it to find its own way there.




