npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

vitest-plugin-vis

v4.1.0

Published

Vitest visual testing plugin

Readme

Vitest Visual Testing Plugin

NPM version NPM downloads

Vitest visual testing plugin allowing you to capture and compare image snapshots automatically and manually.

It requires Vitest Browser Mode to work.

This plugin is inspired by jest-image-snapshot, and extracted from storybook-addon-vis to use directly in Vitest.

Install

npm install --save-dev vitest-plugin-vis

pnpm add --save-dev vitest-plugin-vis

yarn add --save-dev vitest-plugin-vis

Config

The vitest-plugin-vis plugin can be used without customization.

// vitest.config.ts
import { playwright } from '@vitest/browser-playwright'
import { defineConfig } from 'vitest/config'
import { vis } from 'vitest-plugin-vis/config'

export default defineConfig({
	plugins: [vis()],
	test: {
		// vitest v2
		browser: {
			enabled: true,
			provider: 'playwright',
			name: 'chromium',
		},
		// vitest v3
		browser: {
			enabled: true,
			provider: 'playwright',
			instances: [
				{ browser: 'chromium' }
			]
		},
		// vitest v4
		browser: {
			enabled: true,
			provider: playwright(),
			instances: [
				{ browser: 'chromium' }
			]
		}
	}
})

This default configuration will:

  • Use the auto preset, taking image snapshot at the end on each rendering test.
  • Use pixelmatch as the image comparison method.
  • Set config to compare image snapshot with a failure threshold of 0 pixels.
  • Timeout for image comparison is set to 30000 ms.
  • Save image snapshots using the default directory structure.

preset

The preset option set up typical visual testing scenarios.

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { vis } from 'vitest-plugin-vis/config'

export default defineConfig({
	plugins: [
		vis({
			preset: 'auto' // or 'manual' or 'none'
		})
	],
})
  • auto (default): Automatically take a snapshot at the end of each rendering test.
  • manual: You control which test(s) should take a snapshot automatically with the setAutoSnapshotOptions() function.
  • none: Without preset. Set up your visual testing strategy in vitest.setup.ts.

When using the auto or manual preset, manual snapshots are enabled. You can take manual snapshot using the expect().toMatchImageSnapshot() matcher, or the page.toMatchImageSnapshot() for full page snapshot.

If you want to customize the snapshot behavior, you can set the preset to none and configure your own snapshot strategy in vitest.setup.ts:

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { vis } from 'vitest-plugin-vis/config'

export default defineConfig({
	plugins: [
		vis({ preset: 'none' })
	],
	test: {
		browser: {/* ... */},
		setupFiles: ['vitest.setup.ts']
	}
})

// vitest.setup.ts
import { vis } from 'vitest-plugin-vis/setup'

vis.setup({
	auto: true,
	auto: async ({ meta }) => meta['darkOnly'],
	auto: {
		async light() { document.body.classList.remove('dark') },
		async dark() { document.body.classList.add('dark') },
	}
})

As seen in the example above, you can configure the auto snapshot strategy to:

  • Enable/disable auto snapshot for all tests with auto: true/false,
  • Perform some actions before the snapshot is taken,
  • Skip certain snapshots for specific tests by returning false in the function,
  • Take snapshots for different themes or scenarios by providing an object.

Customizing snapshot path

Let's say you have this test:

// src/components/MyComponent.spec.tsx

describe('MyComponent', () => {
	describe('className', () => {
		it('can customize className', () => {
			// ...
		})
	})
})

By default, when you run the test locally, the image snapshot will be saved in the following path:

__vis__/local/__baselines__/components/MyComponent.spec.tsx/MyComponent/className/can-customize-className-auto.png

This path can be broken down into a few parts:

__vis__/local: snapshotRootDir

This is the snapshotRootDir where the image snapshots folders are placed. When running on CI, the snapshotRootDir is default to __vis__/<process.platform>.

__baselines__: baseline folder

This is the folder where the baseline images are saved and used for comparison. There is also a __results__ folder where the current test run images are saved, and a __diffs__ folder where the diff images are saved if the comparison fails.

components/MyComponent.spec.tsx: snapshotSubpath

This is part of the path based on the path of the test file relative to the project root. By default, the plugin will trim the common folder such as src or test from the path to reduce the path length.

