Vitest Cheatsheet
Visual Regression Testing
Use this Vitest reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
What is Visual Regression Testing?
Visual regression tests capture screenshots (or snapshots of rendered HTML) and compare them pixel-by-pixel against a stored baseline. Diffs are flagged as failures, catching unintended UI changes.
Approach 1: HTML Snapshot Testing (Vitest Built-in)
The simplest form — snapshots of the DOM string, not pixels.
// Button.test.tsx import { render } from '@testing-library/react' import { expect, it } from 'vitest' import { Button } from './Button' it('matches DOM snapshot', () => { const { container } = render(<Button label="Click" variant="primary" />) expect(container).toMatchSnapshot() })
Update snapshots:
vitest -u
Limitation: Only catches HTML/attribute changes, not CSS regressions.
Approach 2: Vitest Browser Mode — Screenshot Tests
Vitest Browser Mode runs tests in a real browser (Chromium/Firefox/WebKit via Playwright or WebdriverIO).
Installation
npm install -D @vitest/browser-playwright playwright npx playwright install chromium
Config
// vitest.config.ts import { defineConfig } from 'vitest/config' import { playwright } from '@vitest/browser-playwright' export default defineConfig({ test: { browser: { enabled: true, provider: playwright(), headless: true, instances: [{ browser: 'chromium' }], }, }, })
Screenshot Snapshot
import { page } from 'vitest/browser' import { expect, it } from 'vitest' import { render } from 'vitest-browser-react' import { Button } from './Button' it('visual snapshot of Button', async () => { render(<Button label="Click me" />) const btn = page.getByRole('button', { name: 'Click me' }) await expect(btn).toMatchScreenshot() // pixel snapshot })
Snapshots stored in __screenshots__/ by default.
Updating Screenshots
vitest -u
Approach 3: Playwright + jest-image-snapshot
For pixel-diffing with tolerance control, combine Playwright screenshots with jest-image-snapshot.
Installation
npm install -D playwright jest-image-snapshot
npm install -D vitest-image-snapshot # vitest wrapperSetup
// vitest.config.ts import { defineConfig } from 'vitest/config' export default defineConfig({ test: { setupFiles: ['./tests/visual-setup.ts'], }, })
// tests/visual-setup.ts import { expect } from 'vitest' import { toMatchImageSnapshot } from 'jest-image-snapshot' expect.extend({ toMatchImageSnapshot })
Taking and Comparing Screenshots
// Button.visual.test.ts import { test, expect } from 'vitest' import { chromium } from 'playwright' test('Button visual regression', async () => { const browser = await chromium.launch() const page = await browser.newPage() await page.goto('http://localhost:3000/button-demo') await page.waitForSelector('button') const screenshot = await page.locator('button').screenshot() expect(screenshot).toMatchImageSnapshot({ failureThreshold: 0.01, // 1% pixel difference allowed failureThresholdType: 'percent', customSnapshotsDir: '__baselines__', customDiffDir: '__diffs__', }) await browser.close() })
jest-image-snapshot Options
| Option | Default | Description |
|---|---|---|
failureThreshold | 0 | Max allowed difference |
failureThresholdType | 'pixel' | 'pixel' or 'percent' |
customSnapshotsDir | __image_snapshots__ | Where baselines live |
customDiffDir | same as snapshots | Where diff images are saved |
blur | 0 | Blur before compare (reduce antialiasing noise) |
allowSizeMismatch | false | Allow different image dimensions |
comparisonMethod | 'pixelmatch' | 'pixelmatch' or 'ssim' |
Approach 4: Storybook + @storybook/test-runner
Storybook stories as visual test baselines with snapshot comparison.
npm install -D @storybook/test-runner npx playwright install
// package.json { "scripts": { "test-storybook": "test-storybook" } }
// .storybook/test-runner.ts import { checkA11y } from 'axe-playwright' import type { TestRunnerConfig } from '@storybook/test-runner' import { toMatchImageSnapshot } from 'jest-image-snapshot' const config: TestRunnerConfig = { setup() { expect.extend({ toMatchImageSnapshot }) }, async postVisit(page, context) { const img = await page.screenshot() expect(img).toMatchImageSnapshot({ customSnapshotsDir: `__screenshots__/${context.id}`, failureThreshold: 0.02, failureThresholdType: 'percent', }) }, } export default config
Approach 5: Playwright Built-in Visual Comparisons
If you're using Playwright for E2E alongside Vitest for unit/integration:
// e2e/button.spec.ts (pure Playwright — not Vitest) import { test, expect } from '@playwright/test' test('button visual', async ({ page }) => { await page.goto('/button-demo') await expect(page.locator('button')).toHaveScreenshot('button.png', { maxDiffPixelRatio: 0.02, }) })
npx playwright test npx playwright test --update-snapshots
Baseline Management
# Update all baselines vitest -u # Update only visual tests (filter by file name) vitest run -u visual # Store baselines in git git add __image_snapshots__ __screenshots__ git commit -m "update visual baselines"
CI Workflow
# .github/workflows/visual.yml
name: Visual Regression
on: [push, pull_request]
jobs:
visual:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run test:visual
- name: Upload diff artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: visual-diffs
path: __diffs__/Comparing Approaches
| Approach | Catches CSS? | Speed | Setup | Best For |
|---|---|---|---|---|
| DOM snapshot | No | Very fast | None | Structure changes |
@vitest/browser screenshot | Yes | Moderate | Low | Component-level |
| Playwright + image-snapshot | Yes | Slow | Medium | Full-page pixel diff |
| Storybook test-runner | Yes | Slow | High | Design system components |
| Playwright built-in | Yes | Slow | Medium | E2E visual checks |
Common Gotchas
- Font rendering differs by OS — always run visual tests on the same OS (Linux in CI is safest).
- Animations — disable transitions/animations before screenshotting:
page.addStyleTag({ content: '* { animation: none !important; transition: none !important; }' }). - Flaky antialiasing — use a tolerance threshold (
failureThreshold: 0.02) orbluroption. - Dynamic content — mask timestamps, avatars, ads before comparison.
- Baseline drift — never auto-commit baseline updates in CI; require manual review.