Skip to content

Vitest Plugin

The @pxdiff/vitest package integrates pxdiff into Vitest Browser Mode tests. Take screenshots during component tests and get instant diff results against baselines.

Terminal window
npm install --save-dev @pxdiff/vitest

Peer dependency: vitest >= 4.0.0

Vitest Browser Mode requires a browser provider. We recommend @vitest/browser-playwright:

Terminal window
npm install --save-dev @vitest/browser-playwright

Add the pxdiff plugin to your vitest.config.ts:

vitest.config.ts
import { pxdiffPlugin } from "@pxdiff/vitest/plugin";
import { playwright } from "@vitest/browser-playwright";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [pxdiffPlugin()],
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: "chromium" }],
},
},
});

The plugin automatically registers the toMatchPxdiff matcher and the browser commands needed for screenshot upload — no manual setup files required.

For TypeScript support, add the type augmentation to your tsconfig.json:

{
"compilerOptions": {
"types": ["@pxdiff/vitest/types"]
}
}
Terminal window
export PXDIFF_API_KEY=pxd_your_key_here

That’s it. No global setup file, no fixture boilerplate.

Use the toMatchPxdiff matcher on page (from vitest/browser) or any Locator:

import { expect, it } from "vitest";
import { page } from "vitest/browser";
import { render } from "vitest-browser-react";
it("button visual regression", async () => {
await render(<Button variant="primary">Click me</Button>);
// Full-page screenshot
await expect(page).toMatchPxdiff("button-primary");
});
it("card renders correctly", async () => {
const screen = await render(<Card title="Hello" />);
// Screenshot the full page (includes component in context)
await expect(page).toMatchPxdiff("card-default", {
path: ["components", "Card"],
});
});
await expect(page).toMatchPxdiff("dashboard", {
ignoreSelectors: [".timestamp", ".random-avatar"],
});

Elements with data-pxdiff="ignore" or data-chromatic="ignore" are always hidden automatically:

<span data-pxdiff="ignore">Dynamic content here</span>
await expect(page).toMatchPxdiff("chart", {
maxDiffPixelRatio: 0.01, // Allow up to 1% pixel difference
});

Use path to organize screenshots into a tree in the review UI:

await expect(page).toMatchPxdiff("button-hover", {
path: ["components", "Button"],
});

Vite plugin that configures pxdiff for Vitest Browser Mode.

OptionTypeDefaultDescription
suitestring"vitest"Suite name for grouping snapshots
blockingbooleantrueWhether changed snapshots fail the test
apiUrlstringPXDIFF_API_URL or "https://pxdiff.com"API URL
git.branchstring(auto-detected)Git branch override
git.commitstring(auto-detected)Git commit override
localboolean(auto-detected)Local mode (from PXDIFF_LOCAL or absence of CI)
autoApproveboolean(auto-detected)From PXDIFF_AUTO_APPROVE

Custom matcher for visual regression assertions. Works on any object with a .screenshot() method — page and Locator instances from vitest/browser.

await expect(page).toMatchPxdiff("snapshot-name", options);
OptionTypeDefaultDescription
blockingboolean(from plugin)Override blocking mode for this snapshot
ignoreSelectorsstring[]CSS selectors to hide via visibility: hidden
maxDiffPixelRationumberMax acceptable diff ratio (0–1). Pass if below this
pathstring[]Display hierarchy path for tree grouping in review UI

The matcher determines pass/fail as follows:

  • Pass: unchanged, new, auto-approved, within maxDiffPixelRatio, non-blocking mode, or CI mode (soft-fail)
  • Fail: changed snapshots in blocking mode

In CI mode (when PXDIFF_SESSION_ID is set and not local), the matcher always passes. The pxdiff GitHub check run reports the overall result instead.


The recommended approach for CI is to wrap your Vitest tests with pxdiff run:

.github/workflows/visual.yml
- name: Visual Regression
run: pxdiff run -- npx vitest run
env:
PXDIFF_API_KEY: ${{ secrets.PXDIFF_API_KEY }}

This creates a GitHub check run, groups all screenshots into one session, and reports results back to the PR.

You can also run Vitest tests without pxdiff run. Screenshots are still uploaded and diffed individually:

- name: Visual Tests
run: npx vitest run
env:
PXDIFF_API_KEY: ${{ secrets.PXDIFF_API_KEY }}

vitest.config.ts
import { pxdiffPlugin } from "@pxdiff/vitest/plugin";
import react from "@vitejs/plugin-react";
import { playwright } from "@vitest/browser-playwright";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [react(), pxdiffPlugin()],
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: "chromium" }],
},
},
});
import { expect, it } from "vitest";
import { page } from "vitest/browser";
import { render } from "vitest-browser-react";
it("renders correctly", async () => {
await render(<MyComponent />);
await expect(page).toMatchPxdiff("my-component");
});

If your components use Tailwind, add the Tailwind Vite plugin:

vitest.config.ts
import { pxdiffPlugin } from "@pxdiff/vitest/plugin";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import { playwright } from "@vitest/browser-playwright";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [tailwindcss(), react(), pxdiffPlugin()],
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: "chromium" }],
},
},
});

VariableDescription
PXDIFF_API_KEY(required) API authentication key
PXDIFF_API_URLAPI base URL (default: https://pxdiff.com)
PXDIFF_SESSION_IDSession ID for grouping (set by pxdiff run or auto-generated)
PXDIFF_LOCALSet to true for local development mode
PXDIFF_AUTO_APPROVESet to true to auto-approve all changes
PXDIFF_BRANCHOverride git branch detection
PXDIFF_COMMITOverride git commit detection