Skip to main content
Version: Current

Vitest

The happo/vitest module takes Happo snapshots from component tests that run in Vitest's Browser Mode. Render a component the way your tests already do, with Testing Library or a vitest-browser-* renderer, interact with it, and pass the element to happoScreenshot. Happo then renders it in every browser in your happo.config.ts.

Pre-requisites​

You'll need Vitest's Browser Mode set up with the Playwright or WebdriverIO provider, and a Happo account. Tests that run in jsdom or happy-dom aren't supported.

Installation​

In your project, install the happo package.

npm install --save-dev happo

Setup​

Add the Happo plugin to your Vitest config:

vitest.config.ts
import { playwright } from '@vitest/browser-playwright';
import { happoVitestPlugin } from 'happo/vitest';
import { defineConfig } from 'vitest/config';

export default defineConfig({
plugins: [happoVitestPlugin()],
test: {
browser: {
enabled: true,
provider: playwright(),
// Where the tests run. Happo renders the snapshots in every browser in
// happo.config.ts, so one browser here is enough.
instances: [{ browser: 'chromium' }],
},
},
});

Then call happoScreenshot from happo/vitest/browser in a test. It takes an element, from Testing Library's screen for instance, or a locator from vitest/browser's page:

Button.test.tsx
import { render, screen } from '@testing-library/react';
import { happoScreenshot } from 'happo/vitest/browser';
import { test } from 'vitest';
import { userEvent } from 'vitest/browser';

import Button from './Button';

test('hovered', async () => {
render(<Button>Save</Button>);
const button = screen.getByRole('button');

await userEvent.hover(button);
await happoScreenshot(button, {
component: 'Button',
variant: 'hovered',
});
});

You will also need a happo.config.ts file with some minimal/required configuration:

happo.config.ts
import { defineConfig } from 'happo';

export default defineConfig({
apiKey: process.env.HAPPO_API_KEY,
apiSecret: process.env.HAPPO_API_SECRET,

integration: {
type: 'vitest',
},

targets: {
chrome: {
type: 'chrome',
viewport: '1024x768',
},
},
});

See Configuration for more configuration options.

NOTE: For security reasons, you'll most likely want to pass in apiKey and apiSecret via environment variables:

happo.config.ts
import { defineConfig } from 'happo';

export default defineConfig({
apiKey: process.env.HAPPO_API_KEY,
apiSecret: process.env.HAPPO_API_SECRET,

// ... more config
});

Usage​

To enable Happo, wrap your Vitest command with the happo script:

npx happo -- vitest run

Without the happo wrapper, happoScreenshot does nothing, so the same tests run as usual in watch mode and in your editor.

Test files can run in parallel, as Vitest runs them by default: each snapshot goes to the plugin in Vitest's own process, which keeps them by test. Happo sends a test's snapshots once the test has passed: a failing test's snapshots are dropped, and a retried test keeps only those of the attempt that passed. Everything goes to Happo when the run is over.

Hover, focus and active states​

Happo captures the state a test puts an element in (hovering it with userEvent.hover, tabbing to it) on targets with applyPseudoClasses: true:

happo.config.ts
export default defineConfig({
integration: {
type: 'vitest',
},
targets: {
chrome: {
type: 'chrome',
viewport: '1024x768',
applyPseudoClasses: true,
},
},
});

To add the data-happo-* attributes yourself instead, set autoApplyPseudoStateAttributes to false.

Happo crops a snapshot to the element you pass, so a focus ring drawn outside it (an outline, or a box-shadow) is cut off. To capture it, pass a wrapper with some padding around the focused element.

The pointer stays where a test left it, so a test that hovers something should move off it at the end (userEvent.unhover), or the next test's element may be captured hovered too.

Allowing failures​

By default, no Happo report is produced when the Vitest run fails. To send the passing tests' snapshots anyway, set allowFailures:

happo.config.ts
export default defineConfig({
integration: {
type: 'vitest',
allowFailures: true,
},
// ... rest of the config
});

Selecting targets​

To render a snapshot in some of your targets only, name them with the targets option. An object with name, type and viewport adds a target that isn't in happo.config.ts:

await happoScreenshot(button, {
component: 'Button',
variant: 'default',
targets: [
'chrome',
{ name: 'firefox-small', type: 'firefox', viewport: '400x800' },
],
});

Partial runs​

To exclude specific components or variants from a run, pass the --skip CLI option to the happo wrapper:

npx happo --skip '[{"component":"Card"},{"component":"Button"}]' -- vitest run

Continuous Integration​

Happo detects the CI environment the same way it does for the Playwright integration, and the --nonce and npx happo finalize steps for parallel builds, such as Vitest's --shard, work the same way.

Email notifications​

If you set the --notify CLI argument as part of your Vitest run, an email will be sent out when the Happo comparison report is ready.

Usage instructions for --notify are available in the Continuous Integration docs.

Advanced usage​

Download all assets​

By default, Happo includes the assets your tests load from Vite's dev server in an assets package, and leaves external URLs as they are, for Happo's workers to fetch. To include external assets too, set integration.downloadAllAssets to true.

Clip snapshot strategy​

happoScreenshot takes snapshotStrategy: 'clip', as in the Playwright integration, to render an element as it was laid out on the page rather than on its own.

Skipping snapshots​

Changed in happo v7: the option is --skip; v6 called it --skippedExamples. See Upgrading to happo v7.

If you run a subset of the tests in your test suite, Happo will show "Deleted examples" in the report. To prevent this from happening, you can pass a --skip option to the happo finalize call.

The --skip option needs to be an array coded as a JSON string. Each item in the array needs a component, variant, and target, all strings. Here's an example call:

npx happo finalize --skip '[{"component":"Button","variant":"default","target":"chrome-small"}]'

Remember to skip examples in all targets you have defined in happo.config.ts. If you for instance have targets named "chrome-small" & "firefox-large", you should add two items per snapshot you are skipping. E.g.

npx happo finalize --skip '[{"component":"Button","variant":"default","target":"chrome-small"},{"component":"Button","variant":"default","target":"firefox-large"}]'

Finding the skipped snapshots can be a little tricky, but a little bit of code introspection could help. Here's an example of a script that can serve as a base: https://github.com/happo/happo-e2e/issues/21#issuecomment-1825776491

Troubleshooting​

I need support!​

We're here to help — send an email to support@happo.io and we'll assist you.

Happo isn't producing any screenshots​

Check that Vitest runs through happo -- and that the Happo plugin is in the Vitest config of the project your tests run in. Look for lines starting with [HAPPO] in Vitest's output.

Where are my screenshots?​

During test suite execution, Happo will only record information. All screenshots are taken asynchronously outside of the test run. This means that your test suite will finish sooner than the Happo job is done. To follow along the progress, look for a url logged by Happo:

[HAPPO] https://happo.io/a/284/async-reports/34

Styles are missing from my screenshots​

Styles and assets are collected automatically during your test suite. If you notice styles/images/fonts etc are missing, one of a few things might have happened:

  • CSS selectors depend on context that is missing to Happo. If you e.g. have something like #start-page .header { color: red } and screenshoot .header, the red color will be missing. This is because Happo only sees the .header element, never the surrounding page.
  • There could be a bug in how {props.module} collects styles and assets. Reach out to support@happo.io and we'll triage.