Vitest Cheatsheet
Visual Regression Testing
Use this Vitest reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
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.