ScreenshotNeo

BlogHow-to

How to Add Happo Visual Tests to a Vue Project

Add Happo visual regression tests to Vue through Storybook, Cypress, or Playwright. Set up stories, configure captures, run them in CI, and fix common issues.

By the ScreenshotNeo team4 October 202610 min read

Direct answer: Happo does not require a Vue-specific plugin. Add visual checks through one of Happo’s documented integrations: use Storybook for isolated Vue component states, or connect Happo to existing Cypress or Playwright tests for application flows. The Storybook route is a practical starting point when you want repeatable component examples; reuse a browser test runner when you already capture meaningful app states there. Happo documents all three approaches, plus a generic API.

This guide walks through a Vue 3 and Vite Storybook setup, then explains when to use Cypress or Playwright, how to choose states and targets, and how to reduce flaky comparisons. Check the linked Happo Storybook documentation for current runner-specific details.

1. Choose what Happo should capture

Route Best fit What you maintain
Storybook Reusable components and deliberate states such as loading, error, expanded, or dark mode Stories that render those states consistently
Cypress Pages and user journeys already covered by Cypress Capture points in existing browser tests
Playwright Pages and journeys already covered by Playwright Capture points in existing browser tests
Generic API A custom test harness or integration Authenticated API requests and image/report workflow

For components with many meaningful states, Storybook makes each state explicit and independently reviewable. For a flow that depends on routing, authentication, or several components working together, use the test runner that already drives that flow. Vue itself does not need a Happo adapter; the integration runs against the rendered Storybook or browser page.

2. Prepare Storybook in the Vue project

The example below assumes Vue 3 with Vite. In the project root, initialize Storybook if the project does not already have it:

npm create storybook@latest

Follow the prompts for the Vue 3 and Vite framework. Storybook’s documented framework requires Vue 3 and Vite 5 or later. Start it with the script generated by the setup, commonly:

npm run storybook

Make sure the component you intend to capture renders in Storybook before connecting Happo. Add the same global CSS and application setup the component needs. Storybook creates its own Vue app for previews, so global plugins, components, directives, and mixins must be registered there when required.

For example, if the app uses Pinia, register it in .storybook/preview.ts and load the shared stylesheet:

import { setup } from '@storybook/vue3-vite';
import { createPinia } from 'pinia';
import '../src/style.css';

setup((app) => {
  app.use(createPinia());
});

Replace the stylesheet path and plugins with the ones used by your application. Avoid sharing mutable store state across stories; create or reset the state so each story has a known starting point. See the Storybook Vue 3 and Vite framework guide for setup details.

3. Add representative Vue stories

Create stories for the states where an accidental visual change would matter. A compact Vue component and CSF story can look like this:

<!-- src/components/StatusCard.vue -->
<script setup lang="ts">
defineProps<{
  title: string;
  status: 'ready' | 'loading' | 'error';
}>();
</script>

<template>
  <article class="status-card">
    <h2>{{ title }}</h2>
    <p v-if="status === 'ready'">Everything is up to date.</p>
    <p v-else-if="status === 'loading'">Loading your data…</p>
    <p v-else role="alert">Could not load your data.</p>
  </article>
</template>
// src/components/StatusCard.stories.ts
import type { Meta, StoryObj } from '@storybook/vue3-vite';
import StatusCard from './StatusCard.vue';

const meta = {
  title: 'Components/StatusCard',
  component: StatusCard,
  args: { title: 'Account status' },
} satisfies Meta<typeof StatusCard>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Ready: Story = {
  args: { status: 'ready' },
};

export const Loading: Story = {
  args: { status: 'loading' },
};

export const Error: Story = {
  args: { status: 'error' },
};

Build stories around intentional, stable inputs. Prefer fixed text, data, and dates over live API responses or the current clock. Include important layout variants, such as narrow and wide viewports, only when they represent real behavior you need to protect.

4. Install and configure Happo

Install the current happo package as a development dependency:

npm install --save-dev happo
# or
pnpm add --save-dev happo
# or
yarn add --dev happo

