Visual testing for Claude Code means giving it a way to look at what it built: run the app, open it in a browser, take screenshots at the widths that matter, and compare them with what was intended. Without that, a passing build is the only signal Claude has, and a page can compile perfectly while its layout is broken. The simplest setup is Claude’s own browser access for exploratory checks, plus a small Playwright suite for checks you want to repeat.
Why compiling isn’t design QA
Claude Code’s best practices start with this point: give Claude a check it can run, such as tests, a build, or “a screenshot to compare”. Type checks and builds catch broken code. They don’t catch a sidebar overlapping content, a table forcing horizontal scroll on a phone, an invisible focus state or a button that wrapped onto two lines. Those are visual facts, and only a rendered page shows them.
Step 1: run the app locally
Visual checks need a running page. Tell Claude how to start your dev server in CLAUDE.md (for example pnpm dev on port 3000) so it doesn’t guess. For comparisons that must be stable, test a production build instead: development servers can add overlays and indicators that change between runs. We hit exactly that while writing this guide; more below.
Step 2: inspect in a browser
For exploratory checks, give Claude a browser it can drive. Two common options:
- Claude in Chrome. The Chrome integration lets Claude Code open pages, click, read console errors and take screenshots. Start it with
claude --chrome. - Playwright MCP. Microsoft’s Playwright MCP server gives the agent a browser it controls. Its README gives this command for Claude Code:
claude mcp add playwright npx @playwright/mcp@latestThen ask for evidence, not reassurance:
Open localhost:3000/settings/billing. Take screenshots at 1440px and
390px wide. Check for: horizontal scrolling, overlapping or clipped
text, buttons wrapping, and console errors. Compare with the brief.
List what's wrong before fixing anything, then show me the final
screenshots.Step 3: make the checks repeatable with Playwright
Exploratory checks depend on the agent remembering to do them. A Playwright suite runs the same checks every time, and Claude can run it and read the failures like any other test. Install it with the command from the Playwright docs:
npm init playwright@latestThe config below starts your app if it isn’t running (the webServer option), reuses it if it is, and defines a desktop and a mobile project so every test runs at both sizes.
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
use: {
baseURL: "http://localhost:3000",
},
webServer: {
command: "pnpm dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
projects: [
{ name: "desktop", use: { ...devices["Desktop Chrome"], viewport: { width: 1440, height: 900 } } },
{ name: "mobile", use: { ...devices["Pixel 7"] } },
],
});This test captures a full-page screenshot to look at, fails if the page is wider than the viewport, and compares the page with an approved baseline using toHaveScreenshot:
import { test, expect } from "@playwright/test";
const pages = ["/", "/settings/billing"];
for (const path of pages) {
test(`renders ${path}`, async ({ page }, testInfo) => {
await page.goto(path);
await page.waitForLoadState("networkidle");
// A full-page capture to look at, saved with the test results.
await page.screenshot({
path: testInfo.outputPath(`${testInfo.project.name}${path.replaceAll("/", "-")}.png`),
fullPage: true,
});
// Nothing wider than the viewport.
const overflow = await page.evaluate(
() => document.documentElement.scrollWidth - window.innerWidth,
);
expect(overflow).toBeLessThanOrEqual(0);
// Compared against an approved baseline on later runs.
await expect(page).toHaveScreenshot({
fullPage: true,
animations: "disabled",
mask: [page.locator("nextjs-portal")],
maxDiffPixels: 100,
});
});
}
test("keyboard focus is visible", async ({ page }) => {
await page.goto("/");
await page.keyboard.press("Tab");
const focused = page.locator(":focus");
await expect(focused).toBeVisible();
const outline = await focused.evaluate((el) => {
const s = getComputedStyle(el);
return s.outlineStyle !== "none" || s.boxShadow !== "none";
});
expect(outline).toBe(true);
});We ran this suite with Playwright 1.64 against a local Next.js site, with two of our own page paths in place of the ones above, on both projects. Some notes from that run:
- The first run fails on purpose. When no baseline exists, Playwright writes one and fails the test. Review those images, then run again.
- Dev overlays cause noise. Our second run failed on one page by about 1% of pixels. The difference was the Next.js development indicator in the corner. The
maskline above hides that element; disabling animations removes another common source. After that, three consecutive runs passed. Masking is a workaround: comparing against a production build avoids the problem entirely. - Baselines are per platform. Snapshot names include the browser and operating system, and the docs warn that rendering varies by OS, hardware and settings. Generate baselines in the same environment that checks them, such as your CI image.
- Updating is deliberate. After an intended change, run
npx playwright test --update-snapshotsand review the new images before committing them.
Step 4: compare expected and actual
A baseline comparison tells you something changed. It doesn’t tell you whether the original was right. For new work, the comparison is against intent: the brief, the Figma frame or the reference. Ask Claude to put the two side by side and describe differences in specific terms (spacing, alignment, type, missing states) rather than “looks close”. When Playwright reports a diff, it writes expected, actual and diff images to the results folder; Claude can open those directly.
Step 5: check responsive layouts deliberately
Two projects are a minimum. Add widths where your layout changes, and test real content: long names, empty lists, hundreds of rows. Our guide to making AI-generated UI responsive covers which widths to choose and what to look for.
A repeatable loop
- Brief the change, including the widths and states to check.
- Claude implements it.
- Claude opens the page and screenshots the agreed widths.
- Claude compares against the brief or reference and lists differences.
- Claude fixes them and runs the Playwright suite.
- You review the final screenshots and any baseline updates.
Put steps 3 to 5 in your CLAUDE.md so they happen without asking, or in a skill you call with one command. Screenshots also make your own review faster: judging two images takes less time than re-running everything.
What visual tests don’t cover
Screenshots catch layout, overflow and regressions. They don’t judge whether the hierarchy is right, whether the page fits the product, or whether it’s accessible to someone using a screen reader. Pair this with an accessibility review, and with the design judgement in how to make Claude Code build better UI.