Migrate from Happo
Move a Happo suite to Snapvisor - how happo.config targets, the happo wrapper, happoScreenshot with component and variant, Storybook parameters and report approval translate to the Playwright, Cypress and Storybook SDKs.
This guide moves a Happo setup (the happo package with its Playwright, Cypress or Storybook integration) to Snapvisor. You keep your tests, stories and CI. What changes is the capture call, the wrapper around your test command, and where baselines and review live. It follows the structure of the other migration guides, so the Storybook specifics link to Migrate from Loki and Migrate from Chromatic.
Happo facts come from the public Happo docs at docs.happo.io (the Playwright, Cypress, Storybook and getting-started pages). Snapvisor facts were read from the published @snapvisor/playwright 8.0.0, @snapvisor/cypress 8.0.0 and @snapvisor/storybook 7.0.0 packages. Happo's docs have changed between releases, so check the version you run.
The one change that matters
With Happo, your test run sends what Happo needs to render (for Storybook, a built Storybook package) to Happo, and Happo's workers take the screenshots. A report is compared with the baseline report from your main branch, and you accept or reject the diffs in Happo.
Snapvisor takes a real screenshot in the browser your own test run is already using. The capture call stabilizes the page and takes the picture, your pipeline uploads the images, and Snapvisor compares them with the approved screenshots for the branch. There is no separate rendering step, so what you see is what your browser drew. You approve changes in the review UI, and they become the new baseline for that branch. See Baseline builds.
Your Happo reports do not carry over, and there is no import path. Your first Snapvisor build on your base branch becomes the baseline. Plan the switch for a moment when your base branch is visually correct.
Concept mapping
| Happo | Snapvisor equivalent |
|---|---|
happo.config.ts (or .js) with apiKey, apiSecret, targets and an integration | No config file for capture. A project token in ARGOS_TOKEN, and the SDK registered in your test config. See Access tokens. |
npx happo -- playwright test, npx happo -- cypress run | Your normal test command. The SDK uploads when the run ends. |
happoScreenshot(locator, { component, variant }) (Playwright, Cypress) | argosScreenshot(page, "Component - variant", { element: locator }) for Playwright, cy.argosScreenshot("Component - variant") for Cypress. |
component and variant | One screenshot name, unique within a run. |
targets (a named browser and viewport) | The viewports option, plus Playwright projects for more browsers. See Targets. |
Storybook integration (type: 'storybook') | @snapvisor/storybook. See Storybook. |
snapshotStrategy: 'clip' or the default | Not needed. The screenshot is taken in the page. Pass element (Playwright) or chain from the element (Cypress) to capture one element. |
Parallel runs with --nonce and npx happo finalize | Parallel builds with a shared nonce, finalized as one build. See Parallel testing. |
| GitHub app that posts the result to the pull request | The Snapvisor status check. See GitHub or GitLab. |
| Accepting or rejecting diffs in a report | Approving in the review UI. See Review workflow. |
Playwright
Install the SDK and register the reporter, which uploads screenshots when the run finishes:
npm install --save-dev @snapvisor/playwright// playwright.config.ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@snapvisor/playwright/reporter";
export default defineConfig({
reporter: [
process.env.CI ? ["dot"] : ["list"],
[
"@snapvisor/playwright/reporter",
createArgosReporterOptions({ uploadToArgos: !!process.env.CI }),
],
],
});Then change the capture:
// tests/hero.spec.ts
-import { test } from "happo/playwright";
+import { test } from "@playwright/test";
+import { argosScreenshot } from "@snapvisor/playwright";
-test("start page", async ({ page, happoScreenshot }) => {
+test("start page", async ({ page }) => {
await page.goto("http://localhost:7676");
const heroImage = page.locator(".hero-image");
- await happoScreenshot(heroImage, { component: "Hero Image", variant: "default" });
+ await argosScreenshot(page, "Hero Image - default", { element: heroImage });
});argosScreenshot takes Playwright's own screenshot options, so mask and clip work as in Playwright. Defaults worth knowing, all read from the package source:
- Full page. A capture of a page is full page unless you pass
fullPage: false. A capture of an element is the element. - Animations are disabled unless you pass
animations: "allow". - Masks.
[data-visual-test="blackout"]is masked by default. Passing your ownmaskreplaces that default, so includepage.locator('[data-visual-test="blackout"]')in your array if you rely on it. - Stabilization is on by default: fonts, images and
aria-busyregions settle first, carets and scrollbars are hidden, and font anti-aliasing is normalized.
Drop the happo wrapper from your test command (npx happo -- playwright test becomes npx playwright test). See SDKs for the full option list.
Cypress
npm install --save-dev @snapvisor/cypress// cypress.config.ts
import { defineConfig } from "cypress";
import { registerArgosTask } from "@snapvisor/cypress/task";
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
registerArgosTask(on, config, {
// Upload only on CI. Without this option the default is to upload.
uploadToArgos: !!process.env.CI,
});
return config;
},
},
}); // cypress/support/e2e.ts
-import "happo/cypress";
+import "@snapvisor/cypress/support";-cy.get(".header").happoScreenshot({ component: "Header", variant: "large" });
+cy.get(".header").argosScreenshot("Header - large");Remove the Happo task from cypress.config and stop wrapping cypress run with the happo command. Defaults, read from the package source:
cy.argosScreenshotaccepts Cypress's owncy.screenshot()options, so Cypress's owncaptureanddisableTimersAndAnimationsdefaults apply.- Elements matching
[data-visual-test="blackout"]are blacked out by default. Passing your ownblackoutarray replaces that default, so include'[data-visual-test="blackout"]'in it to keep it. - Stabilization is on by default; pass
stabilize: falseto turn it off.
Storybook
Happo builds your Storybook and renders every story on its workers. With Snapvisor, your own Storybook test run renders the stories in a real browser and captures one screenshot per story. The setup, for both the Vitest addon and the Test Runner, is in the Storybook section of the Chromatic guide. Register the plugin like this:
// vitest.config.ts
import { defineConfig } from "vitest/config";
import { argosVitestPlugin } from "@snapvisor/storybook/vitest-plugin";
export default defineConfig({
plugins: [
argosVitestPlugin({
// Set to true to upload; nothing is uploaded when this is omitted.
uploadToArgos: process.env.CI === "true",
}),
],
});How Happo's story parameters translate:
Happo parameters.happo | Snapvisor |
|---|---|
themes: ['light', 'dark'] | Modes, one screenshot per mode. See Viewports and modes. |
targets on a story | Modes and viewports, as above. |
delay, waitFor, waitForContent | Wait in the story's play function. Snapvisor already waits for fonts and images to settle. |
beforeScreenshot, afterScreenshot | Do the DOM work in the story's play function. The plugin also has a beforeScreenshot option, but in @snapvisor/storybook 7.0.0 it receives only { runStabilization }, with no page or DOM access. |
happo: false | Snapvisor screenshots every story your test run visits. To leave a story out, keep it out of the test run. |
In Storybook captures the aria-busy wait is off by default. Passing your own stabilize object replaces that default, so include waitForAriaBusy: false in it to keep the wait off.
Targets
A Happo target is a named browser and viewport, such as chrome at 1024x768. Snapvisor captures in the browser your test run uses, so targets become:
- Viewports. Pass
viewportsto capture one screenshot per size from a single call:
await argosScreenshot(page, "Hero Image - default", {
viewports: ["iphone-6", { width: 1280, height: 800 }],
});- Browsers. Add Playwright projects (or run Cypress in more than one browser). Each project runs the whole suite, so more projects means more screenshots and a longer run.
Tolerances
Happo's comparison settings do not carry over. Snapvisor has one setting, threshold, a sensitivity between 0 and 1 that defaults to 0.5. Higher means less sensitive. Set it per screenshot, or for all stories on the Storybook plugin:
await argosScreenshot(page, "chart", { threshold: 0.7 });Start from the default and adjust only when a specific screenshot is noisy. A change is flagged for review, not failed, so a small difference is a decision for a reviewer and not a red build.
The CI change
Before, the job wrapped the test command with the Happo CLI and passed Happo credentials:
- run: npm ci
- run: npx happo -- playwright test
env:
HAPPO_API_KEY: ${{ secrets.HAPPO_API_KEY }}
HAPPO_API_SECRET: ${{ secrets.HAPPO_API_SECRET }}After, the job runs your tests with the project token:
- run: npm ci
- run: npx playwright test # captures via argosScreenshot, uploads via the reporter
env:
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}Copy the project token from your project's settings in the app, and store it as a CI secret, never in the repository. See Access tokens. On GitHub Actions you can drop the secret with tokenless authentication. More workflows, including merge queues and fork pull requests, are in GitHub Actions recipes.
Run both side by side
You do not have to switch in one pull request. Keep the happo wrapper and happoScreenshot calls and add the Snapvisor capture next to them:
- Add the Snapvisor SDK and calls, and merge them to your base branch so the first Snapvisor build becomes the baseline.
- Keep Happo as your gate, and the Snapvisor check optional, while you compare results.
- When the Snapvisor builds match your expectations on a few pull requests, make the Snapvisor check required, then remove Happo.
Make the Snapvisor check the gate
The upload finishes once the build has been created, whether or not it contains changes. A failed upload (bad token, unresolvable commit, network) is an error. The review gate is the commit status check Snapvisor posts to the pull request, which asks for review until someone approves or rejects the changes.
What you can delete
Once builds are landing in Snapvisor and the check is required:
happo.config.ts(or.happo.js) and thehappodependency. If you are still on Happo's older separate packages (happo.io,happo-e2e,happo-playwright,happo-cypress,happo-plugin-storybook), remove those too.- The
happowrapper in test scripts and CI, and the Happo task and import in your Cypress config and support file. - The Happo API key and secret from CI secrets.
parameters.happoentries in stories.- The Happo GitHub app, once the Snapvisor status check is required.
First-build checklist
After the first Snapvisor build on your base branch:
- Open the build and confirm it has a screenshot for each component and variant you replaced.
- Look through the screenshots for fonts, images and animations that did not settle. Fix those first, before any threshold change.
- Run the same build twice on one commit and check that nothing shows as changed.
- Open a pull request with one small visual change. Confirm the Snapvisor status check appears, asks for review, and clears after you approve.
- Make the Snapvisor check required in branch protection.
Frequently asked questions
- Do I have to stop using Playwright, Cypress or Storybook?
- No. Snapvisor captures through the test run you already have. Your specs, fixtures and stories stay, and only the screenshot call and the wrapper around the run change.
- Can I import my Happo reports as baselines?
- No. There is no import path. Your first Snapvisor build on your base branch becomes the new baseline: it has nothing to compare against, so it is approved automatically. Run it when your base branch is visually correct.
- Where do component and variant go?
- Into the screenshot name. Happo identifies a screenshot by component and variant. Snapvisor identifies it by one name that must be unique within a run, such as Hero Image - default.
- Can I run Happo and Snapvisor side by side while I switch?
- Yes. They are independent. Keep the happo wrapper and happoScreenshot calls, add the Snapvisor capture calls next to them, and remove Happo once the Snapvisor check is your gate.
Migrate from Loki
Move Storybook visual tests from Loki to Snapvisor - how loki test, update and approve, the .loki reference folder, chromeSelector, configurations, tolerance and skipped stories translate.
Migrate from reg-suit
Move a reg-suit setup to Snapvisor - how regconfig.json, key generators, storage publishers and notifier plugins translate, and how to keep your capture tool (storycap or plain Playwright screenshots) or switch to the Snapvisor SDKs.