Set up UI Verify visual regression testing in a repo end to end — detect or scaffold the capture path (Storybook stories, a Playwright suite, or Vitest browser-mode component tests), install the uiver
Goal: take a repo from no visual testing to a green UI Verify check on every PR, with the first stories authored the cheap + deterministic way so the setup doesn't start flaky or expensive. This skill is the conductor — it decides the capture path, wires CI, then hands the authoring and determinism work to the focused skills.
This large 2,236-word instruction set is purpose-built for testing & qa work in AI coding agents. Paste directly into your CLAUDE.md or AI agent settings.
Goal: take a repo from no visual testing to a green UI Verify check on every PR, with the first
stories authored the cheap + deterministic way so the setup doesn't start flaky or expensive. This
skill is the conductor — it decides the capture path, wires CI, then hands the authoring and
determinism work to the focused skills.
UI Verify captures UI four ways. Choose by what the repo already has:
.storybook/ or @storybook/ in package.json, or is a@playwright/test suite and you want to test assembled real@vitest/browser--screenshots; UI Verify diffs, judges, and
baselines them with no rendering. There are no stories to author and determinism is owned by your
capture harness, so Steps 2-3 (client SDK + story authoring) don't apply — install the CLI
(npm i -D uiverify), then jump to Step 4 and wire the CI upload with --screenshots ./screenshots
in place of --static-dir. Upload only the screens a PR changed and the rest carry forward.If none exists and the repo is a browser app, scaffold Storybook (npx storybook@latest init) — it's
the lower-friction path and gives you deterministic isolation for free (see storybook-visual-testing).
Storybook:
npm i -D uiverifyPlaywright — swap the import so every test also archives its UI state:
- import { test, expect } from '@playwright/test';
+ import { test, expect } from '@uiverify/playwright';npm i -D @uiverify/playwright uiverifyVitest — add the plugin to vitest.config.ts so every browser-mode test archives its final DOM:
import { playwright } from '@vitest/browser-playwright';
import { uiverifyPlugin } from '@uiverify/vitest/plugin';
export default defineConfig({
plugins: [uiverifyPlugin()],
test: { browser: { enabled: true, provider: playwright(), instances: [{ browser: 'chromium' }] } },
});npm i -D @uiverify/vitest @vitest/browser-playwright uiverifyDon't snapshot whatever exists as-is. Author (or refactor) the first few stories using the two
authoring skills, in this order:
economical-visual-tests — collapse variant matrices into gallery/data-driven stories so you snapshotstorybook-visual-testing (Storybook), playwright-visual-testing (Playwright), orvitest-visual-testing (Vitest) — remove the run-to-run variation the tool can't neutralize from
outside your app (mostly: the clock, live data, infinite animations).Add a CI workflow so every PR gets a check, and so it runs once on your default branch — that
first default-branch build is what becomes the baseline every PR diffs against, so without the push
trigger every PR shows all-new until a merge happens to seed it. Store the project's UIVERIFY_API_KEY
(a uv_proj_… key from the dashboard) as a repo secret, then run the uiverify CLI (installed in
Step 2). Change [main] to whatever your default branch is (master, develop) — a repo whose default
isn't main gets a push trigger that never fires. Accept that first build once, or add--auto-accept-changes to the upload on your default branch to seed it automatically.
# .github/workflows/visual.yml
name: Visual
on:
push:
branches: [main] # your default branch, so the first build seeds the baseline
pull_request:
jobs:
uiverify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 } # full history so baseline ancestry works
- uses: actions/setup-node@v4
with: { node-version: 24, cache: npm }
- run: npm ci
- run: npm run build-storybook -- --stats-json # ./storybook-static + the dependency graph
- run: npx uiverify upload --static-dir storybook-static
env:
UIVERIFY_API_KEY: ${{ secrets.UIVERIFY_API_KEY }}
# Playwright / Vitest instead (browsers aren't preinstalled on the runner): run
# "npx playwright install --with-deps && npx playwright test" (or "npx vitest run"), then
# "npx uiverify upload --static-dir uiverify-archive".
# Screenshot upload (native / mobile / React Native): produce your PNGs (Detox, Maestro, etc.),
# then "npx uiverify upload --screenshots ./screenshots" in place of the build+upload steps.Keep --stats-json on the build - it is the default, not a tuning step (see "Render only what the PR
could have changed" below). Locally the same thing is:
npm run build-storybook -- --stats-json && UIVERIFY_API_KEY=uv_proj_… npx uiverify upload --static-dir storybook-static
# Playwright: npx playwright test && npx uiverify upload --static-dir uiverify-archive
# Vitest: npx playwright install --with-deps && npx vitest run && npx uiverify upload --static-dir uiverify-archiveOptional — run only on PRs that can change the UI. The workflow above fires on every PR, and a
Playwright archive has no dependency graph of its own, so it renders in full unless you add one (a
Vitest or Storybook upload already renders only what the PR could have changed — see below). On a busy repo, scope the job to
UI-affecting changes. Two ways, and they differ on required checks:
paths: filter — add paths: to the trigger. Simplest, but it skips the dorny/paths-filter (required-check-safe) — a first job computes whether UI files changed; the# .github/workflows/visual.yml
on:
push:
branches: [main] # advance the baseline as PRs merge
pull_request:
jobs:
changes:
runs-on: ubuntu-latest
outputs: { ui: '${{ steps.f.outputs.ui }}' }
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: dorny/paths-filter@v4
id: f
with:
filters: |
ui:
- 'src/components/**'
- 'src/app/**'
- 'packages/ui/**' # shared UI packages
- '**/*.css'
- '.github/workflows/visual.yml' # the workflow itself
uiverify:
needs: changes
runs-on: ubuntu-latest
steps: # every step below is also guarded: if: ${{ needs.changes.outputs.ui == 'true' }}
- if: ${{ needs.changes.outputs.ui == 'true' }}
uses: actions/checkout@v4
with: { fetch-depth: 0 }
# ...the setup-node / install / build / upload steps from Step 4, each guarded the same way.
# On main, add --auto-accept-changes to the upload so the post-merge render becomes the baseline.A missed render is silent — bias to include. An unnecessary run costs a few CI minutes; a
missedlib/ holding both a cn() helper the UI imports and unrelated db/AI code stays in the filter. If thatRender only what the PR could have changed — on by default for Storybook and Vitest. Whenever the
upload carries a dependency graph, UI Verify renders just the stories your commit's changed files could
affect and carries the rest of the baselines forward, so a PR's build time and bill track the size of the
diff, not the size of the suite. It is safe from the first run - when it
can't prove a story is unaffected it renders it, and a first build has nothing to carry, so it renders
everything anyway. Local uploads are safe on the default too: files you haven't committed count as
changed, so what they affect renders.
For Storybook the graph comes from the build, and an agent must not drop it: --stats-json writespreview-stats.json, the dependency graph the server reads. Without it every story renders, and the
upload log says so on every run. If the repo already has a visual workflow, edit its build and upload
steps in place rather than appending new ones, or the job builds Storybook twice and registers two builds
per PR.
Vitest needs no build flag: @uiverify/vitest 1.6+ writes the Vite module graph into the archive
on its own, so the plain upload is enough:
- run: npx uiverify upload --static-dir uiverify-archive
env:
UIVERIFY_API_KEY: ${{ secrets.UIVERIFY_API_KEY }}For Vitest the skip is only as granular as your test files: the server traces a changed source file to
the tests that
.visual.test.tsx file (each importing only what it renders). A file that colocates next to itsSecurityPage.visual.test.tsx beside SecurityPage.tsx) lets a one-component change skip everydist/, gitignored generated code) always renders; the test run prints [uiverify] The tests in N
file(s) always render: ... with the reason. It does nothing for the Playwright path out of the box - a Playwrightpreview-stats.json to theCheck the Storybook major before you write the build step - the flag is --stats-json on Storybook 8+,
but --webpack-stats-json on 7.x, and passing the wrong one fails the build step before the upload ever
runs.
Once wired, every PR gets a UI Verify check. The first run on your default branch captures the
baseline; after that each PR is diffed against it, and a real visual regression (or an operational
failure like a broken build or network error) turns the job red. Strict-by-default is intentional: a
silently dropped upload must not leave CI green. If the first few diffs are noisy, don't disable the
check — loop back to the determinism skill (Step 3) to remove the flake at its source.
The check gates the PR; the
MCP is how your coding agent reviews it without leaving the terminal —curl, or the image tools come backclaude mcp add --scope project --transport http uiverify https://uiverify.ai/mcpThen restart Claude Code, run /mcp, pick uiverify, and log in to UI Verify in the browser. That one
login reaches every project in every team the user belongs to, so it is done once, not per project, and
there is no API key in the config: the committed .mcp.json holds only the URL and each teammate logs in
with their own account.
Then hand the review loop to
triage-visual-changes (it has the per-client setup and the recipes:making-ui-changes and point your AGENTS.md / CLAUDE.md at it.* The setupCLAUDE.md — do it as part of setup: install the making-ui-changes skill (the playbookAGENTS.md andCLAUDE.md so every tool (Claude, Codex, Cursor, …) sees it:## UI changes
- Before changing any component, page, or styles, read the `making-ui-changes` skill and follow it:
reuse before you create, add/update the story or capture in the same change, check the blast radius on
shared components, and prove the change with a visual test before calling it done.That turns a one-time wiring into a habit the agent follows on every change.
economical-visual-tests applied).Setup Visual Testing is a free testing & qa skill for AI coding agents. Set up UI Verify visual regression testing in a repo end to end — detect or scaffold the capture path (Storybook stories, a Playwright suite, or Vitest browser-mode component tests), install the uiver. It provides a specialized system prompt that configures your agent with testing & qa expertise.
Copy the system prompt below and paste it into your CLAUDE.md file or Claude Code's custom instructions to activate Setup Visual Testing.
Setup Visual Testing is compatible with Claude Code, Cursor, GitHub Copilot, Windsurf, OpenClaw, Cline, and any AI agent that supports custom system prompts or .cursorrules files.
Yes, Setup Visual Testing is completely free and open source. The full source is available on GitHub at https://github.com/uiverify/uiverify/blob/main/packages/skills/skills/setup-visual-testing/SKILL.md. You only need a subscription to the AI agent you use it with.
Weekly roundup of top Claude Code skills, MCP servers, and AI coding tips.