TL;DR One line to install it, then always ask for the change and the check in the same breath. Claude opens the page, measures what it just wrote with
browser_evaluate, and retries until it matches. What closes the loop is not the screenshot: it is the measurement. The accessibility tree will tell you the button is there and correctly labelled. Only the measurement tells you it is white on white.
Every Playwright MCP guide stops right where the interesting part begins. You get the install command, a list of tools, and that's it, as if having the server connected were the achievement. It isn't. The achievement is what changes in how you ask for things once Claude can look at the result of its own edit.
The difference from verifying at the end is timing, not tooling. If what you want is a one-off audit after you're done, /run and /verify already ship inside Claude Code and do exactly that: read the diff, launch the app, check whether the change shows up. This is a different thing. This is handing over the instrument before it starts, so the two or three rounds of correction happen while it works, without you in the middle.
Result:
> Change the pay button to the brand colour and check on
localhost:3000 that it reads against the background
⏺ Edit src/checkout.css
⏺ browser_navigate http://localhost:3000
⏺ browser_snapshot
- button "Pay now" [ref=e5]
⏺ browser_evaluate getComputedStyle(btn)
{ color: "rgb(255, 255, 255)",
background: "rgb(255, 255, 255)" }
Contrast 1:1. The button exists but is invisible.
⏺ Edit src/checkout.css
⏺ browser_evaluate getComputedStyle(btn)
{ color: "rgb(15, 23, 42)",
background: "rgb(217, 128, 73)" }
Contrast 6.06:1. Passes AA.
Two edits and a check in between, with nobody asking twice.
Install
claude mcp add playwright -- npx @playwright/mcp@latest
claude mcp list
The second line is the one that matters. Without playwright: ... ✔ Connected, none of the rest exists:
playwright: npx @playwright/mcp@latest - ✔ Connected
It installs into whichever project you're in. Add --scope user if you want it everywhere. In a backend-only repo it's dead weight, so per-project is usually the sane call, the same reasoning behind how many MCPs actually earn a slot.
What you're actually handing over
Twenty-four tools in the version in front of me (1.63.0-alpha). Five of them carry the work: browser_navigate, browser_snapshot, browser_evaluate, browser_click and browser_console_messages. The rest covers specific cases: forms, network, tabs, file uploads, keyboard.
The count is not the point. How it looks at the page is. Playwright MCP doesn't hand Claude an image, it hands over the accessibility tree. Here is an entire page seen from the inside:
- generic [ref=e2]:
- heading "Checkout" [level=1] [ref=e3]
- paragraph [ref=e4]: "Total: 42.00 EUR"
- button "Pay now" [ref=e5]
Three and a half lines. A screenshot of that same page costs two orders of magnitude more context and needs a model that can look at images. And even then, a screenshot doesn't give you a number. It gives you something to have an opinion about.
Why measurement beats screenshots
That tree is real output from a page I wrote to test this. Read it again. The heading is there. The total is there. The button is there and named exactly as it should be.
The page is broken. The button is white text on a white background.
Nothing in the accessibility tree says so, because the tree describes structure, not appearance. An agent looking only at that concludes it's done, and it's right within what it can see. The only way to catch it is to ask for the measurement:
browser_evaluate
() => {
const b = document.querySelector('#pay');
const s = getComputedStyle(b);
return { color: s.color, background: s.backgroundColor };
}
{ "color": "rgb(255, 255, 255)",
"background": "rgb(255, 255, 255)" }
No room for interpretation there. Two identical values are two identical values, and Claude fixes it without arguing.
Which gives you the one rule that matters when you hand an agent an instrument: it has to return a number or a boolean, not a description. A screenshot to look at produces an opinion. A getComputedStyle produces a fact. It iterates on facts by itself; on opinions it comes back and asks you.
Ask for the change and the check together
The habit change is a single sentence, and it is the whole point of this tip:
Bump the line height on the pricing card to 1.6 and check
on localhost:3000 that the text doesn't overflow its box.
Instead of:
Bump the line height on the pricing card to 1.6.
The second version ends with you opening a browser. The first ends with Claude opening it, comparing scrollHeight against clientHeight, and fixing the overflow it just caused before it says anything to you.
You set the stop condition in that same sentence. Without an explicit one the agent keeps polishing, the classic failure of any loop like this. If the task runs long or you'll repeat it, /goal exists for precisely that.
The browser is one case, not the idea
Playwright MCP is the instrument when your work lives behind a URL. The idea underneath has nothing to do with frontend: what makes an agent converge is having a way to check its own work that doesn't depend on you confirming it.
| What you're touching | The instrument that closes the loop |
|---|---|
| A page or a web app | Playwright MCP (browser_evaluate, browser_console_messages) |
| An API | curl against the endpoint you touched, and the status code |
| A domain function | The test that fails first and passes after |
| A file that renders (SVG, image, PDF) | A script that measures the output, not a glance at it |
| Performance | A Lighthouse run with a concrete threshold |
Same pattern, different skin. If your loop runs against a Figma design, it's already written up: Figma and Chrome MCP build exactly this with the spec as ground truth. What changes with Playwright is that you need neither Figma nor a reference design. The claim you made in the prompt is enough.
I've had this as a rule in my own CLAUDE.md for months, in these words: never call a task done without proving it works. And the skill I use to generate LinkedIn images contains no "take a look and see how it turned out". It contains a script that measures clipping, footer margin and the gap between blocks, and returns OK or MAL. Both are this tip applied outside the browser.
Four things that will happen on day one
file:// is blocked. Point it at a loose HTML file and the server answers Error: Access to "file:" protocol is blocked. You need something serving over HTTP, even python3 -m http.server.
It starts headed. By default a visible browser window opens, which during a long session turns into a window-juggling act. Install it with the flag if you'd rather it stayed out of sight:
claude mcp add playwright -- npx @playwright/mcp@latest --headless
The page comes from cache and the measurement doesn't move. This happened to me writing this tip: I edited the CSS, measured again, and got identical values back. The file was already fixed; the browser was serving the previous copy. When two consecutive rounds return exactly the same numbers, suspect the cache before you suspect the edit, and navigate with a different query string.
The console counts errors that aren't yours. My run reported Console: 1 errors and the error was a 404 on the favicon. When you ask it to read the console, tell it what counts as a real failure.
Reference
| Tool | What it does in the loop |
|---|---|
browser_navigate |
Open the URL where the change lives |
browser_snapshot |
See the page structure as text, not pixels |
browser_evaluate |
Pull the number that decides pass or fail |
browser_console_messages |
Catch the runtime error the UI doesn't show |
browser_click |
Drive to the state where the change manifests |
| Command | What it does |
|---|---|
claude mcp add playwright -- npx @playwright/mcp@latest |
Installs it in the current project |
claude mcp add playwright --scope user -- npx @playwright/mcp@latest |
Installs it across all your projects |
claude mcp list |
Confirms it reports ✔ Connected |
claude mcp remove playwright |
Removes it from the project |
If you're weighing this against the other ways to give Claude a browser, the Chrome extension versus Chrome DevTools MCP comparison still holds, and Playwright lands on the same side as the latter: a clean browser, none of your cookies, none of your open sessions.
Requirements
- Node installed, since the server runs through
npx - Your app served over HTTP on some port
- Playwright downloads its browser the first time you launch it
Official docs: Connect Claude Code to tools via MCP · Playwright MCP