Create happo.config.ts in the project root. Happo’s Storybook integration uses the Storybook config directory; the default shown here is .storybook. The configuration below also defines credentials and browser targets following Happo’s published package example:

import { defineConfig } from 'happo';

export default defineConfig({
  apiKey: process.env.HAPPO_API_KEY!,
  apiSecret: process.env.HAPPO_API_SECRET!,
  integration: {
    type: 'storybook',
    configDir: '.storybook',
  },
  targets: {
    'chrome-desktop': {
      type: 'chrome',
      viewport: '1280x720',
    },
    'firefox-desktop': {
      type: 'firefox',
      viewport: '1280x720',
    },
  },
});

Set HAPPO_API_KEY and HAPPO_API_SECRET in your local shell or CI secret store. Do not commit credentials to the repository. Happo’s CLI adds its client runtime to the Storybook package it builds, so current setups do not need a manual registration import. The optional happo/storybook/register import is for helper APIs such as theme switching; older guidance may show it as required, so follow the current docs for your installed version.

Add a package script so local and CI runs use the same command:

{
  "scripts": {
    "happo": "happo"
  }
}

Then run the suite:

npm run happo

Use Happo’s configuration documentation to confirm options and available browser targets for your account. Supported browsers and devices include Chrome, Firefox, Safari, Microsoft Edge, and iOS Safari, but plan access can vary. Do not assume every target is included on every plan.

5. Select useful states, themes, and waits

Cover states that represent real risk

Start with a small suite of high-value states: default, loading, error, empty, expanded, and responsive variants where they apply. Too many near-identical snapshots raise review and runtime costs without adding much coverage. Include a state if it protects a meaningful layout, content, or interaction outcome.

Capture light and dark themes

Happo’s Storybook integration supports theme variants through story parameters and a theme-switching function. Set the theme parameter only if the preview can switch to that theme deterministically. See the theme section in Happo’s Storybook docs for the current helper setup; it uses Storybook’s addons channel to request and observe the theme change.

Wait for asynchronous content deliberately

Prefer stories whose data is available immediately. When a story genuinely waits on content, Happo offers waitForContent for a string to appear, waitFor for a condition to become truthy, and a per-story delay. A wait that times out fails the snapshot by default. Use a delay only as a last resort: it slows runs and can conceal the real readiness problem. Increase the render timeout only when a known operation, such as a deliberately slow interaction, needs more time.

export const PaymentForm: Story = {
  parameters: {
    happo: {
      waitFor: () => document.querySelector('[data-payment-form-ready]'),
    },
  },
};

Use a stable selector or visible content tied to actual readiness. A fixed delay does not guarantee that a network request, font, or third-party iframe has finished.

Exclude stories that should not be snapshotted

Some examples are inherently random, depend on unavailable third-party services, or are unsuitable for static comparison. Disable those individually with the documented happo: false story parameter and keep important deterministic alternatives in the suite.

6. Use Cypress or Playwright for application states

If the target is a complete Vue page or user journey, connect Happo to the browser runner already used by the project. Happo documents separate integrations for Cypress and Playwright. These integrations capture states from browser tests and let Happo compare them across configured browser targets and viewport sizes.

The exact package setup and capture API are runner-specific and can change. Use the current integration page for the installed versions rather than copying an old plugin snippet. Keep functional assertions in the test and add visual captures at stable checkpoints after the page reaches the intended state. For example, a flow should establish its test data, navigate to the relevant route, wait for the UI condition, and then capture. Do not capture immediately after navigation if the page still has loading placeholders.

7. Run the suite in CI and review changes

  1. Store Happo credentials as CI secrets and expose them to the visual-test job.
  2. Install dependencies with the repository’s lockfile-based command.
  3. Run the same Happo command used locally on pull requests and on the base branch so comparisons have a recent baseline.
  4. Publish or inspect the Happo report and review visual changes before accepting them.
  5. When a change is intentional, approve the new appearance through your team’s normal review process so subsequent comparisons use the intended baseline.

For Storybook, Happo supports partial runs with --only and --skip to capture selected components or story files. For example:

npm run happo --only '[{"component":"StatusCard"}]'