If you place your test files in multiple folders, such as in both tests and src folders, and they might have files with the same name and create conflicting snapshots, you can use snapshotSubpath to customize the snapshot sub-path to avoid conflicts.

// vitest.config.ts
import { storybookVis } from 'storybook-addon-vis/vitest-plugin'
import { defineConfig } from 'vitest/config'

export default defineConfig({
	plugins: [
		storybookVis({
			// keep the folder structure
			snapshotSubpath: (subpath) => subpath
		})
	],
	// ...
})

With the above configuration, the snapshot folder structure will look like this:

v __vis__
	> # ...
	v local # snapshot generated on local machine
		> __baselines__
			v examples
				v button.stories.tsx
					snapshot-1.png
					snapshot-2.png
			v src
				v button.stories.tsx
					snapshot-1.png
					snapshot-2.png
			v tests
				v button.stories.tsx
					snapshot-1.png
					snapshot-2.png
v examples
	button.stories.tsx
v src
	button.stories.tsx
v tests
	button.stories.tsx

MyComponent/className/can-customize-className: snapshotId

This is the ID of the snapshot based on the test name and scope. This is not customizable.

auto: snapshotKey

This is the key of the snapshot. In this case, it is auto because the snapshot is taken automatically at the end of the test. If you take a manual snapshot, the key will be 0, 1, etc. If you customize it when taking the snapshot, the global customization will not be used.

You can customize the snapshotRootDir, snapshotSubpath, and snapshotKey with corresponding options:

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { vis, trimCommonFolder } from 'vitest-plugin-vis/config'

export default defineConfig({
	plugins: [
		vis({
			snapshotRootDir: ({
				ci, // true if running on CI
				platform, // process.platform
				providerName, // 'playwright' or 'webdriverio'
				browserName, // 'chromium', 'firefox', etc.
				screenshotFailures, // from `browser` config
				screenshotDirectory, // from `browser` config
			}) => `__vis__/${ci ? platform : 'local'}`,
			snapshotSubpath: ({ subpath }) => trimCommonFolder(subpath),
			// Alphanumeric characters, and underscore are allowed. Dash is not allowed.
			snapshotKey: 'auto',
		})
	]
})

Customizing auto snapshot subject

By default, auto snapshots are taken from the document.body element.

You can customize this globally by specifying your selector in the subject option:

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { vis } from 'vitest-plugin-vis/config'

export default defineConfig({
	plugins: [
		vis({
			subject: '[data-testid="subject"]'
		})
	]
})

You can also customize the subject per test using the setAutoSnapshotOptions function:

// some.test.ts
import { page } from 'vitest/browser'
import { expect, it } from 'vitest'
import { render } from 'vitest-browser-react'
import { setAutoSnapshotOptions } from 'vitest-plugin-vis'

it('set your own subject', async () => {
	setAutoSnapshotOptions({ subject: '[data-testid="subject"]' })
	render(<div data-testid="subject">hello world</div>)
})

Customizing snapshot comparison options

You can customize the snapshot comparison options globally in the config:

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { vis } from 'vitest-plugin-vis/config'

export default defineConfig({
	plugins: [
		vis({
			// set a default subject selector (e.g. `[data-testid="subject"]`) to capture image snapshot
			subject: undefined,
			comparisonMethod: 'pixel', // or 'ssim'
			// pixelmatch or ssim.js options, depending on `comparisonMethod`.
			diffOptions: undefined,
			timeout: 30000,
			failureThresholdType: 'pixel',
			failureThreshold: 0,
		})
	]
})

TypeScript Configuration

The main usage of this add-on is to use the toMatchImageSnapshot matcher.

Since it is exposed under the expect object of vitest, you typically do not need to import vitest-plugin-vis directly.

Because of this, TypeScript may not recognize the matcher. To address this, you can add the following to your tsconfig.json:

{
	"compilerOptions": {
		"types": ["vitest-plugin-vis"]
	}
}

Or use the triple-slash reference.

To do that, create a typing file, e.g. types/vitest-plugin-vis.d.ts:

/// <reference types="vitest-plugin-vis" />

Make sure to include this file in your tsconfig.json:

{
	"files": ["types/vitest-plugin-vis.d.ts"],
	// or
	"include": ["src", "types"]
}

