← Claude Code Hub
✦ Tip #172 Aug 21, 2026

Playwright MCP in Claude Code: give it eyes and it fixes its own work before you ask

Every Playwright MCP guide stops at the install. The interesting part starts right after: with a browser in front of it, Claude stops shipping blind and corrects itself.

Two facing cards: on the left the browser_snapshot output showing the accessibility tree of a checkout page with the button line highlighted, labelled looks done; on the right the browser_evaluate output where color and background both return rgb(255,255,255) over a white-on-white button, labelled contrast 1:1; below, the loop Edit, navigate, evaluate, Edit, evaluate with the return leg marked no match and the end marked match

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

Workshop for teams

Multiply your team's output without sacrificing quality: a 6 to 8 hour AI First workshop, online, on the Claude platform.

See the workshop
Free guide

The 51 essentials, as a PDF.

One page per tip. Five chapters. What I actually use daily in production — no theory, no fluff.

  • I. Getting started 10 tips
  • II. Awareness 3 tips
  • III. Mastery 22 tips
  • IV. Autonomy 10 tips
  • V. Comparison 6 tips
Are you a professional Web developer?

You'll receive the guide by email · You join the Gravitas newsletter · Unsubscribe anytime

of 51
#

Wmedia · 51 Tips
Free guide · 51 tips · 5 chapters

The 51 essentials, as a PDF.

Are you a professional Web developer? · Unsubscribe anytime