vitest-image-snapshot
v0.6.46
Published
Visual regression testing for images. Compare rendered outputs against reference images to catch visual bugs.
Maintainers
Readme
Image Snapshot Testing
Visual regression testing for images. Compare rendered outputs against reference images to catch visual bugs.
- Native vitest integratio
- Accepts
ImageDataor PNG buffers - test WebGPU, Canvas, or any image processing - Aggregated HTML diff report for visual failures
Setup
1. Install
pnpm install --save-dev vitest-image-snapshot2. Import in your test file
import { imageMatcher } from "vitest-image-snapshot";
imageMatcher(); // Call once at the top level3. (Optional) Configure HTML diff report
Configure the reporter in vitest.config.ts:
import { defineConfig } from 'vitest/config'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
const __dirname = dirname(fileURLToPath(import.meta.url))
export default defineConfig({
test: {
include: ['src/test/**/*.test.ts'],
reporters: [
'default',
['vitest-image-snapshot/reporter', {
reportPath: join(__dirname, '__image_diff_report__'), // Absolute path recommended for monorepos
autoOpen: 'failures', // Auto-open report in browser on failures (or 'always' to always open or 'never')
}]
],
},
})Default behavior (no configuration):
- Report location:
{vitest.config.root}/__image_diff_report__/index.html - Auto-open:
failures(can override withIMAGE_DIFF_AUTO_OPEN=alwaysorIMAGE_DIFF_AUTO_OPEN=neverenv var)
Configuration options:
reportPath: Absolute or relative toconfig.root(default:'__image_diff_report__')autoOpen: Auto-open report in browser on failures, always or never (default: 'failures' or 'never' on CI)port: Port for html report server (default: 4343)
Basic Usage
Accepts standard browser ImageData or Buffer (pre-encoded PNGs):
// With ImageData (from canvas, shader tests, etc.)
await expect(imageData).toMatchImage("snapshot-name");
// With Buffer (pre-encoded PNG)
await expect(pngBuffer).toMatchImage("snapshot-name");
// Auto-generated name from test name (single snapshot per test only)
await expect(imageData).toMatchImage();Options
await expect(imageData).toMatchImage({
name: "edge-detection",
threshold: 0.2, // Allow more color variation
});See Match Options for all available configuration options.
Updating Snapshots
When you intentionally change shader behavior:
# Update all snapshots
pnpm vitest -- -u
# Update specific test file
pnpm vitest ImageSnapshot.test.ts -- -uDiff Report
If you added ImageSnapshotReporter to vitest.config.ts, failed tests generate a self-contained HTML report with:
- Expected vs Actual side-by-side
- Diff visualization (mismatched pixels highlighted)
- Mismatch statistics
- All images copied to report directory (portable and shareable)
Default location: {vitest.config.root}/__image_diff_report__/index.html
Monorepo behavior: In workspace mode (running tests from workspace root), each package's report goes to its own directory when using absolute paths in config.
Auto-open on failure:
IMAGE_DIFF_AUTO_OPEN=failures pnpm vitest
# Always auto-open
IMAGE_DIFF_AUTO_OPEN=always pnpm vitestOr enable via inline reporter config:
reporters: [
'default',
['vitest-image-snapshot/reporter', { autoOpen: 'failures' /* or 'always' or 'never' */ }]
]IMAGE_DIFF_AUTO_OPEN will override reporter config if both are set.
CI
When a CI environment is detected then IMAGE_DIFF_AUTO_OPEN and config options are ignored and autoOpen is set to "never".
The report is still generated and can be saved as an artifact for review or published to a static hosting service if you prefer.
Directory Structure
package-root/
├── src/test/
│ ├── ImageSnapshot.test.ts
│ ├── __image_snapshots__/ # Reference images (commit to git)
│ │ └── snapshot-name.png
│ ├── __image_actual__/ # Current test outputs (gitignore, always saved)
│ │ └── snapshot-name.png
│ └── __image_diffs__/ # Diff visualizations (gitignore, only on failure)
│ └── snapshot-name.png
└── __image_diff_report__/ # HTML report (gitignore, self-contained)
├── index.html
└── src/test/ # Copied images preserving directory structure
├── __image_snapshots__/
├── __image_actual__/
└── __image_diffs__/Notes:
__image_actual__/saves on every run (pass or fail) for manual inspection- Report copies all images to
__image_diff_report__/preserving directory structure - Report is self-contained and portable (can be zipped, shared, or committed)
API Reference
toMatchImage()
Vitest matcher for comparing images against reference snapshots.
await expect(imageData).toMatchImage(nameOrOptions?)Parameters:
imageData: ImageData | Buffer- Image to comparenameOrOptions?: string | MatchImageOptions- Snapshot name or options
Match Options:
interface MatchImageOptions {
name?: string; // Snapshot name (default: auto-generated from test name)
threshold?: number; // Color difference threshold 0-1 (default: 0.1)
allowedPixelRatio?: number; // Max ratio of pixels allowed to differ 0-1 (default: 0)
allowedPixels?: number; // Max absolute pixels allowed to differ (default: 0)
includeAA?: boolean; // Disable AA detection if true (default: false)
}How Comparison Works
Image comparison uses pixelmatch, which converts RGB to YIQ color space for perceptually-weighted comparison. YIQ separates luminance (Y) from chrominance (I, Q), making the comparison more aligned with human perception than raw RGB byte comparison.
The threshold option (0-1) controls sensitivity to color differences in YIQ space.
Color space note: Images are compared as raw RGBA bytes. If using display-p3 (supported in WebGPU via GPUCanvasConfiguration.colorSpace), ensure both reference and actual images use the same color space.
Examples
WebGPU Shaders
import { imageMatcher } from "vitest-image-snapshot";
import { testFragmentImage } from "wgsl-test";
imageMatcher();
test("shader output matches snapshot", async () => {
const result = await testFragmentImage({
projectDir: import.meta.url,
device,
src: `@fragment fn fs_main() -> @location(0) vec4f { return vec4f(1.0, 0.0, 0.0, 1.0); }`,
size: [128, 128],
});
await expect(result).toMatchImage("red-output");
});Canvas/DOM ImageData
import { imageMatcher } from "vitest-image-snapshot";
imageMatcher();
test("canvas output matches snapshot", async () => {
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d')!;
// Draw something
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 128, 128);
const imageData = ctx.getImageData(0, 0, 128, 128);
await expect(imageData).toMatchImage("red-canvas");
});Troubleshooting
"No reference snapshot found"
First run: Snapshot created automatically
CI: Run with -u locally first, then commit snapshots
Images don't match but look identical
thresholdtoo strict - increase tolerance- GPU/driver differences - use
allowedPixelRatio - Anti-aliasing differences - set
includeAA: true
Build Version
vitest-image-snapshot is currently part of the wesl-js monorepo. (It'll eventually move to it's own repo).
Contributions
See Implementation.md for details and feature ideas.
