Back to Blog

Visual Regression Testing With Claude Code

The Diffy plugin adds ten visual regression testing skills to Claude Code, so the agent that changed your CSS can also show you what it changed on the page.

Yuriy Gerasymov
Yuriy Gerasymov
17 Aug 2026 · 4 min read

An AI coding agent will happily refactor your stylesheet, swap a component library, or bump a dependency that ships its own CSS. What it cannot do, on its own, is look at the result.

That is the gap. The tests pass, the build is green, the diff in the terminal looks reasonable — and the pricing page is broken at 375px. Somebody has to open a browser to find out, which means the fastest part of the workflow now ends in the slowest part.

We built a Claude Code plugin to close it. The Diffy plugin gives Claude ten skills for visual regression testing, so the agent that made the change can capture the page, compare it against a baseline, and report which pages and breakpoints moved — inside the same session, before you ever alt-tab.

The plugin is open source under MIT at github.com/DiffyWebsite/diffy-skills.

Install it in two commands

Claude Code plugins are distributed through marketplaces. Add ours, then install the plugin:

/plugin marketplace add DiffyWebsite/diffy-skills
/plugin install diffy@diffy

The skills arrive namespaced — /diffy:visual-diff, /diffy:compare-environments, and so on — and Claude also invokes them on its own when your request calls for one. “Check whether my header change broke anything” is enough; you do not have to remember a command name.

Two prerequisites before the first run:

# 1. Install the diffy CLI (pick a bin directory you can write to)
wget -O /usr/local/bin/diffy https://github.com/diffywebsite/diffy-cli/releases/latest/download/diffy.phar && chmod a+x /usr/local/bin/diffy

# 2. Authenticate with your API key from https://app.diffy.website/#/keys
diffy auth:login <API_KEY>

That is the whole setup for remote capture. Local capture needs one more thing, which is the next section.

Two capture modes: local and remote

Most visual testing tools make you choose between “runs on my machine” and “runs in the cloud” once, at adoption time. The plugin ships both, and picks per request.

LocalRemote
Where screenshots are takenYour machineDiffy’s servers
What it capturesA running dev server (localhost:3000)A deployed URL
Needs DockerYesNo
Typical useMid-edit, before you pushProd vs staging, PR environments, CI
Skillsvisual-diff, upload-screenshotcompare-environments, remote-screenshot

Local mode captures inside Diffy’s published diffywebsite2/screenshot-worker container — the same Chromium, fonts, and Playwright runtime Diffy runs in production. That detail matters more than it sounds. A screenshot taken by your local Chrome on macOS does not render like a screenshot taken by a headless Linux browser in a data centre; font hinting alone will paint half the page red in a diff. Because local capture runs in the production rendering runtime, a screenshot from your laptop lines up with a Diffy baseline.

The trade is Docker. The first local capture runs a one-time provisioning step that pulls the image (about 1.1 GB), clones the worker code into ~/.cache/diffy/diffy-worker, and installs its dependencies inside the container. Later runs reuse all of it. The container reaches your dev server through host.docker.internal, and the plugin rewrites localhost and 127.0.0.1 in the URL for you.

Remote mode needs none of that — no Docker, no Node, no local browser. Diffy screenshots both sides on its own infrastructure. The requirement there is reachability: the URLs must be resolvable from Diffy’s network, either publicly or with the HTTP basic-auth credentials you pass in.

When a request is ambiguous — a bare “compare my UI” with no dev server or environment named — Claude asks which mode you want rather than guessing.

Workflow 1: catching regressions while you edit

This is the loop the plugin was built for, and it runs in two phases.

Before you touch anything, ask for a baseline. Claude captures the current state of your running app and remembers the screenshot ID in .diffy-visual/state.json:

Take a Diffy baseline of localhost:3000 before we start.

Then make the change — or have Claude make it. When you are ready, ask again:

Now compare against the baseline.

Claude re-captures, creates the diff, waits for it to finish, and reports back with a verdict like “3 of 6 page/breakpoint views changed; largest: /pricing at 375px (12.4%)” plus a shareable report link.

