Green CI, Broken UI: Testing Rails UIs with Claude Code and Playwright MCP

A Rails app's checkout page passed every test in the suite: the right total, the right line items, a 200 on every request. On a phone, the shipping address label ran straight past the edge of the screen and got cut off mid-word, because nothing in that suite ever rendered the page at a real width and looked at it.
No test in that suite catches that. A test checks what the server sent back, not what a specific viewport does with it once a browser lays it out. Catching it, and confirming the fix, meant doing something no test assertion does: opening the page at a phone width in a real browser and looking at what actually renders.
No test in that suite catches that. A test checks what the server sent back, not what a specific viewport does with it once a browser lays it out. Catching it, and confirming the fix, meant doing something no test assertion does: opening the page at a phone width in a real browser and looking at what actually renders.

It took a couple of minutes, and it's now a standing part of how this JetRockets team ships Rails frontend work.

This article covers how Claude Code and Playwright MCP fit into that process: setting it up, running it as a developer before a PR goes out, and using it as the last step of a UX/UI review when the code alone can't settle a question.

Why a Green CI Isn't Enough for a Rails App

Automated tests prove that your Rails app does what you told it to do. They don't prove that the page a user sees is usable. A view can return a 200 and still render a broken layout, exactly like the checkout page above: a test can assert the response is correct without ever checking what a real viewport does with it. A form can submit correctly and still start with the cursor in the wrong field. A button can work perfectly and look disabled.

None of that shows up in a test. A missing autofocus on a form's first field, a clickable element that keeps the default arrow cursor instead of a pointer, a label that overflows its container on a narrower screen. None of these are hard to fix. They're tedious to find across many screens and screen sizes, which is why they slip through. The question that matters isn't "how do you test harder?" It's "how do you make this check cheap enough that nobody skips it?"

Testing in Three Layers

The answer is a layered process, where each layer is cheap and catches what the previous one can't.
  1. Automated tests and CI. Tests prove the logic. This is table stakes, and it comes first.
  2. A visual pass by the developer, before the PR. Claude drives a real browser through the pages the change touches, and reports rendering problems, console errors, and failed requests.
  3. A UX/UI review of the diff, with the browser as a tiebreaker. A review skill reads the code against a library of frontend rules. When the code leaves a genuine doubt that only a rendered page can settle, it escalates to Playwright to answer that specific question.

The rest of this article covers how to set up the tooling, then layers 2 and 3.

Playwright MCP Setup for Claude Code in Five Minutes

You need Node.js, Claude Code, and a Rails app to test against. Add the Playwright MCP server:
claude mcp add playwright -- npx @playwright/mcp@latest
Any other MCP-compatible client adds it the same way, through the standard mcpServers config, pointing at npx @playwright/mcp@latest.

This gives Claude Code a set of browser_* tools. The ones used most:
  • browser_navigate — Open a URL
  • browser_snapshot — Read the page's accessibility tree
  • browser_take_screenshot — Save an image of the page
  • browser_click, browser_type, browser_fill_form — Interact with the page
  • browser_resize — Change the viewport
  • browser_console_messages — Read JS errors and warnings
  • browser_network_requests — See requests and responses
  • browser_evaluate — Run JS inside the page

Two defaults are worth knowing before your first run. The browser opens headed, so you watch everything Claude Code does, which is what you want while you're learning to trust it. And it keeps a persistent profile, so a login survives between sessions. Use the --isolated flag if you'd rather start clean every time.

Verify it works: start your Rails app locally and ask, "Open http://localhost:3000, take a screenshot, and tell me if there are any console errors." If a browser opens and you get a picture back, you're done.

Layer 2: Claude Code's Visual Pass with Playwright MCP

This is the workflow that gets used most, and it's deliberately boring:
  1. Tests pass locally and in CI.
  2. Start the Rails dev server.
  3. Ask Claude Code to visit the pages your change touches and report what it sees.
  4. Open the PR.

A prompt that works well:
The server is running on localhost:3000. I changed pagination on the admin
stores, reports, and search pages. Visit each one, check that the page renders
with pagination controls, click to page 2, and report any console errors or
failed network requests. Screenshot anything that looks wrong.

Three habits make this useful instead of noisy.

Give It a Scope

"Check the whole app" produces a shallow answer and burns time. Name the pages, name the change, and say what "correct" looks like. The more specific the expectation, the more useful the report.

Read the Console and Network, Not Just the Picture

A page that looks fine can still be throwing JavaScript errors or firing a request that returns a 500 in the background. Asking for console messages and failed requests catches problems a screenshot hides. It's also the fastest way to debug: instead of describing a bug to someone, you point Claude Code at it and ask why the request failed.

Re-run After Follow-Up Changes

If review feedback changes anything that might touch the UI, run the pass again. It costs almost nothing, and "I only changed a helper" is how visual regressions get merged.

Layer 3: Review That Reads First and Renders Only When It Has To

Frontend changes get reviewed with a UX/UI review skill for Claude Code. It works in two phases, and the order matters.

Phase One: Static Analysis of the Diff

