Vitest visual regression testing
UI Verify turns the Vitest browser-mode tests you already have into visual regression tests. One plugin archives what each test renders, UI Verify screenshots it in one pinned browser and diffs it against the baseline, and your coding agent sees what its change did to the UI before it opens a pull request. Every pull request is checked again. No Storybook, and no screenshots in your repo.




What is Vitest visual regression testing?
Vitest visual regression testing compares what a browser-mode component test renders against an approved baseline image, so a change to the pixels shows up as a diff you review. Unit assertions check the logic and never see the UI; a visual test catches the padding, color, font or layout change that every assertion passes straight through.
Vitest is a good place for it. A browser-mode test renders one component, or one page, in isolation with the data you give it, so most of what makes screenshot tests flaky on a live app is gone before you start. With @uiverify/vitest there is no per-test code to write: each test is captured as it ends.
How do you set up visual testing in Vitest?
Your tests need Vitest 4 or 5 in browser mode, on the Playwright provider (@vitest/browser-playwright), with Chromium. Setup takes a few minutes.
1. Install the SDK
Add @uiverify/vitest and Vitest's Playwright provider as dev dependencies.
npm i -D @uiverify/vitest@1.6.0 @vitest/browser-playwright2. Add the plugin to your Vitest config
uiverifyPlugin() is the whole integration. Every browser-mode test now archives its final DOM, plus the stylesheets, fonts and images that DOM uses.
import { defineConfig } from "vitest/config";
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" }] },
},
});3. Choose the moment, if you need to
A test with no extra code is captured when it ends. To capture at a moment you choose, or several named states, await your render and call takeSnapshot(). A test that calls it is not captured again at its end.
import { test } from "vitest";
import { render } from "vitest-browser-react"; // or your framework's browser render helper
import { takeSnapshot } from "@uiverify/vitest";
test("menu", async () => {
await render(<Menu />); // await it so the DOM is committed before capture
await takeSnapshot("closed");
});4. Run the tests and upload the archive
Run Vitest as usual, then upload ./uiverify-archive with your project API key. UI Verify replays each test, screenshots it and diffs it against the baseline.
npx playwright install --with-deps # one-time: browsers for Vitest browser mode
npx vitest run # writes ./uiverify-archive
UIVERIFY_API_KEY=your_key npx -y uiverify@1.8.0 upload --static-dir ./uiverify-archive5. Run it on every pull request
Run the same commands in CI, on pull requests and on your default branch; UI Verify renders only the tests a change affects. Accept the first default-branch build once, or add --auto-accept-changes to that upload, and it becomes the baseline every pull request diffs against. The full GitHub Actions workflow, global CSS and font setup, and whole-page captures are in the Vitest quickstart.
How does UI Verify compare to toMatchScreenshot and Chromatic?
Teams that visual-test Vitest today use Vitest's built-in toMatchScreenshot() assertion or Chromatic's Vitest plugin. All three start from the same browser-mode tests. They differ in where the screenshot is taken, where the baselines live, and who reviews a change.
| UI Verify | Vitest toMatchScreenshot() | Chromatic for Vitest | |
|---|---|---|---|
| Where the screenshot is taken | UI Verify's cloud, replaying an archive of each test in one pinned browser | The machine running the test, so each OS gets its own baseline (-chromium-darwin, -chromium-linux) | Chromatic's cloud, from an archive of each test |
| Where baselines live | Hosted, one per branch, resolved from git | PNG files committed in __screenshots__/ | Hosted by Chromatic |
| Updating a baseline | Accept in the dashboard or from your agent; merging advances it | Re-run with --update and commit the new PNGs | Accept in Chromatic's web app |
| Where you review a change | A check and a comment on the pull request, and the review queue | A failing test, with diff images written to disk | A check on the pull request, an opt-in pull request comment, and Chromatic's web app |
| Skipping unchanged tests | On by default: the Vite module graph picks the tests a change affects | No carry-forward: every test that runs is screenshotted and diffed again (vitest --changed narrows which tests run) | TurboSnap, supported for Vitest |
| Review from a coding agent | MCP tools return each diff and accept or reject it | The agent reads the failing test and the PNGs on disk | Its MCP server serves component docs, not build review |
| Intended change or regression | An optional AI judge labels each change and says why | A pixel threshold | A pixel diff, judged by a reviewer |
| Price | Free for 10,000 snapshots a month (50,000 for open source); paid plans include more, then per snapshot | Free, it runs in your CI | Per snapshot, with a free tier |
Vitest's own guide says why the built-in assertion needs care: rendering differs across operating systems, fonts and GPUs, so it recommends Docker, CI-only runs or a cloud service. Chromatic and UI Verify both take that cloud route. For the full Chromatic comparison, including pricing at your volume, see UI Verify vs Chromatic.
What does UI Verify add to Vitest?
- Your coding agent checks its own UI work. While it edits, the agent runs the affected Vitest tests, uploads the archive with
uiverify check, and reads the diffs over MCP, so it sees what its change did to the UI before it opens a pull request. See Check visual changes mid-edit. - Every pull request is checked again. UI Verify posts a check and a comment with the diff on the pull request, and a change waits in the review queue until you, or your agent, accept or reject it.
- The same pixels on every machine. Every capture is replayed in one fixed environment, so your Mac and the Linux CI runner produce the same screenshot. You still freeze in-app inputs like the clock and live data; the determinism checklist shows how.
- No screenshots in your repo. Baselines are hosted, one per branch, resolved from git, and every version of a test is kept, so you can see how a component looked build over build. See baselines.
- Only what changed re-renders. UI Verify re-renders the tests your pull request affects and carries the rest forward at a fifth of the price of a render, so a small change no longer pays for the whole suite. It is on by default; see skip unchanged.
- Baselines advance when you merge. Nothing to regenerate on the right OS and nothing to commit.
- Flaky changes stay out of the way. A change that does not reproduce on a re-render is marked flaky instead of turning the pull request red. See automatic flake detection.
- An AI judge, if you want one. Turn it on and each change is labeled an intended change or a likely regression, with the reason. See the AI judge.
When is toMatchScreenshot or Chromatic the better choice?
toMatchScreenshot() is the right tool for a small suite that only ever runs on one machine, or inside one Docker image everyone renders through. It is free, it needs no service, and it can screenshot in Firefox and WebKit as well as Chromium, where UI Verify renders Vitest captures in Chromium.
Chromatic is a strong choice if your team already uses it for Storybook and wants one tool and one bill, or relies on its designer sign-off workflow and published Storybooks. UI Verify fits when you want your coding agent to review its own UI changes, an AI judge on the pull request, and a lower price per snapshot.
Frequently asked questions
Do I need Storybook for Vitest visual testing?
No. @uiverify/vitest captures your Vitest browser-mode tests directly, and UI Verify renders them without a Storybook build.
Which Vitest versions does UI Verify support?
Vitest 4 and 5, in browser mode, on the Playwright provider (@vitest/browser-playwright) with Chromium. Tests that run in jsdom or happy-dom have no real DOM to capture.
Do I have to call takeSnapshot() in every test?
No. Every browser-mode test is captured when it ends. Call takeSnapshot() to capture at a moment you choose or several named states instead (a test that calls it is not captured again at its end), opt a test out with disableAutoSnapshot(), or scope capture to files like *.visual.test.tsx with Vitest's own include.
Can Vitest visual tests cover whole pages?
Yes. Render the page's component inside the layouts and providers your app wraps it in, give it fixture data instead of a live backend, and the capture shows the page the way users see it. The Vitest quickstart covers whole pages, including Next.js App Router routes.
Why do Vitest visual tests flake?
Usually live data or a capture taken before the component finished rendering, then the clock. UI Verify already holds animations, fonts and randomness still. Flaky Vitest visual tests walks through the fixes.
Why do toMatchScreenshot baselines fail in CI?
Operating systems rasterize text and edges differently, so a baseline made on a Mac does not match a render on a Linux CI runner. Why Vitest screenshot baselines drift in CI shows it and the fixes.
How much does Vitest visual testing with UI Verify cost?
It is free for 10,000 snapshots a month, and 50,000 a month for open-source projects. A snapshot is one capture (a test's end-of-test capture, or each takeSnapshot() call) in one browser at one viewport, and the tests your pull request does not touch are carried forward at a fifth of that price instead of re-rendered. See snapshot billing.
Can my coding agent set it up?
Yes. The Set up visual testing skill wires the plugin and CI, and Deterministic Vitest captures is the determinism checklist it applies. The Vitest quickstart also opens with a prompt you can paste into your agent.
Guides and posts
Visual-test your Vitest suite today
Add one plugin, upload once, and your next pull request is checked. Free for 10,000 snapshots a month, 50,000 for open source.