expect().toMatchImageSnapshot()
expect().toMatchImageSnapshot() is a custom matcher that compares the subject to a baseline image snapshot.
It is also available on the global expect.
It can be used whether or not you enabled the auto preset.
Typically you disable auto snapshots when you take snapshots manually.
Disable auto snapshots
Section titled “Disable auto snapshots”You can disable auto snapshots at various levels:
import { vis } from 'vitest-plugin-vis/setup'
// Setup without auto snapshotsvis.setup()import { beforeAll, beforeEach, it } from 'vitest'import { setAutoSnapshotOptions } from 'vitest-plugin-vis'
beforeAll(() => { setAutoSnapshotOptions(false)})
beforeEach(() => { setAutoSnapshotOptions(false)})
it('...', () => { setAutoSnapshotOptions(false)})See setAutoSnapshotOptions.
A manual snapshot can be taken together with an auto snapshot in the same test.
The manual snapshot gets the key 1.
Basic usage
Section titled “Basic usage”it('should take snapshot manually', async ({ expect }) => { await render(<MyComponent />)
await expect(document.body).toMatchImageSnapshot()})Subject
Section titled “Subject”The subject can be:
- a Locator, for example
page.getByTestId('subject') - an element, for example
page.getByTestId('subject').element() document.body, or thebaseElementreturned byrender()(same as the body)- an image encoded as a base64 string
Anything else fails immediately. A string that is not base64 encoded fails with:
'toMatchImageSnapshot()' expects the subject to be an element, locator, or image encoded in base64 string, but got: abcThe same message is used for undefined, null, true, false, and numbers.
Options
Section titled “Options”The matcher accepts an optional options object.
snapshotKey
Section titled “snapshotKey”Customize the snapshot key.
await expect(document.body).toMatchImageSnapshot({ snapshotKey: 'my_custom_snapshot',})A key that contains a dash is rejected with Snapshot key cannot contain dash.
createMissingBaseline
Section titled “createMissingBaseline”When true and there is no baseline image yet,
the matcher writes the captured image as the baseline and the assertion succeeds,
without using Vitest’s -u (updateSnapshot) mode.
If a baseline already exists, it does not change the comparison.
it('creates baseline on first run', async ({ expect }) => { await render(<div data-testid="subject">save new content</div>) const subject = page.getByTestId('subject')
await expect(subject).toMatchImageSnapshot({ snapshotKey: 'create_missing_baseline_expect', createMissingBaseline: true, })
expect(await page.hasImageSnapshot({ snapshotKey: 'create_missing_baseline_expect' })).toBe(true)})failureThreshold and failureThresholdType
Section titled “failureThreshold and failureThresholdType”Control how much difference is allowed before the test fails.
failureThresholdType is 'pixel' (the default) or 'percent'.
// Allow up to 70 pixels to be differentawait expect(subject).toMatchImageSnapshot({ snapshotKey: 'unit_test', failureThreshold: 70,})
// Allow up to 1% differenceawait expect(subject).toMatchImageSnapshot({ snapshotKey: 'unit_test', failureThreshold: 1, failureThresholdType: 'percent',})When the difference is beyond the threshold, the error message says how far:
Expected image to match within 20 pixels but was differ by N pixels.Expected image to match within 0.1% but was differ by N.NN%.With no threshold the message is Expected image to match but was differ by N pixels.
(for percent, ... but was differ by N.NN%.).
N stands for the measured difference. The spec asserts the message shape, not the numbers.
comparisonMethod
Section titled “comparisonMethod”'pixel' (the default) compares pixel by pixel.
'ssim' compares structural similarity.
await expect(document.body).toMatchImageSnapshot({ comparisonMethod: 'ssim', snapshotKey: 'unit_test',})With ssim, a threshold in pixels works the same way, for example failureThreshold: 20 fails with
Expected image to match within 20 pixels but was differ by N pixels.
diffOptions
Section titled “diffOptions”For comparisonMethod: 'pixel', the options of pixelmatch:
await expect(document.body).toMatchImageSnapshot({ comparisonMethod: 'pixel', diffOptions: { threshold: 0.1, // Matching threshold (0-1, default: 0.1) includeAA: false, // Include anti-aliased pixels (default: false) alpha: 0.1, // Blending factor for unchanged pixels (default: 0.1) aaColor: [255, 255, 0], // Color for anti-aliased pixels [R, G, B] diffColor: [255, 0, 0], // Color for different pixels [R, G, B] diffColorAlt: null, // Alternative color for dark-on-light differences diffMask: false, // Draw diff over transparent background (default: false) },})For comparisonMethod: 'ssim', the options of ssim.js:
await expect(document.body).toMatchImageSnapshot({ comparisonMethod: 'ssim', diffOptions: { ssim: 'bezkrovny', // 'bezkrovny' (fast, default) | 'fast' (accurate) | 'weber' | 'original' windowSize: 11, // Window size for SSIM calculation (default: 11) k1: 0.01, // First stability constant (default: 0.01) k2: 0.03, // Second stability constant (default: 0.03) bitDepth: 8, // Bits per channel (default: 8) downsample: 'original', // 'original' | 'fast' | false },})timeout
Section titled “timeout”Set a timeout for taking the snapshot, in milliseconds. The default is 30000.
await expect(document.body).toMatchImageSnapshot({ timeout: 60000 })reviewImage
Section titled “reviewImage”When a snapshot mismatches, the matcher finds the changed regions and writes two files next to the diff image:
<name>.regions.json: the image size and one box per changed region.<name>.review.png: for each region, the baseline, result, and diff crops side by side.
The failure message lists the regions and the review image:
Changed: 2 regions: 30x30 at (0,0), 36x36 at (134,334)Review: ./__vis__/__diffs__/a.spec.ts/case-1.review.png (baseline, result, diff of each region)Open the review image first: it shows only what changed and is much smaller than the full images.
The review image is skipped, and the message says why, when cropping does not help:
- the image size changed:
Review: skipped, the image size changed; open Actual or Difference instead - the regions cover most of the image:
Review: skipped, the regions cover N% of the image
Set reviewImage: false to not write the review image.
The Review: line is then left out of the message, and Changed: and the .regions.json file are still written.
await expect(document.body).toMatchImageSnapshot({ reviewImage: false })expectToFail
Section titled “expectToFail”Expect the matcher to fail. It is useful for testing that changes are detected.
it('should detect differences', async ({ expect }) => { await render(<div data-testid="subject">World</div>) const subject = page.getByTestId('subject')
await expect(subject) .toMatchImageSnapshot({ expectToFail: true }) .then( () => { throw new Error('Should not reach') }, (error) => { expect(error.message).toMatch(/Expected image to match but was differ by \d+ pixels./) }, )})The failure message includes the paths of the images:
Expected: ./__vis__/.../....pngActual: ./__vis__/.../....pngDifference: ./__vis__/.../....pngChanged: 1 region: WxH at (X,Y)Review: ./__vis__/.../....review.pngIf the snapshot matches although expectToFail is set, the matcher fails with
Snapshot ... matched but expected to fail, followed by the options used, the diff, and the image paths.
If the image size changed, the message is Snapshot ... mismatched followed by
The image size changed from 128x128 to 64x64 (the sizes are the baseline and the result), and the review image is skipped.
Errors
Section titled “Errors”- Called outside a test:
'`toMatchImageSnapshot()` must be called in a test.' - Called in a concurrent test:
'`toMatchImageSnapshot()` cannot be called in a concurrent test because concurrent tests run at the same time in the same iframe and affect each other's environment.' - Snapshot key with a dash:
Snapshot key cannot contain dash.
The matcher supports Vitest’s retry option. A test with { retry: 1 } can take more than one snapshot with different content.
Choosing a comparison method
Section titled “Choosing a comparison method”Use pixel (the default) for precise detection of small UI changes, text changes, and layout shifts.
Use ssim when images have gradients, shadows, or anti-aliasing, to reduce false positives from minor rendering differences.
A threshold of 1% is a reasonable start:
await expect(document.body).toMatchImageSnapshot({ comparisonMethod: 'ssim', failureThreshold: 0.01, failureThresholdType: 'percent', diffOptions: { ssim: 'bezkrovny', // faster; use 'fast' for accuracy },})