🔧 Setup Visual Testing

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

About Setup Visual Testing

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.

Key Capabilities

  • Storybook path — the repo has `.storybook/` or `@storybook/*` in `package.json`, or is a
  • Playwright path — the repo has a `@playwright/test` suite and you want to test assembled real
  • Vitest path — the repo already runs component tests in Vitest browser mode (`@vitest/browser`
  • Screenshot-upload path — the repo is native / mobile / React Native (or otherwise can't
  • import { test, expect } from '@playwright/test';

What This Skill Covers

  • Step 1 — pick the capture path (don't ask the user; detect it)
  • Step 2 — install the client
  • Step 3 — author the first stories the right way
  • Step 4 — wire CI
  • Step 5 — the check gates every PR
  • Step 6 — connect your agent so it can see the diffs (MCP)

How It Works

  1. `economical-visual-tests` — collapse variant matrices into gallery/data-driven stories so you snapshot
  2. `storybook-visual-testing` (Storybook), `playwright-visual-testing` (Playwright), or

Use Cases

  • Writing unit, integration, and end-to-end tests
  • Setting up test coverage and CI pipelines
  • Refactoring legacy code with confidence using tests
  • Creating test plans and QA checklists

Example Prompts

Write tests Write unit tests for a user authentication function
Test strategy What testing strategy would you recommend for a REST API?

System Prompt (2,236 words)

Set up UI Verify, end to end

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.

Step 1 — pick the capture path (don't ask the user; detect it)

UI Verify captures UI four ways. Choose by what the repo already has:

  • Storybook path — the repo has .storybook/ or @storybook/ in package.json, or is a
component/design-system library. Each story is a snapshot. This is the default; prefer it when components exist in isolation.
  • Playwright path — the repo has a @playwright/test suite and you want to test assembled real
pages (the app, staging, or prod), not isolated components. Each test archives its final UI state for UI Verify to replay + diff.
  • Vitest path — the repo already runs component tests in Vitest browser mode (@vitest/browser
with a Playwright provider), and you want those component renders diffed without adopting Storybook. Each browser-mode test archives its final DOM. Prefer this over scaffolding Storybook only when a browser-mode suite already exists.
  • Screenshot-upload path — the repo is native / mobile / React Native (or otherwise can't
render in a browser) but already produces finished screenshots from its own harness (Detox, Maestro, native snapshot tests). Upload those PNGs directly with --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).

Step 2 — install the client

Storybook:

npm i -D uiverify

Playwright — 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 uiverify

Vitest — 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 uiverify

Step 3 — author the first stories the right way

Don'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 snapshot
the fewest billable shots for full coverage. Do this
before determinism work — fewer stories is less to make deterministic.
  • storybook-visual-testing (Storybook), playwright-visual-testing (Playwright), or
vitest-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).

Step 4 — wire CI

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-archive

Optional — 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:

  • Plain paths: filter — add paths: to the trigger. Simplest, but it skips the whole job, so it
reports no status. If the UI Verify check is a required status check, a PR touching none of those paths is stuck "Expected" and can't merge. Use only when the check isn't required.
  • dorny/paths-filter (required-check-safe) — a first job computes whether UI files changed; the
upload job always runs but guards each step on that result, so a backend-only PR no-ops yet still reports green and never wedges the required check. Prefer this.
# .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 missed
one renders nothing, the check passes green, and the unseen change becomes the next baseline every branch
inherits. So list every dir the UI is built from (components, pages, shared UI packages, global CSS) and
the workflow file itself
— and don't exclude a mixed folder just because it's mostly backend: a
lib/ holding both a cn() helper the UI imports and unrelated db/AI code stays in the filter. If that
over-runs, split the UI-facing code onto its own path, then filter on that — separate for granularity;
never trade coverage for speed.

Render 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 writes
preview-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
import it and carries the rest forward, so keep one page/component per
.visual.test.tsx file (each importing only what it renders). A file that colocates next to its
component (SecurityPage.visual.test.tsx beside SecurityPage.tsx) lets a one-component change skip every
other page; a single mega-test that imports half the app re-renders on every commit. A test whose imports
git can't follow (bundler output, as under Angular's unit-test builder, a workspace package's built
dist/, 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 Playwright
archive has no dependency graph, so every test renders unless you add a preview-stats.json to the
archive directory yourself ([module graph format](https://uiverify.ai/docs/skip-unchanged#module-graph-format)).

Check 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.

Step 5 — the check gates every PR

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.

Step 6 — connect your agent so it can see the diffs (MCP)

The check gates the PR; the MCP is how your coding agent reviews it without leaving the terminal —
list builds, look at the actual before/after pixels, post them to the PR, accept baselines. Add it as a
real MCP server (Claude Code, Cursor, Codex, …) — don't hand-roll curl, or the image tools come back
as blocks a shell can't render. One command for Claude Code, run in the project root:

claude mcp add --scope project --transport http uiverify https://uiverify.ai/mcp

Then 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:
bucket real regressions vs noise, put the before/after in the PR, bulk-accept). On a
passed build the
agent can still pull any story's current image by id to confirm it looks right.

Make it stick — install making-ui-changes and point your AGENTS.md / CLAUDE.md at it.* The setup
only pays off if new UI keeps getting captured, and it will only get captured if the agent reads the
discipline on
every* UI change instead of when a human remembers to. So don't leave the customer to
hand-edit CLAUDE.md — do it as part of setup: install the making-ui-changes skill (the playbook
for changing UI safely — reuse before you create, add the story/capture in the same change, check the
blast radius, prove it with a visual test), then add a one-line rule to both AGENTS.md and
CLAUDE.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.

Done when

  • A PR shows a UI Verify check with a baseline captured.
  • The first build's changed-stories list is empty or all-real (no clock/data/animation flake) — if not,
loop back to the determinism skill for that path.
  • Stories cover the variants without one-story-per-combo explosion (economical-visual-tests applied).

Frequently Asked Questions

What is Setup Visual Testing?

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.

How do I use Setup Visual Testing with Claude Code?

Copy the system prompt below and paste it into your CLAUDE.md file or Claude Code's custom instructions to activate Setup Visual Testing.

Which AI coding agents work with 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.

Is Setup Visual Testing free to use?

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.

Related Skills

Get the best new skills
in your inbox

Weekly roundup of top Claude Code skills, MCP servers, and AI coding tips.