The part that keeps this maintainable: there is no config file. Pages, breakpoints, masks, delays, scroll behaviour, login steps, cookies, and custom JS all come from your Diffy project settings, and the worker re-bases each configured page onto your local URL. You maintain capture behaviour in one place, and it applies identically whether the screenshot is taken on your laptop or in production. When you need to change it, /diffy:update-project-settings edits the project rather than a file in your repo.

Workflow 2: comparing two deployed environments

The remote counterpart is a single request. Prod against staging:

Compare production and staging for project 12345.

Or a pull request environment against production:

Compare prod against https://pr-123.example.com.

Diffy screenshots both sides server-side and returns a diff ID. For CI, the underlying CLI accepts --commit-sha, which attaches a GitHub check to that commit — the same mechanism described in our post on running visual regression tests from a GitHub and Pantheon workflow. Results can also come back as JUnit XML, which most CI systems will render as test output without further work.

What Claude actually reports

Whichever workflow produced the diff, /diffy:get-diff-info turns it into something readable:

  • Overall changed percentage0 means nothing moved.
  • A per-page, per-breakpoint table, sorted by change percentage descending, so the worst regression is the first thing you read.
  • A shareable report link to the visual diff, for the teammate who wants to see it rather than read about it.
  • JUnit XML, when you ask for it.

Claude reads the JSON and summarizes it. You get the table, not a wall of nested objects.

The full skill list

Ten skills, composable. The two orchestrators do the whole job end to end; the rest are building blocks you can chain yourself.

SkillModeWhat it does
/diffy:visual-difflocalEnd-to-end before/after regression on your local UI
/diffy:compare-environmentsremoteCompare two environments server-side
/diffy:create-projecteitherCreate a project from a base URL, pages, and breakpoints
/diffy:get-project-settingseitherShow a project’s settings as export YAML
/diffy:update-project-settingseitherEdit pages, environments, breakpoints, masks, login, schedule
/diffy:upload-screenshotlocalCapture a running app in the worker container and upload it
/diffy:remote-screenshotremoteAsk Diffy to screenshot an environment on its servers
/diffy:get-screenshot-infoeitherCheck whether a screenshot set finished capturing
/diffy:compare-screenshotseitherCreate a diff from two existing screenshot set IDs
/diffy:get-diff-infoeitherSummarize a diff: percentages, table, report link, JUnit

What it will not do

Two limits worth stating plainly, because both are deliberate.

The plugin never deletes projects. Every skill that touches project settings carries an explicit instruction not to delete a Diffy project — not through the CLI, not through a direct API call, not through the web UI. If you ask, Claude refuses and points you at the dashboard. The repository backs that up for anyone hacking on the plugin itself with a permissions.deny list in .claude/settings.json that blocks delete-shaped diffy commands outright, and you can copy the same rules into your own project settings if you want a guardrail that does not depend on the model behaving. Handing an agent a destructive verb on your test history is not a feature.

It is not a local pixel diff. Diffy is a cloud service, and these skills use it — you need an account, an API key, and a project. In local mode the screenshots are captured on your machine and uploaded; the comparison itself happens on Diffy’s side, along with everything that rides on it — including the AI screenshot checks that flag cookie banners and CDN challenge pages before they turn into false positives.

One housekeeping note: the skills write small metadata files under .diffy-skills/ and .diffy-visual/. Add both to .gitignore — Claude offers to do it for you on first run.

Getting started

If you already use Diffy, the plugin is two commands away and works against your existing projects and their existing settings. If you do not, create a free account, set up a project with the pages and breakpoints you care about, and point the plugin at it.

And if you are new to the practice itself, start with what visual regression testing is and when it pays off — the plugin makes the loop faster, but the reason to run it at all does not change: false positives are the thing that kills adoption, and a well-configured project is what prevents them.

Issues and pull requests are welcome at DiffyWebsite/diffy-skills.

Related Articles

More guides on visual regression testing, QA automation, and keeping your site pixel-perfect as it changes.