Usage

Automatic snapshots

By default, the plugin will use the auto preset, which will take a snapshot at the end of each rendering test.

You can control how the auto snapshot is taken using the setAutoSnapshotOptions function:

import { setAutoSnapshotOptions } from 'vitest-plugin-vis'
import { beforeEach, it } from 'vitest'

beforeAll(() => {
	// Apply options to all tests in the current suite (file)
	setAutoSnapshotOptions(/* options */)
})

beforeEach(() => {
	// Apply options to all tests in the current scope
	setAutoSnapshotOptions(/* options */)
})


it('disable snapshot per test', async () => {
	// Apply options to this test only
	setAutoSnapshotOptions(/* options */)
})

describe('nested scope', () => {
	beforeEach(() => {
		// Apply options to all tests in the current scope
		setAutoSnapshotOptions(/* options */)
	})
})

It supports options of expect(...).toMatchImageSnapshot(options):

setAutoSnapshotOptions({
	enable: true,
	comparisonMethod: 'pixel',
	snapshotKey: 'auto',
	diffOptions: { threshold: 0.01 },
	failureThreshold: 0.01,
	failureThresholdType: 'percent',
	timeout: 60000
})

You can also enable/disable auto snapshot by passing boolean:

// enable/disable auto snapshot
setAutoSnapshotOptions(true /* or false */)

You can also provide additional options, which you can use during theme to enable/disable snapshot for each theme:

setAutoSnapshotOptions({
	skipDark: true
})

// in vitest.setup.ts
vis.setup({
	auto: {
		async dark(options) {
			if (options.skipDark) return false
			document.body.classList.add('dark')
		},
	}
})

Manual Snapshots

You can take snapshots manually:

// some.test.ts
import { render } from 'vitest-browser-react'
import { page } from 'vitest/browser'
import { it } from 'vitest'

it('manual snapshot', async ({ expect }) => {
	render(<div data-testid="subject">hello world</div>)
	await expect(document.body).toMatchImageSnapshot(/* options */)
	// or
	const subject = page.getByTestId('subject')
	await expect(subject).toMatchImageSnapshot(/* options */)
})

You can customize the snapshot comparison options per assertion:

// some.test.ts
import { render } from 'vitest-browser-react'
import { page } from 'vitest/browser'
import { it } from 'vitest'

it('manual snapshot with options', async ({ expect }) => {
	render(<div data-testid="subject">hello world</div>)
	const subject = page.getByTestId('subject')
	await expect(subject).toMatchImageSnapshot({
		snapshotKey: 'custom',
		failureThreshold: 0.01,
		failureThresholdType: 'percent',
		diffOptions: {
			threshold: 0.1
		},
		timeout: 60000
	})
})

Full Page Snapshot

You can also take a full page snapshot:

import { page } from 'vitest/browser'
import { it } from 'vitest'

it('full page snapshot', async () => {
	await page.toMatchImageSnapshot({ fullPage: true })
})

Has Snapshot

While less common, you can also check if a snapshot exists:

import { page } from 'vitest/browser'
import { it } from 'vitest'

it('Has Snapshot', async ({ expect }) => {
	const hasSnapshot = await page.hasImageSnapshot(/* options */)
	if (!hasSnapshot) {
		// do something
	}
	else {
		// do something else
	}
})

This is useful when you are performing negative test.

webdriverio Support

While Vitest Browser Mode supports both playwright and webdriverio, webdriverio currently does not work well with visual testing.

There are two issues we are aware of:

element click intercepted: WebDriverError: element click intercepted: Element is not clickable at point

This occurs in CI when --window-size is not set. To work around this issue, you can set the --window-size flag in your config:

// vitest.config.ts

export default {
	test: {
		browser: {
			instances: [
				{
					browser: 'chrome',
					capabilities: {
						'goog:chromeOptions': {
						args: ['--window-size=1280,720']
						}
					}
				}
			]
		}
	}
}

fullPage is not working

This occurs when the browser is in headless mode. But even when it is not in headless mode, the resulting snapshot is still not capturing the full page.

For the time being, we recommend using playwright for visual testing.

Git Ignore

The local snapshots, current run results, and diffs should be ignored by git. Add the following lines to your .gitignore file:

