Upgrading to happo v7
happo v7 removes some old options and changes some defaults. Most of the new defaults are settings that were already in v6 as opt-ins and that we think give most projects a better experience: fewer diffs nobody can see, screenshots that don't depend on servers outside your control, and problems that can't be approved by accident. Each section below says what changed, why, what you'll notice, and how to keep the v6 behavior if you need to.
How to upgrade with the least friction
Almost every change in v7 can be tried on v6 first. That lets you take the changes one pull request at a time and review the diffs each one brings, instead of all at once.
- Upgrade to the latest v6 release.
- Work through Before you upgrade. Each step is a config change you can make, review and merge on its own.
- Upgrade to v7. If you did step 2, the upgrade itself should bring no new diffs.
You can also upgrade straight to v7. A config that uses a removed or unknown option fails with an error that names it, and every problem in the config is reported together. Read Changes that don't fail first: those ones take effect without an error.
Changes that don't fail
These changes don't stop a run. Happo behaves differently instead, so check each one before you upgrade:
- The status check can change name. A project without
groupnow reports to theHappocheck instead ofHappo (<project>). If branch protection requires the old name, pull requests can't be merged until you change it. - External requests are blocked. Fonts, images or scripts loaded from other hosts go missing from screenshots unless a target lists them in
allowedHostnames. - A color delta threshold (
compareThreshold) set at happo.io stops applying. WithoutdeepComparein the config, comparisons use the default threshold instead. - Screenshots grow. Examples with an outline or a shadow at their edge get bigger screenshots, so the first run shows a diff for each of them.
- Some comparisons can't be approved. Render errors, new accessibility violations and pages that start to scroll sideways block approval.
- Hover and focus states show up. Cypress and Playwright snapshots show the state an element is in when the snapshot is taken.
- Runs are split into more chunks. Pages, Cypress and Playwright runs are chunked automatically.
Before you upgrade
Do these on the latest v6 release. Each one is safe to do on its own, and each one has a section further down with the details.
- Node.js
^22.18.0or>=24.0.0, and Storybook v9 or later. See Requirements. - Fix the warnings. v6 prints
Unknown optionfor every option it doesn't know. v7 fails on them, so fix them now. See Unknown options are errors. - Replace
--skippedExampleswith--skip. It takes the same value. - Have happo.io post your statuses. If you pass
--githubTokenor setgithubApiUrl, connect your repository (see Continuous integration, or GitHub Enterprise), then remove them. - Custom integration: return
estimatedSnapsCountfrombuild(). v6 already uses it, so you can do this now. See Custom integrations must returnestimatedSnapsCount. - Set
allowedHostnameson each target. Read the hostnames your snapshots need from the logs of a recent run, list them, and check that nothing goes missing. SeeallowedHostnamesdefaults to[]. - Set
deepCompare. Copy your project's compare settings from happo.io into the config, or try the new default. SeedeepCompareis on by default. - Set
group: true, and update branch protection to require theHappocheck. Seegroupdefaults totrue. - Set
includeOutlinesAndShadows: trueon each target, and review the size diffs. SeeincludeOutlinesAndShadowsdefaults totrue. - Set
detectHorizontalOverflow: trueon each target. This brings no diffs by itself. SeedetectHorizontalOverflowdefaults totrue. - Set
blockApprovalto the conditions you want. SeeblockApprovalblocks on every condition by default. - Cypress and Playwright: set
autoApplyPseudoStateAttributes: true, and review the diffs. SeeautoApplyPseudoStateAttributesdefaults totrue.
Leave chunks in your config until you upgrade: v6 still uses it, and v7 makes you remove it.
Requirements
- Node.js:
^22.18.0or>=24.0.0. Node 23 is no longer supported: it reached its end of life in June 2025 and gets no security fixes. Node 22 and 24 are long-term support releases. - Storybook: v9 or later. Storybook v8 is no longer supported. Supporting it meant shipping a second copy of Happo's Storybook addon, built against Storybook 8's APIs, alongside the one every newer version uses.
Removed options
| Removed | Why | What to do |
|---|---|---|
chunks target option | Happo now decides how many chunks to use from the size of the run. See Chunking is always automatic. | Remove it. |
useFullPageFallbackForTallScreenshots target option | Carried over from the legacy happo.io package. v6 already ignored it, with a warning. | Remove it. |
githubApiUrl config option | happo.io posts statuses itself, for github.com and GitHub Enterprise. | Remove it. |
--githubToken CLI flag | happo.io posts statuses itself, for github.com and GitHub Enterprise. | Remove it. |
--skippedExamples CLI flag | It was another name for --skip. | Use --skip, which takes the same value. |
A removed config option is an error that says what to do:
The `targets.chrome.chunks` option in config file happo.config.ts has been removed. Happo now decides how many chunks to use, based on the size of the run. Remove the option and Happo will parallelize for you.
A removed CLI flag is rejected as an unknown option.
Posting statuses as pull request comments
The happo CLI no longer posts a comment to the pull request when given a GitHub token. happo.io posts the statuses itself, for github.com and for GitHub Enterprise, so the CLI doesn't need a token to do it. Before you upgrade, make sure your repository is connected: see Continuous integration and GitHub Enterprise.
Unknown options are errors
v6 warned about config options it didn't know. v7 fails on them, and suggests the closest known option when there is one:
Unknown option `targets.chrome.viewPort` in config file happo.config.ts. Did you mean `viewport`?
Why: a misspelled option was ignored, with a warning that was easy to miss, so a config could look right and not do what it said.
Before you upgrade: look for Unknown option in the output of a v6 run and fix each one. Configs carried over from the legacy happo.io package are the most likely to have them, for example a top-level stylesheets or compareThreshold. See Migrating from Legacy Happo Packages for where each one went.
Changed defaults
allowedHostnames defaults to []
A target without allowedHostnames now blocks every external request. In v6 it blocked nothing.
Why: a snapshot that loads a font, a script or an image from another server changes when that server does, and fails when it is down. Those are diffs and failures that have nothing to do with your change. Blocking by default keeps snapshots reproducible, and makes every outside dependency one you chose.
What you'll notice: fonts, images or styles that came from other hosts are missing from screenshots, and the first run shows diffs for them. With the pages integration, pages fail to load unless their own hostnames are listed.
Before you upgrade: you don't have to guess what to list. Every run already logs the hostnames its pages reached for, list or no list. Open the logs of a recent run in happo.io and look for this line:
External requests: 12 to 2 hostnames: fonts.gstatic.com (x11), cdn.example.com
List the ones your snapshots need on each target:
import { defineConfig } from 'happo';
export default defineConfig({
targets: {
chrome: {
type: 'chrome',
viewport: '1024x768',
allowedHostnames: ['fonts.gstatic.com', '*.mycdn.example.com'],
},
},
});
A hostname you'd rather not depend on, such as an analytics script, can stay off the list. Then run once and check the snapshots: anything blocked is named on the same log line. Set allowedHostnames: [] to block everything, as v7 does. See Missing fonts, images, or other external assets if something goes missing.
To keep the v6 behavior: there is no setting that allows every hostname. List the hostnames your snapshots use.
ios-safari and ipad-safari targets are unaffected: the option doesn't apply to them.
deepCompare is on by default
A config without deepCompare now compares with:
deepCompare: {
compareThreshold: 0.002,
diffAlgorithm: 'color-delta',
}
Why: without a threshold, every pixel that changed is a diff, including anti-aliasing and color changes too small to see. Those are diffs that someone has to look at and can't review. At 0.002, two colors that close can't be told apart on screen, so the diffs that remain are ones you can see. See Compare with a threshold.
What you'll notice: fewer diffs. If your project has compare settings at happo.io, they stop applying: settings from the config take the place of the project's, so comparisons use 0.002 instead.
Before you upgrade: check your project's settings at happo.io. If it has a color delta threshold you want to keep, copy it into deepCompare as compareThreshold in the config. If it has none, try the new default by setting it in the config now.
To keep the v6 behavior: set deepCompare: false to send no settings and use the project's settings at happo.io.
blockApproval blocks on every condition by default
A comparison can no longer be approved while the report has examples that failed to render, while the comparison introduces new accessibility violations, or while a page starts to scroll sideways. In v6 every blockApproval condition was off unless you turned it on.
Why: each of these is almost always a bug, and approving a comparison approves the screenshots that show it. A render error means an example has no screenshot to compare, so approving it lets a broken example through unseen. Blocking means these get fixed, or deliberately allowed in the config, rather than approved by accident.
What you'll notice: a comparison that has one of these can't be approved, and the comparison page says why.
A page that already scrolled sideways before the upgrade doesn't block approval: its baseline snapshot was taken without measuring it, so only a change that makes a page that fit scroll sideways counts. Pages are measured only on targets with detectHorizontalOverflow, which v7 also turns on.
Before you upgrade: turn the conditions on in v6 (horizontalOverflow is available from happo v6.26.0). Fix the render errors in your current reports first: unlike the other two conditions, render errors that are already there block approval too.
import { defineConfig } from 'happo';
export default defineConfig({
blockApproval: {
renderErrors: true,
accessibilityViolations: true,
horizontalOverflow: true,
},
// ... rest of config
});
To keep the v6 behavior: set a condition to false to allow approval despite it:
import { defineConfig } from 'happo';
export default defineConfig({
blockApproval: {
accessibilityViolations: false,
},
// ... rest of config
});
autoApplyPseudoStateAttributes defaults to true
For the Cypress and Playwright integrations, integration.autoApplyPseudoStateAttributes is now on by default.
Why: a test that hovers over a button or tabs to a link is usually checking that state, but the screenshot didn't show it unless you added data-happo-* attributes by hand. Now the screenshot shows the hover, focus or active state an element is in when the snapshot is taken, including the keyboard focus ring.
What you'll notice: diffs for snapshots taken while an element was hovered, focused or pressed, such as right after a click.
Before you upgrade: set autoApplyPseudoStateAttributes: true in the integration's options, and review the diffs.
To keep the v6 behavior: if you set data-happo-hover, data-happo-active or data-happo-focus-visible attributes yourself and want to keep doing so, set the option to false.
group defaults to true
A config without group now puts its project in the default group. The projects that run for the same commits combine into one Happo job, which posts a single Happo status check for all of them. In v6, each project got a job and a Happo (<project>) status check of its own.
Why: one status check for all of a commit's projects is one check to require in branch protection, one check to watch, and one job page that lists every project. Adding a project no longer means adding a check.
What you'll notice: a project other than your account's default one reports to Happo instead of Happo (<project>). If branch protection requires the Happo (<project>) checks, pull requests wait for checks that never arrive, and can't be merged.
Before you upgrade: set group: true in each config, and change branch protection to require Happo instead of the Happo (<project>) checks. A named group (group: 'e2e') gives several projects a Happo (e2e) check of their own, for checks you want to keep separate. See Multi-project setup.
If you orchestrate: keep the orchestrate call. The projects it lists still join its job, which still posts one Happo check, and it still holds that check until every project is done. See Orchestration with group.
To keep the v6 behavior: set group: false to keep a project's own job and check:
import { defineConfig } from 'happo';
export default defineConfig({
project: 'e2e',
group: false,
// ... rest of config
});
happo skip follows the same default: without a group, it passes the Happo check.
includeOutlinesAndShadows defaults to true
Screenshots now make room for the outlines and box shadows that reach past the content, so a focus ring or a shadow at the edge of an example is no longer cut off. In v6, a target had to set includeOutlinesAndShadows to get that.
Why: a screenshot cropped to the elements' boxes cuts off what is drawn outside them. A focus ring is how keyboard users see where they are, and a change that breaks one at the edge of an example went unnoticed.
What you'll notice: every example with an outline or an outer box shadow at its edge gets a bigger screenshot, so the first run after the change reports a diff for each of them.
Before you upgrade: set includeOutlinesAndShadows: true on each target in v6, in a pull request of its own, and accept the diffs there. They are all size changes from the same cause, so they are quick to review together.
To keep the v6 behavior: set the option to false on a target to keep cropping to the elements' boxes alone:
import { defineConfig } from 'happo';
export default defineConfig({
targets: {
chrome: {
type: 'chrome',
viewport: '1024x768',
includeOutlinesAndShadows: false,
},
},
});
detectHorizontalOverflow defaults to true
Happo now checks whether each page scrolls sideways: whether it reaches past the right edge of the target's viewport. A page that starts to is a diff, even when its pixels haven't changed. In v6, a target had to set detectHorizontalOverflow to get that.
Why: a page that scrolls sideways is almost always a bug, especially on a phone, and it is easy to miss in a screenshot, which simply comes out wider.
What you'll notice: no new diffs at first. A page that already scrolled sideways is flagged on the comparison page, but becomes a diff only when a change makes a page that fit scroll sideways.
Before you upgrade: set detectHorizontalOverflow: true on each target in v6, then look at the pages the comparison page flags. Those are pages that scroll sideways today.
To keep the v6 behavior: set the option to false on a target to leave sideways scrolling unchecked:
import { defineConfig } from 'happo';
export default defineConfig({
targets: {
chrome: {
type: 'chrome',
viewport: '1024x768',
detectHorizontalOverflow: false,
},
},
});
Chunking is always automatic
Happo splits a run into about 100 snapshots per chunk, up to 20 chunks. In v6 this applied to Storybook, and to custom integrations that returned an estimatedSnapsCount. In v7 it also applies to:
- the pages integration, chunked on the number of pages
- Cypress and Playwright runs, chunked on the number of snapshots
Both were a single chunk in v6 unless you set chunks.
Why: a fixed chunks was either too few for a large run, which made it slow, or too many for a small one. Happo knows how big the run is, so it can pick.
What you'll notice: a large run finishes sooner. A run with 250 snapshots is now three snap-requests instead of one.
Custom integrations must return estimatedSnapsCount
The build function of a custom integration has to return estimatedSnapsCount alongside rootDir and entryPoint.
Why: with chunks gone, it is the only thing that tells Happo how many workers to use. Without it, a custom integration that used to set chunks would drop to a single worker and get slower, with nothing to say why.
Before you upgrade: return it now. v6 already uses it to split the run.
import { defineConfig } from 'happo';
export default defineConfig({
integration: {
type: 'custom',
build: async () => ({
rootDir: './tmp/happo-custom',
entryPoint: 'bundle.js',
estimatedSnapsCount: 42,
}),
},
});
The number doesn't have to be exact. It has to be a non-negative, finite number; leaving it out is an error.