Claude Code first reads a few existing screens in the Rails app to learn how it builds buttons, forms, and cards, so it judges the change against the project's own conventions instead of some outside default. Then it checks the changed files against a library of rules grouped into four lenses, plus one cross-cutting pass:
  • Breakage: interactions and forms that fail under edge-case input.
  • Resilience: loading, error, and empty states; what the page shows when something goes wrong.
  • Clarity: copy and affordances that confuse instead of guide.
  • Visual consistency: spacing, typography, and component use measured against the project's own existing patterns, not an external design system.
  • Accessibility: WCAG 2.1 AA conformance, checked alongside whichever of the above lenses triggered it.

Each rule in that library is written the same way: what to detect, how confident a static read of the code can be about it, and a suggested fix. Most of what QA ends up flagging is visible in the code itself this way. A missing autofocus on the first field of a form, or a clickable element with no pointer cursor, shows up by reading the markup. That takes seconds and needs no browser. A team rebuilding this doesn't need this exact rule set, just the shape: group your own frontend conventions into a handful of lenses like the ones above, and write each rule as a detect/confidence/fix triple.

Phase Two: Playwright for Real Doubts

Some questions can't be settled by reading code: whether text really overflows with real dynamic content, whether contrast holds up once rendered, whether a focus ring is actually visible. When the static pass leaves one of those unresolved, Claude Code brings up the Rails app, opens the specific affected page with Playwright MCP, and answers that one question. It doesn't wander through the app looking for more.

It's built this way for two reasons: static checks are fast and deterministic, so they should do as much as they can, and a browser session is slower and easier to misread, so it earns its place only when nothing else can answer the question, a genuine, specific, unresolved doubt, never a blanket "check everything visually" pass.

Findings Say How Sure They Are

Every finding is tagged with how confident the check is, and the language reflects it. A doubt that couldn't be confirmed gets a hedge, never an assertion. A reviewer who reads a hedge knows to look. A reviewer who reads an assertion knows to fix. Blurring the two teaches people to ignore the whole report.

The skill also never edits code. It reports, and a person decides.

For QA: Faster UX and UI Checks

The same ideas work outside code review. QA can point Claude Code at a Rails app and get through the mechanical parts of a UX pass quickly, with checks like these.

Mobile Overflow

Resize the browser to 375x812 and visit /checkout, /profile, and /orders.
On each page, check whether the page scrolls horizontally, and list any
element that extends beyond the viewport width. Screenshot each page.
Under the hood this compares document.documentElement.scrollWidth against the viewport width through browser_evaluate. You don't need to know that to ask for it.

Focus and Cursor

On the sign-up and new-project forms, tell me which element has focus right
after the page loads. Then list every button and clickable element whose
computed cursor is not "pointer".

Other Checks That Fit the Same Pattern

  • Keyboard navigation: can you reach and submit the form with Tab and Enter only?
  • Empty, loading, and error states: what does the page show with no data, or when a request fails?
  • Dark mode and reduced motion, through browser_emulate_media.
  • The same page at phone, tablet, and desktop widths, side by side.

The goal isn't to replace a QA engineer's judgment. It's to remove the mechanical part of the job, so their time goes to what only a person can evaluate: whether the flow makes sense, whether the copy is clear, whether something feels wrong.

Ground Rules

Keep Automated Tests as the Foundation

A browser pass tells you a page worked once, today. It won't catch the regression six months from now. When Claude finds a real bug, turn it into a test.

Treat Visual Verdicts as a First Filter, Not a Final Word

Claude can tell you a button is present and clickable. It can't reliably tell you the layout is elegant, the spacing feels right, or the flow is confusing. Look at the screenshots yourself before you trust the verdict.

Log in Yourself, Every Time

Authenticate in the browser window, then let Claude continue. Never paste passwords into a prompt. Anything you type into a conversation can end up stored in logs, and the persistent profile already keeps your session for the next run.

Keep the Output Folder Out of Git and Off Tickets

Playwright MCP saves snapshots and screenshots in a .playwright-mcp/ folder inside your project. Those files can contain real user data from a staging or production-like environment. Add the folder to .gitignore and don't attach its contents to tickets without looking.

Confirm the App Is Actually Running Before You Start

If the server isn't up, Claude will report a connection error or, worse, a confident description of the wrong thing.

Point It at Local, Not a Shared Environment

Run this against your local environment by default. Staging is often just a copy of production, real user data included, which is the same reason the output folder rule above matters. Treat it with the same caution as production unless you've actually confirmed it's scrubbed. Never let an assistant click through a production (or production-like) admin panel with your real account.

The Underlying Rule

Every useful browser check has the same shape: a human knew what "correct" looked like, and Claude did the clicking, resizing, and reading that would have taken an hour by hand.

The ones that went nowhere had the opposite shape: a vague request like "check if everything looks okay," with no expectation behind it. Claude answered with confidence, and there was nothing to compare that answer against.

Two rules held up:

Claude Looks, You Judge

Use the browser to see faster, at more screen sizes, across more pages than you'd ever check by hand. Keep the decision about whether the result is acceptable with a person.

Read First, Render When You Must

Static checks answer most questions in seconds. Reserve the browser for the questions only a rendered page can settle.

If your team ships Rails frontend code, try the visual pass on your next PR. It takes a couple of minutes, and you'll know within the first run whether it's worth keeping.

That same discipline, verifying what AI produces instead of trusting it by default, runs through how JetRockets' Rails development services handle AI-assisted work end to end: from the Rails upgrades covered in Upgrading Rails Using AI: When It Helps and When It Lies to the UI checks covered here.
Share: