UI Verify
All docs
Getting started7 min readUpdated

Storybook visual regression testing

Add visual regression testing to Storybook in minutes: install the uiverify CLI, build your stories, and get a screenshot diff check on every pull request.

Set it up with your coding agent

Paste this into Claude Code, Cursor, or any coding agent and it wires up UI Verify from these docs.

Set up UI Verify visual regression testing for my Storybook project.

Follow https://uiverify.ai/docs/quickstart-storybook to install the packages and add the config. https://uiverify.ai/llms-full.txt has the same docs as one plain-text reference if you want to read them without fetching each page.

Then run one build and upload it right now, locally - no CI or GitHub App needed for this - so my first build lands in the dashboard and we can confirm the setup works before wiring anything else. Use the upload command from the quickstart. Open the build page it prints in my browser so I can see the captured stories.

Ask me for my UIVERIFY_API_KEY when you need it for the upload.

Install the UI Verify skills for this project - the making-ui-changes playbook, the economical-visual-tests, triage, and check-visual-changes playbooks, plus the deterministic-capture guide for this framework - so you can change UI safely, author cheap stable stories, preview an edit's visual impact before pushing, and review builds yourself:
npx skills add uiverify/uiverify \
  --skill making-ui-changes \
  --skill economical-visual-tests \
  --skill triage-visual-changes \
  --skill check-visual-changes \
  --skill storybook-visual-testing

Then add a short rule to my AGENTS.md and CLAUDE.md so you read the making-ui-changes skill before any UI change from now on: `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.`

After installing, restart this session (or reload the window) so the new skills load - you will not have them until I do.

Once that first build is in and looks right, wire the GitHub Actions workflow from the quickstart so every push is checked (add UIVERIFY_API_KEY as a repository secret), and remind me to install the UI Verify GitHub App from my setup page so the check and PR comment post - that's the one step only I can do, and it isn't needed for the first upload.
If this repo is public and takes pull requests from forks, use the two-workflow setup from https://uiverify.ai/docs/open-source-and-forks instead, so fork pull requests get the check too without exposing the key.

If you already have Storybook, you already have your capture targets: every story is a state worth screenshotting. UI Verify renders each one, captures it, and diffs it against the baseline. Here is the whole setup.

Prefer a complete, runnable example? The Storybook example is a small app wired to this exact path: clone the folder, add your key, and it works.

1. Get a project API key

Sign up at uiverify.ai, create a project, and copy its API key. Store it in your CI as a secret named UIVERIFY_API_KEY.

2. Build your Storybook

bash
npm run build-storybook -- --stats-json   # outputs ./storybook-static
UI Verify captures the Storybook you build, so anything your stories need to render must be in that build. Load your global stylesheet and any icon font in .storybook/preview.ts, the same import that makes your stories render in Storybook itself, and prefer bundling a web font over a CDN <link> so the capture stays self-contained. The rule of thumb: if a story looks right in your local Storybook, its fonts are already in the build. Empty icon glyphs or fallback text in a capture mean a stylesheet the story depends on is missing from preview.ts.
.storybook/preview.ts
import "bootstrap-icons/font/bootstrap-icons.css"; // an icon font
import "../src/styles/globals.css";                // your global CSS and base font

3. Upload it

Run this locally to establish your first baselines. Pass the API key inline (or export it first) - the CLI reads it from the environment.

bash
UIVERIFY_API_KEY=your_key npx -y uiverify@1.7.0 upload --static-dir ./storybook-static

Your first run captures every story as new and flags them for review - accept them to set your baselines. After that, each run diffs against those baselines and reports what moved.

4. Catch a change locally

You do not need CI or a pull request to see a diff. Change something visible in a story - a color, a label - then rebuild Storybook and re-run the exact same upload on the same commit. UI Verify diffs the new build against your first one and flags what moved, so you see the review flow before wiring anything up.

bash
npm run build-storybook -- --stats-json
UIVERIFY_API_KEY=your_key npx -y uiverify@1.7.0 upload --static-dir ./storybook-static

Open the build page it prints: the changed story is waiting in the review queue, where you accept or reject it. Accepting promotes the new screenshot to the baseline for that branch.

5. Wire it into CI

.github/workflows/visual.yml
name: UI Verify
on:
  push:
    branches: [main] # your default branch, so the first build seeds the baseline
  pull_request:

jobs:
  visual:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0   # full history so the baseline can be resolved
      - run: npm ci && npm run build-storybook -- --stats-json
      - run: npx -y uiverify@1.7.0 upload --static-dir ./storybook-static --only-changed
        env:
          UIVERIFY_API_KEY: ${{ secrets.UIVERIFY_API_KEY }}
The push trigger on your default branch matters as much as pull_request: it runs the job once on your default branch so the first build becomes the baseline every PR diffs against. Change [main] to whatever your default branch is (master, develop), or a repo whose default is not main gets a 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. Without it, every PR shows all-new until a merge happens.
fetch-depth: 0 matters: the default shallow checkout hides your branch history, and UI Verify needs it to resolve the right baseline. Without it you get phantom diffs.
Close the accept loop before you call setup done. The upload job exits non-zero on an unreviewed build, so after you accept the changes UI Verify flips its own check green but the failed Actions job stays red until it re-runs, and every accepted PR looks broken. Always set up one of the two: make UI Verify: <slug> a required status check, or add the auto-rerun workflow that clears the job the moment you accept. Both are in Require the check and auto-clear the CI job.

6. Review changes on the pull request

UI Verify posts a check and a comment on the PR. Changed stories go to a review queue where you accept or reject each one; accepting promotes the new screenshot to the baseline for that branch. See how the review model works.

7. Install the visual-testing skills

Give your coding agent the visual-testing playbooks so it can do the work in your repo: triage a build's changes, write economical visual tests, check an edit's visual impact before pushing, and keep captures deterministic. This installs just these skills, not the whole bundle. Restart the agent session afterward so they load.

bash
npx skills add uiverify/uiverify \
  --skill making-ui-changes \
  --skill economical-visual-tests \
  --skill triage-visual-changes \
  --skill check-visual-changes \
  --skill storybook-visual-testing

8. Review changes from your coding agent

Connect the UI Verify MCP and your agent can pull a build's changes into the conversation, look at each diff, read the AI judge's verdict, and accept the intended ones. Your project setup page has this command with the key already filled in; the header is your UIVERIFY_API_KEY. See Triage visual changes from your coding agent.

bash
claude mcp add --scope project --transport http uiverify https://uiverify.ai/api/mcp \
  --header 'Authorization: Bearer ${UIVERIFY_API_KEY}'

Project scope writes it to your committed .mcp.json, so the whole team gets it. The key is referenced as the UIVERIFY_API_KEY env var (single-quoted so your shell does not expand it at add time), not baked in, so the committed file never holds the secret: each teammate sets UIVERIFY_API_KEY in their environment and Claude Code expands it at runtime.

9. Install the GitHub App

Install the UI Verify GitHub App from your project's setup page and point it at your repo, so it can post a check and a comment on each pull request. This is the one step your coding agent cannot do for you. It does not block getting started: your first upload and baselines work without it, so add it when you want the results to show up on GitHub.

Speed up the build with --test

Build with --test (npm run build-storybook -- --test) for a test-optimized Storybook that builds 2-4x faster. It drops the docs-blocks, a11y, and coverage addons a snapshot build never needs, while still building every story canvas - which is exactly what UI Verify captures. The only thing it removes is autodocs and MDX docs pages, so keep it unless you deliberately snapshot those. It is a Storybook 8+ flag, so omit it on Storybook 7, and it composes with --stats-json: npm run build-storybook -- --test --stats-json.

Render only what changed

The CI workflow above already renders only what changed. --only-changed on the upload renders just the stories your pull request affects and carries the rest forward from their baselines, and --stats-json on the build writes the dependency graph it reads. Keep both: without --stats-json the flag has no graph and every story renders. It is the single biggest lever on build time and cost - see Skip unchanged stories.

Skip a story

Every story is snapshotted by default. To exclude one, set disableSnapshot in its parameters. UI Verify reads it under parameters.uiVerify, and also under parameters.chromatic, so an existing Chromatic config carries over unchanged. Set it on a single story, or on the component's meta to skip the whole file. A story with a play function runs its interaction testing steps first, and the snapshot captures the settled result.

Button.stories.tsx
export const Playground: Story = {
  // rendered in Storybook, never screenshotted or billed
  parameters: { uiVerify: { disableSnapshot: true } },
};

Frequently asked questions

Do I need to write new tests?

No. Your existing stories are the tests. If a component has no story yet, add one story that renders it - and prefer a dense gallery story over one story per variant.

Why does a story come back changed when nothing changed?

That is a flaky diff, and it is almost always run-to-run variation the screenshot faithfully captured - a clock, an animation, live data. See Fix flaky visual tests.

Visual testing for agents

UI Verify captures your UI on every pull request and an AI judge tells an intended change from a real regression. See how it works.

Get started