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