@vizzly-testing/vitest
v0.2.0
Published
Drop-in replacement for Vitest visual testing - powered by Vizzly
Maintainers
Readme
@vizzly-testing/vitest
Drop-in replacement for Vitest visual testing - powered by Vizzly
Overview
This package completely replaces Vitest's native visual testing with
Vizzly's powerful platform. Just add the plugin and continue using Vitest's
standard toMatchScreenshot API - no code changes required!
True drop-in replacement. Zero API changes. Maximum power.
Features
- ✅ Native Vitest API - Use
toMatchScreenshot- no custom matchers! - 🎨 Per-Screenshot Properties - Add metadata for multi-variant testing
- 🏃 TDD Mode - Interactive local dashboard with live comparisons
- ☁️ Cloud Mode - Team collaboration with visual reviews
- 🚀 CI/CD Ready - Parallel execution and baseline management
- 🔬 Better Diffing - Powered by honeydiff (Rust-based, faster than pixelmatch)
Installation
pnpm install -D @vizzly-testing/vitest @vizzly-testing/cli vitest @vitest/browser @vitest/browser-playwrightQuick Start
1. Configure Vitest
Add the Vizzly plugin to vitest.config.js:
import { defineConfig } from 'vitest/config'
import { vizzlyPlugin } from '@vizzly-testing/vitest'
import { playwright } from '@vitest/browser-playwright'
export default defineConfig({
plugins: [vizzlyPlugin()],
test: {
browser: {
enabled: true,
instances: [
{
browser: 'chromium',
provider: playwright()
}
]
}
}
})2. Write Tests
Use Vitest's native toMatchScreenshot matcher - no changes needed:
import { expect, test } from 'vitest'
import { page } from 'vitest/browser'
test('homepage looks correct', async () => {
// Render your component/page
document.body.innerHTML = '<h1 class="hero">Hello World</h1>'
// Basic screenshot
await expect(page.getByRole('heading')).toMatchScreenshot('homepage.png')
// With properties for multi-variant testing
await expect(page.getByRole('heading')).toMatchScreenshot('hero-section.png', {
properties: {
theme: 'dark'
},
threshold: 5
})
})3. Run Tests
TDD Mode (local development with live dashboard):
# Terminal 1: Start Vizzly TDD server
pnpm exec vizzly tdd start
# Terminal 2: Run tests
pnpm exec vitest
# Open dashboard at http://localhost:47392Note: New screenshots automatically create baselines on first run and pass! You can review them
in the dashboard at http://localhost:47392/dashboard. Future runs will compare against the
baseline.
Cloud Mode (CI/CD with team collaboration):
pnpm exec vizzly run "pnpm exec vitest" --waitAPI Reference
Plugin
The plugin does not take configuration today. Add it once to your Vitest
plugins list and pass screenshot-specific options to toMatchScreenshot.
import { vizzlyPlugin } from '@vizzly-testing/vitest'
// Simple - just add the plugin
vizzlyPlugin()Screenshot Options
All options are passed directly to toMatchScreenshot:
await expect(page).toMatchScreenshot('screenshot.png', {
// Custom properties for multi-variant testing
properties: {
theme: 'dark',
language: 'en',
userRole: 'admin'
},
// Vizzly diff sensitivity threshold
threshold: 5,
// Ignore tiny connected pixel clusters
minClusterSize: 10,
// Full page capture
fullPage: true
})Available Options:
- Playwright/Vitest screenshot options such as
animations,caret,mask,maskColor,omitBackground,scale, andtimeoutare passed through to the browser screenshot capture. - Vizzly automatically adds
browser,url,viewport,viewport_width, andviewport_heightmetadata based on the current browser session. properties(object) - Custom metadata for signature-based baseline matching. Reserved runtime fields stay pinned to the current browser session; explicit viewport fields are still allowed when a test intentionally needs a custom signature.threshold(number) - Vizzly diff sensitivity threshold. When omitted, the Vizzly server configuration is used.minClusterSize(number) - Ignore connected diff clusters smaller than this sizefullPage(boolean) - Capture full scrollable page instead of viewport. This applies to page targets; locator targets stay element-sized.failOnDiff(boolean) - Fail this assertion when Vizzly reports a visual diff. When omitted, the Vizzly server or environment setting is used.
Multi-Variant Testing
Use properties to test the same component in different states:
test('button variants', async () => {
// Light theme
document.body.classList.add('theme-light')
await expect(page.getByRole('button')).toMatchScreenshot('button.png', {
properties: { theme: 'light' }
})
// Dark theme
document.body.classList.remove('theme-light')
document.body.classList.add('theme-dark')
await expect(page.getByRole('button')).toMatchScreenshot('button.png', {
properties: { theme: 'dark' }
})
})Vizzly will manage separate baselines for each variant using signature-based matching: button.png|theme:dark|...
How It Works
This plugin completely replaces Vitest's native screenshot testing by:
- Extending
expectAPI - Registers a customtoMatchScreenshotmatcher that overrides Vitest's - Disabling native system - Sets
screenshotFailures: falseto prevent conflicts - Direct HTTP communication - Screenshots POST directly to Vizzly server from browser context
TDD Mode
- Plugin injects setup file that extends
expectwith custom matcher - Your test calls
toMatchScreenshot→ captures screenshot in browser - Matcher POSTs screenshot to local Vizzly TDD server
- Server compares using honeydiff → returns pass/fail result
- Dashboard shows live results at
http://localhost:47392/dashboard - Accept/reject changes in UI → baselines updated in
.vizzly/baselines/
Cloud Mode
- Same custom matcher captures screenshot in browser
- POSTs to Vizzly server which queues for upload
- After tests complete → uploads to Vizzly cloud
- Team reviews changes in web dashboard
- Tests always pass - comparison happens asynchronously in cloud (use
--waitflag to fail on visual changes)
TypeScript
Full TypeScript support included! The plugin automatically extends Vitest's types:
import { expect, test } from 'vitest'
import { page } from 'vitest/browser'
test('typed screenshot', async () => {
await expect(page).toMatchScreenshot('hero.png', {
properties: {
// Full autocomplete support
theme: 'dark'
},
threshold: 5,
fullPage: true
})
})Vizzly Direct Integration
You can also use Vizzly without the comparator by calling vizzlyScreenshot() directly:
import { vizzlyScreenshot } from '@vizzly-testing/cli/client'
test('manual screenshot', async () => {
let screenshot = await page.screenshot()
await vizzlyScreenshot('homepage', screenshot, {
properties: { theme: 'dark' }
})
})Comparator approach (this package):
- ✅ Native Vitest API (
toMatchScreenshot) - ✅ Integrated with Vitest's snapshot management
Direct approach:
- ✅ Full control over screenshot capture
- ✅ Works with any test runner
- ✅ More explicit
Examples & Tests
See the package tests in the Vizzly CLI repo under clients/vitest/tests/:
- vitest-plugin.spec.js - Unit tests for plugin configuration and comparator function
- e2e/ - End-to-end test project running actual Vitest tests with the plugin
The E2E tests serve as both validation and a working example. Run them with:
# From clients/vitest directory
pnpm install
pnpm run test:unit # Run unit tests
pnpm run test:e2e # Run E2E tests (requires vizzly tdd start)
pnpm test # Run unit testsTroubleshooting
"Vizzly not available" message
Make sure you're running tests with either:
vizzly tdd start(TDD mode)vizzly run "pnpm exec vitest"(cloud mode)
Screenshots not appearing in dashboard
- Check
pnpm exec vizzly tdd statusfor TDD, make sureVIZZLY_TOKENis set for cloud capture - Verify API token is set:
pnpm exec vizzly whoami - Check console for error messages
License
MIT © Stubborn Mule Software