**/__vis__/**/__diffs__
**/__vis__/**/__results__
**/__vis__/local

Vitest Browser Mode

Vitest visual testing plugin runs on Vitest Browser Mode. Please follow its guide to set up your environment.

Bonus note, if you want to install Firefox on WSL, you can follow these steps: Install Firefox on Ubuntu 22.04. Also, you may need to sudo apt-get install xdg-utils to fix xdg-settings: not found.

Running on CI

When running on CI, the plugin will save the image snapshots in the <root>/__vis__/<process.platform> directory.

The image snapshots are taken on the server side using playwright or webdriverio depending on your browser provider. It is recommended to run your tests serially to avoid flakiness.

Migrating from v2

vitest-plugin-vis v3 is a number of breaking changes from v2.

If you are using vitest-plugin-vis v2, you can follow the migration guide here to use v3.

Preset changes

The enable and manual options are combined as manual. The only difference between enable and manual was that manual was not capable to take automatic snapshot even when you use setAutoSnapshotOptions in your test.

platform option is removed

The platform option is removed. It is replaced with snapshotRootDir which takes a function to determine the snapshot root directory.

customizeSnapshotSubpath is replaced with snapshotSubpath

The main difference is that customizeSnapshotSubpath receives the subpath as a string, while snapshotSubpath receives { subpath: string }.

This change allows us to expand it by adding more properties such as viewport in the future.

customizeSnapshotId is replaced with snapshotKey

In v3, we need a stable snapshot ID to be able to identify snapshots originated from the same test. We couldn't to that with customizeSnapshotId.

The snapshotKey has a reduced responsibility of only customizing the snapshot key, which is added to the end of the snapshot filename.

Let's say you have a test file src/components/x/x.test.ts. Within that file, you have a test:

// src/components/x/x.test.ts

describe('some scope', () => {
	it('should do something', () => {
		// ...
	})
})

By default, the snapshot will be saved in the following path:

// auto snapshot
__vis__/local/__baselines__/components/x/x.test.ts/some-scope/should-do-something-auto.png

// manual snapshot
__vis__/local/__baselines__/components/x/x.test.ts/some-scope/should-do-something-1.png

// auto snapshot map: `{ light() {...}, dark() {...} }`
__vis__/local/__baselines__/components/x/x.test.ts/some-scope/should-do-something-light.png
__vis__/local/__baselines__/components/x/x.test.ts/some-scope/should-do-something-dark.png

The snapshotKey defined in your config or is setAutoSnapshotOptions() will affect the auto snapshot

// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
	plugins: [
		vis({
			snapshotKey: 'custom'
		})
	]
})

// or
// src/components/x/x.test.ts

describe('some scope', () => {
	it('should do something', () => {
		setAutoSnapshotOptions({
			snapshotKey: 'custom'
		})
		// ...
	})
})
// auto snapshot
__vis__/local/__baselines__/components/x/x.test.ts/some-scope/should-do-something-auto.png
// becomes
__vis__/local/__baselines__/components/x/x.test.ts/some-scope/should-do-something-custom.png

If you define a snapshotKey in your manual snapshot, expectedly it will be used for that snapshot only.

// src/components/x/x.test.ts

describe('some scope', () => {
	it('should do something', () => {
		expect(document.body).toMatchImageSnapshot({
			snapshotKey: 'custom'
		})
		page.toMatchImageSnapshot({
			snapshotKey: 'custom'
		})
	})
})
// manual snapshot
__vis__/local/__baselines__/components/x/x.test.ts/some-scope/should-do-something-1.png
// becomes
__vis__/local/__baselines__/components/x/x.test.ts/some-scope/should-do-something-custom.png

subjectDataTestId is replaced with subject

If you are using subjectDataTestId in your config, you can replace it with subject in your config.

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { vis } from 'vitest-plugin-vis/config'

export default defineConfig({
	plugins: [
		vis({
			// subjectDataTestId: 'subject'
			subject: '[data-testid="subject"]'
		})
	]
})

FAQ

feature X in jest-image-snapshot is missing

Some features in jest-image-snapshot are not implemented in vitest-plugin-vis yet. This is because through our own usage, we do not found a good use case for them.

If you have a good use case for these features, please open an issue or PR.