Partial runs can reduce work on a pull request when a suitable baseline report exists. Run full captures on the default branch to keep that baseline current, and verify the filter in CI logs when investigating a missing or stale comparison. See the Happo Storybook guide for supported filter entry forms and behavior.

8. Keep comparisons reliable and affordable

  • Control inputs: use fixed fixture data, dates, locale, and feature flags. Mock services that otherwise return changing content.
  • Make readiness observable: wait for a meaningful rendered condition rather than guessing with a long delay.
  • Reduce animation noise: Happo describes animation silencing and asynchronous asset and font waits. These reduce avoidable variation but do not make every page deterministic.
  • Use tolerance with care: color-delta tolerance can ignore tiny rendering differences such as anti-aliasing noise. A high tolerance can also hide a real subtle regression.
  • Limit unnecessary targets: each additional browser, viewport, theme, or story expands the capture set. Choose the combinations your users and support requirements justify, and confirm plan access.
  • Use partial runs intentionally: run only affected stories in pull requests if your workflow maintains a valid baseline; keep full baseline runs on the main branch.
  • Review diffs, not just pass/fail: an intended redesign can correctly produce many differences. Confirm the changed areas and update the baseline only after review.

Happo pricing and plan details determine included browsers and usage allowances. Check the Happo pricing page before expanding the target matrix; the available evidence does not establish a universal per-snapshot cost, so do not estimate spend from a fixed rate without checking your plan.

9. Troubleshoot common problems

Symptom Likely cause Fix
CLI cannot find a config happo.config.ts is outside the working directory or named differently. Run the command from the repository root and use a supported config filename. Check the Happo configuration docs for the current detection rules.
Authentication fails Missing, misspelled, or unavailable CI environment variables. Check that both secret values are set for this job and the config reads the same variable names. Never print their values into logs.
Storybook preview is missing app styling or components Global CSS, plugins, directives, or components are only registered by the production app bootstrap. Import shared styles and register the required Vue app setup through Storybook’s setup hook.
Story renders blank or throws in isolation It relies on a router, store, provider, global component, or API state absent from the preview. Register the application dependency in the preview or provide a story decorator/fixture that sets up the required context.
Capture times out waiting for content The readiness selector or text never appears, or an external dependency is unavailable. Verify the condition in Storybook, mock or stabilize the external dependency, and only then adjust the timeout if the operation truly needs longer.
Screenshots differ on every run Dynamic dates, random content, animation, unstable API data, fonts, or browser-specific rendering. Fix inputs and readiness first. Silence animations or apply a carefully chosen tolerance where appropriate; inspect the diff to avoid masking defects.
Story state leaks between captures Mutable module or store state survives as Happo navigates between stories. Reset shared state per story or enable navigatePerStory in the Storybook integration. The latter gives stories a fresh page load and is slower.
Some browser targets are unavailable The selected plan may not include every browser or device. Check plan entitlements and configure only targets available to the project.
TypeScript component metadata is incomplete Storybook docgen may not resolve project references or aliases. Check the Vue Storybook framework documentation for the appropriate vue-component-meta and tsconfig setup.

Or skip the browser setup

If you need a screenshot of a page rather than a visual regression test suite, ScreenshotNeo provides a one-request website screenshot API and an MCP server. It is not a replacement for Happo’s baseline comparison workflow; it can return a clean screenshot or PDF of a URL without you maintaining screenshot-browser capture code.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

See the ScreenshotNeo API documentation for request options. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.

FAQ

Does Happo support Vue?

The documented route is through integrations such as Storybook, Cypress, and Playwright rather than a Vue-only Happo adapter. These capture the rendered Vue UI.

Can I test both components and full pages?

Yes. Use Storybook stories for isolated component states and browser tests for pages or multi-step journeys. They can cover different risks in the same repository.

Do I need to add a Happo import to Storybook preview?

For current Storybook integration, the CLI injects the client runtime into the built package. The registration import is optional for helper APIs; consult the current docs if using an older Happo version.

Can I use Happo for accessibility checks too?

Happo documents accessibility regression workflows alongside visual testing. Review its current integration and plan documentation for the exact setup and availability.