Migrating from Legacy Happo Packages
This guide will help you migrate from the legacy Happo packages (happo.io,
happo-cypress, happo-playwright, etc.) to the new unified happo package.
Overview
The new happo package is a modern, TypeScript-first rewrite that consolidates
functionality from multiple legacy packages into a single, unified package. This
migration brings several benefits:
- Unified package: One package instead of multiple (
happo.io,happo-cypress,happo-playwright,happo-plugin-storybook,happo-e2e, andhappo-static) - TypeScript support: Built-in TypeScript support with full type definitions
- Modern ES modules: Uses ES modules
- Improved configuration: More flexible configuration file formats
- Simplified target configuration: Plain object syntax instead of class-based targets
Breaking Changes
Node.js Version Requirement
The new happo package requires Node.js >= 22. Ensure your environment
meets this requirement before migrating.
Package Installation
Remove all legacy Happo packages and install the new unified happo package:
npm uninstall happo.io \
happo-cypress \
happo-playwright \
happo-e2e \
happo-plugin-storybook \
happo-static
npm install --save-dev happo
The new happo package replaces all of these legacy packages. All functionality
is now included in the single unified package.
Configuration File Format
The new package supports multiple configuration file formats and encourages TypeScript/ES modules:
Before (legacy):
const { RemoteBrowserTarget } = require('happo.io');
module.exports = {
apiKey: process.env.HAPPO_API_KEY,
apiSecret: process.env.HAPPO_API_SECRET,
targets: {
'chrome-desktop': new RemoteBrowserTarget('chrome', {
viewport: '1024x768',
}),
},
};
After (new):
import { defineConfig } from 'happo';
export default defineConfig({
apiKey: process.env.HAPPO_API_KEY!,
apiSecret: process.env.HAPPO_API_SECRET!,
project: 'default',
integration: {
type: 'storybook',
configDir: '.storybook',
},
targets: {
'chrome-desktop': {
type: 'chrome',
viewport: '1024x768',
},
},
});
Target Configuration
The new package uses plain objects instead of the RemoteBrowserTarget class
when configuring targets.
Before (legacy):
const { RemoteBrowserTarget } = require('happo.io');
module.exports = {
// ... rest of config
targets: {
'firefox-desktop': new RemoteBrowserTarget('firefox', {
viewport: '1024x768',
chunks: 2,
freezeAnimations: 'last-frame',
}),
'chrome-mobile': new RemoteBrowserTarget('chrome', {
viewport: '375x667',
}),
'ios-safari': new RemoteBrowserTarget('ios-safari', {
viewport: '375x667',
}),
},
};
After (new):
import { defineConfig } from 'happo';
export default defineConfig({
// ... rest of config
targets: {
'firefox-desktop': {
type: 'firefox',
viewport: '1024x768',
chunks: 2,
freezeAnimations: 'last-frame',
},
'chrome-mobile': {
type: 'chrome',
viewport: '375x667',
},
'ios-safari': {
type: 'ios-safari',
},
},
});
Key changes:
- Remove
new RemoteBrowserTarget()wrapper - Use
typeinstead of the first constructor argument - All target options remain the same (viewport, chunks, freezeAnimations, etc.)
- Mobile Safari targets (
ios-safari,ipad-safari) don't require a viewport (they use a fixed size) freezeAnimationsnow defaults to'last-frame'
Animations
The previous default for stopping animations was to freeze them on the first
frame. The new package changes this to stop at the last frame instead. To get
the old behavior, set target.freezeAnimations: 'first-frame':
import { defineConfig } from 'happo';
export default defineConfig({
// ... rest of config
targets: {
chrome: {
type: 'firefox',
viewport: '1024x768',
freezeAnimations: 'first-frame',
},
},
});
CLI Commands
The CLI command changes as well:
Before (legacy):
npx happo run
# or
npm run happo run
After (new):
npx happo
# or
npm run happo
The CLI is now provided by the happo package instead of happo.io and has a
simplified API.
Removed happo-ci* scripts
The following scripts have all been replaced by the main happo CLI command:
happo-cihappo-ci-github-actionshappo-ci-travishappo-ci-circlecihappo-ci-azure-pipelines
The main happo CLI command now detects these CI environments automatically.
Removed --allow-failures flag
The --allow-failures flag from the happo-e2e command was removed. To allow
failures, set integration.allowFailures: true in your configuration file
instead.
import { defineConfig } from 'happo';
export default defineConfig({
integration: {
type: 'cypress',
allowFailures: true,
},
// ... rest of config
});
Integration Types
The new package uses an integration field in the configuration to specify the
type of integration.
Custom Bundle Integration
Before (legacy):
module.exports = {
generateStaticPackage: () => ({ path: './static' }),
// ... rest of config
};
After (new):
import { defineConfig } from 'happo';
export default defineConfig({
integration: {
type: 'custom',
build: async () => ({
rootDir: './tmp/happo-custom',
entryPoint: 'bundle.js',
}),
},
// ... rest of config
});
The returned object from build has to include a rootDir (path to the folder
where files have been built, e.g. /foo/bar/build) and entryPoint (local file
name of the built JavaScript bundle, e.g. bundle.js)
Note: If you're using the happo-static library, replace any happo-static
imports with imports for happo/custom.
Storybook Integration
Before (legacy):
const happoPluginStorybook = require('happo-plugin-storybook');
module.exports = {
plugins: [
happoPluginStorybook({
configDir: '.storybook',
}),
],
// ... rest of config
};
After (new):
import { defineConfig } from 'happo';
export default defineConfig({
integration: {
type: 'storybook',
configDir: '.storybook',
},
// ... rest of config
});
Storybook preset
Before (legacy):
export default {
addons: ['happo-plugin-storybook/preset'],
// ... rest of storybook config
};
After (new):
export default {
addons: ['happo/storybook/preset'],
// ... rest of storybook config
};
Storybook decorator
Before (legacy):
import 'happo-plugin-storybook/register';
import happoDecorator from 'happo-plugin-storybook/decorator';
After (new):
import 'happo/storybook/register';
import happoDecorator from 'happo/storybook/decorator';
Cypress Integration
The configuration changes for Cypress integration:
Before (legacy):
const { RemoteBrowserTarget } = require('happo.io');
module.exports = {
targets: {
chrome: new RemoteBrowserTarget('chrome', {
viewport: '1024x768',
}),
},
// ... rest of config
};
After (new):
import { defineConfig } from 'happo';
export default defineConfig({
integration: {
type: 'cypress',
},
targets: {
chrome: {
type: 'chrome',
viewport: '1024x768',
},
},
// ... rest of config
});
In your Cypress test files, update your imports to use the new package:
Before (legacy):
import 'happo-cypress';
After (new):
import 'happo/cypress';
And in your cypress.config.js, update the task import:
Before (legacy):
const happoTask = require('happo-cypress/task');
After (new):
const happoTask = require('happo/cypress/task');
The happo-e2e wrapper command is replaced by the main happo command. Replace
npx happo-e2e -- cypress run with npx happo -- cypress run
Playwright Integration
The configuration changes for Playwright integration:
Before (legacy):
const { RemoteBrowserTarget } = require('happo.io');
module.exports = {
targets: {
chrome: new RemoteBrowserTarget('chrome', {
viewport: '1024x768',
}),
},
};
After (new):
import { defineConfig } from 'happo';
export default defineConfig({
integration: {
type: 'playwright',
},
targets: {
chrome: {
type: 'chrome',
viewport: '1024x768',
},
},
});
In your Playwright test files, update your imports to use the new package:
Before (legacy):
import { test } from 'happo-playwright';
After (new):
import { test } from 'happo/playwright';
The happo-e2e wrapper command is replaced by the main happo command. Replace
npx happo-e2e -- playwright test with npx happo -- playwright test
Happo Examples Integration
The Happo examples integration has been removed in favor of the custom
integration type (originally in the happo-static package).
This puts the bundling and rendering of the examples to the DOM completely in your control. If you are using the legacy Happo examples integration, you will need to convert these to a Happo custom type. Follow the Happo Custom integration documentation.
E2E Command Changes
For Cypress and Playwright integrations, use the new happo CLI command:
Before (legacy):
npx happo-e2e -- npx cypress run
npx happo-e2e -- npx playwright test
After (new):
npx happo -- cypress run
npx happo -- playwright test
Configuration Options
These options, that were only used by the Happo Examples integration, are removed:
stylesheets- removedpublicFolders- removedsetupScript- removedcleanupScript- removedinclude- removedexclude- removed
Some target options changed:
freezeAnimations- now defaults to'last-frame'
Environment Variables
The following environment variables have been removed and are no longer necessary:
BASE_BRANCH-- use--baseBranchinsteadCHANGE_URLandHAPPO_CHANGE_URL-- use--linkinsteadCURRENT_SHA-- use--afterShainsteadHAPPO_BEFORE_SHA_TAG_MATCHER-- use--beforeShaTagMatcherinsteadHAPPO_BUILD_STORYBOOK_COMMAND-- this is no longer usedHAPPO_FALLBACK_SHAS_COUNT-- use--fallbackShasCountinsteadHAPPO_FALLBACK_SHAS-- use--fallbackShasinsteadHAPPO_GITHUB_BASEandGITHUB_BASE-- use--githubBaseinsteadHAPPO_IS_ASYNC-- async mode is now the only modeHAPPO_MESSAGE-- use--messageinsteadHAPPO_NONCE-- use--nonceinsteadHAPPO_NOTIFY-- use--notifyinsteadHAPPO_SIGNED_URL-- we use signed upload URLs by default nowPREVIOUS_SHA-- use--beforeShainstead
Migration Steps
- Update Node.js: Ensure you're running Node.js >= 22
- Remove legacy packages: Uninstall all legacy Happo packages (
happo.io,happo-cypress,happo-playwright,happo-e2e,happo-plugin-storybook,happo-static) - Install new package: Install the unified
happopackage - Update configuration file: Convert your
.happo.jsto use the new format:- Remove
RemoteBrowserTargetimports/usage - Convert targets to plain objects with
type - Optionally convert to TypeScript (
happo.config.ts)
- Remove
- Update integration configs: Add
integrationfield - Update imports: Update any imports from legacy packages (e.g.,
happo-cypress→happo/cypress,happo-playwright→happo/playwright) - Update CLI commands: Replace
happo-e2ecommands withhappofor Cypress/Playwright integrations - Update environment variables: Remove unused env vars
- Test your setup: Run your Happo tests to ensure everything works
Troubleshooting
"Cannot find module 'happo.io'" or "Cannot find module 'happo-cypress'"
This error occurs if you haven't updated your dependencies. Make sure to:
- Remove all legacy Happo packages from
package.json(happo.io,happo-cypress,happo-playwright,happo-e2e,happo-plugin-storybook,happo-static) - Add
happotopackage.json - Run
npm install(orpnpm install/yarn install) - Update any imports in your code to use the new package exports (e.g.,
happo-cypress→happo/cypress)
"RemoteBrowserTarget is not defined"
This error occurs if you're still using the old target syntax. Convert all
new RemoteBrowserTarget(...) to plain objects with type.
"Node.js version mismatch"
Ensure you're running Node.js >= 22. You can check your version with:
node --version
Configuration file not found
The new package looks for configuration files named happo.config.{ext} in this
order:
.js.mjs.cjs.ts.mts.cts
Make sure your configuration file matches one of these names.
Need Help?
If you encounter issues during migration, please reach out to support@happo.io