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 wrapper

Setup

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

OptionDefaultDescription
failureThreshold0Max allowed difference
failureThresholdType'pixel''pixel' or 'percent'
customSnapshotsDir__image_snapshots__Where baselines live
customDiffDirsame as snapshotsWhere diff images are saved
blur0Blur before compare (reduce antialiasing noise)
allowSizeMismatchfalseAllow 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

ApproachCatches CSS?SpeedSetupBest For
DOM snapshotNoVery fastNoneStructure changes
@vitest/browser screenshotYesModerateLowComponent-level
Playwright + image-snapshotYesSlowMediumFull-page pixel diff
Storybook test-runnerYesSlowHighDesign system components
Playwright built-inYesSlowMediumE2E 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) or blur option.
  • Dynamic content — mask timestamps, avatars, ads before comparison.
  • Baseline drift — never auto-commit baseline updates in CI; require manual review.