Cypress
The happo/cypress module adds Happo cross-browser
screenshots to your Cypress test suite.
Check out the
happo-cypress-example-todomvc
for a demo of how this module is used.
Pre-requisites
Before you start following the instructions here, you'll need a working Cypress test suite and a Happo account.
Installation
In your project, install the happo npm module.
npm install --save-dev happo
Setup
Import happo/cypress in your cypress/support/commands.js file:
import 'happo/cypress';
Then, register the provided happoTask in your cypress.config.js file (or
cypress/plugins/index.js if you are on Cypress v9 or earlier):
- Cypress v10
- Cypress v9
const { defineConfig } = require('cypress');
import happoTask from 'happo/cypress/task';
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
happoTask.register(on);
return config;
},
},
});
import happoTask from 'happo/cypress/task';
export default (on) => {
happoTask.register(on);
};
Add a happo.config.ts file with some minimal/required configuration:
import { defineConfig } from 'happo';
export default defineConfig({
integration: {
type: 'cypress',
},
apiKey: process.env.HAPPO_API_KEY,
apiSecret: process.env.HAPPO_API_SECRET,
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:
import { defineConfig } from 'happo';
export default defineConfig({
apiKey: process.env.HAPPO_API_KEY,
apiSecret: process.env.HAPPO_API_SECRET,
// ... more config
});
Usage with cypress run
To enable Happo in your test suites triggered via cypress run, you'll need to
use the happo wrapper. Here's an example:
npx happo -- cypress run
If you're using yarn, you might have to specify the
double dashes twice (the first one is consumed by yarn itself):
yarn happo -- -- yarn cypress run
If you're not using the happo wrapper with cypress run, Happo will be
disabled for the whole test suite.
Usage with cypress open
When running Cypress tests locally using the cypress open command, Happo is
disabled by default. The happo wrapper won't work with cypress open since it
depends on the Cypress command to finish after a test run (which won't happen
with cypress open).
Usage
To record Happo screenshots in your test suite, use happoScreenshot:
describe('Home page', function () {
it('loads properly', function () {
cy.visit('/');
cy.get('.header').happoScreenshot();
});
});
Happo focuses more on component screenshots as opposed to full-page screenshots.
Because of that, you always need to select a child before you call
happoScreenshot. If you still need a full-page screenshot you can use the
<body> element:
cy.get('body').happoScreenshot();
Happo identifies screenshots by component and variant. By default, the
component name and variant are inferred from the current test case. If you want
more control, you can provide these in an options argument:
// Full control, pass in both component and variant:
cy.get('.header').happoScreenshot({ component: 'Header', variant: 'large' });
// Control the component name, but let variant be auto-assigned
cy.get('.footer').happoScreenshot({ component: 'Footer' });
// Control variant, but let component name be inferred
cy.get('.footer').happoScreenshot({ variant: 'dark' });
// No control, infer component and variant from current test
cy.get('.footer').happoScreenshot();
Allowing failures
By default, no Happo reports are produced when the Cypress run fails. In some
cases, you might want to allow Happo to succeed even if the overall test run
fails. The allowFailures configuration option can then be used:
export default defineConfig({
integration: {
type: 'cypress',
allowFailures: true,
},
// ... rest of the config
});
If you're using yarn, you might need to pass the double dashes twice, like so:
yarn happo -- --allow-failures -- yarn cypress run
Selecting targets
If you want to avoid rendering an example in all browser targets (found in
happo.config.ts), you can use the targets option. The example will then be
rendered in the specified targets exclusively.
cy.get('.footer').happoScreenshot({
component: 'Footer',
targets: ['chrome-small'],
});
In this example, the "Footer" snapshot will only be rendered in the target named
'chrome-small' found in happo.config.ts.
Dynamic targets
If you want to create a snapshot in a target that isn't defined in the
happo.config.ts config, you can use an object with name, browser and
viewport properties. Here's an example where a snapshot is taken in a
dynamically generated target:
cy.get('.footer').happoScreenshot({
component: 'Footer',
targets: [{ name: 'firefox-small', browser: 'firefox', viewport: '400x800' }],
});
Here, "Footer" will only be rendered in a 400x800px Firefox window.
You can mix and match dynamic targets and target names as well:
cy.get('.footer').happoScreenshot({
component: 'Footer',
targets: [
'chrome-small',
{ name: 'firefox-small', browser: 'firefox', viewport: '400x800' },
],
});
"Footer" is now rendered in Chrome (target specified in happo.config.ts) and
Firefox (dynamic target).
Hiding dynamic content
If you have dynamic content that changes often, you can ask Happo to hide it from the screenshot. This can help prevent unwanted diffs caused by e.g. timestamps, dates, randomized content.
cy.happoHideDynamicElements();
Confusingly enough, elements keep being visible while you run the Cypress test
suite. Instead, a data-happo-hide attribute is added. This will inform Happo's
workers to hide the elements before taking the screenshot. To cover up/replace
elements instead of hiding them, use the replace option:
cy.happoHideDynamicElements({ replace: true });
Elements will then be replaced (actually covered) by black rectangles. Please
note that in some cases this can affect layout. Simply hiding elements (the
default) is slightly safer, since all we have to do is set visibility: hidden.
You can use data-happo-hide manually in your source code as well if you prefer
to hide certain elements without using happoHideDynamicElements.
Selectors
By default happoHideDynamicElements will attempt to find <time> elements to
hide. You can add your own selectors using the selectors option.
cy.happoHideDynamicElements({
selectors: ['.date'],
});
Using selectors will not affect the default. To remove the default selectors,
you can set defaultSelectors to an empty array:
cy.happoHideDynamicElements({
defaultSelectors: [],
selectors: ['.date'],
});
Matchers
The default implementation of happoHideDynamicElements will attempt to find
things like "2 days ago", "2:45 PM", etc. You can supply your own content
matchers (regular expressions) if the default implementation isn't working for
you.
cy.happoHideDynamicElements({
matchers: [/liked by [0-9]+ people/],
});
To minimize false negatives, only leaf nodes are hidden by matchers.
Using matchers will not affect the default. To remove the default matchers,
you can set defaultMatchers to an empty array:
cy.happoHideDynamicElements({
defaultMatchers: [],
matchers: [/liked by [0-9]+ people/],
});
Continuous Integration
If you run the test suite in a CI environment, the happo module will do its
best to auto-detect your environment and adapt its behavior accordingly:
- On PR builds, compare the screenshots against the
mainbranch - On
mainbranch builds, simply create the Happo report
To get the results of the Happo jobs back to your PRs/commits, you need to install and configure the Happo GitHub app. Instructions are available in the Continuous Integration docs.
Happo auto-detects the following CI environments:
- GitHub Actions
- Circle CI
- Travis CI
- Azure DevOps
If you are using a different CI service, you'll have to set a few CLI args when
invoking the happo command:
--beforeShathe commit SHA that the branch/PR is based on (usually a commit onmain). Only set this for PR builds.--afterShathe SHA of the commit currently under test (typically the SHA atHEAD). Always set this.--baseBranchthe default/base branch you use, e.g.origin/dev. Defaults toorigin/main, so you only need to set this if you are using a different base branch.--linka URL to the PR/commit to help contextualize the report. Optional.
GitHub Actions example
Here's an example of how you can use Happo with Cypress in a GitHub Actions workflow. It makes use of the Cypress GitHub action.
name: Cypress with Happo workflow
on:
# Configure this workflow to trigger on pull requests and pushes to main
push:
branches:
- main
pull_request:
branches:
- main
jobs:
cypress:
name: Cypress with Happo
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Run cypress
uses: cypress-io/github-action@v2
with:
command-prefix: npx happo -- npx
env:
# Inject secrets to the build
HAPPO_API_KEY: ${{ secrets.HAPPO_API_KEY }}
HAPPO_API_SECRET: ${{ secrets.HAPPO_API_SECRET }}
Circle CI example
This example triggers a Cypress with Happo run in a Circle CI environment. It uses the Cypress Circle CI Orb. Separately, you'll need to configure the Circle CI project to inject the Happo API key and secret through environment variables.
version: 2.1
orbs:
node: circleci/node@4.1.0
cypress: cypress-io/cypress@1.27.0
workflows:
cypress:
jobs:
- cypress/run:
name: Run Cypress with Happo
command-prefix: 'npx happo -- npx'
Travis CI example
This is a simplified example of using Cypress with Happo in Travis CI. For a more in-depth explanation of how to set up Cypress with Travis, see https://docs.cypress.io/guides/guides/continuous-integration.html#Travis
language: node_js
node_js:
- 24
install:
- npm ci
script:
- npx happo -- cypress run
Azure DevOps
Here's an example of how you can run Cypress with Happo in an Azure DevOps environment. This example assumes you have a Pull request trigger set up in your repository.
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- task: NodeTool@0
inputs:
versionSpec: '24.x'
displayName: 'Install Node.js'
- script: |
npm ci
npx happo -- npx cypress run
displayName: 'Install and Run Happo'
env:
HAPPO_API_KEY: $(happoApiKey)
HAPPO_API_SECRET: $(happoApiSecret)
Parallel builds
If you're running your Cypress test suite across multiple machines, you'll need to do two things:
- Use the
--nonceCLI argument to tie individual runs together. Any string unique to the build will do. - After the whole test suite is done, call
npx happo finalize. Make sure that the same--nonceis set as for the individual builds.
In some CI tools, you can use a built-in environment variable as --nonce. In
CircleCI for instance, you can use
--nonce ${CIRCLE_WORKFLOW_ID}.
You can also use a timestamp or a randomly generated string. The important thing
is that it's unique to the current CI run.
Here's an example configuration for a parallel build running in Circle CI (adapted from the config used for the happo-cypress test suite):
version: 2.1
orbs:
node: circleci/node@4.1.0
cypress: cypress-io/cypress@1.27.0
workflows:
cypress:
jobs:
- cypress/install
- cypress/run:
name: cypress-parallel
requires:
- cypress/install
start: npm run start-dev-server
parallel: true
parallelism: 4
command-prefix: 'npx happo --nonce ${CIRCLE_WORKFLOW_ID} -- npx'
post-steps:
- run: 'npx happo --nonce ${CIRCLE_WORKFLOW_ID} finalize'
Notice how the same --nonce is used in both the Cypress run itself and the
finalize call.
Email notifications
If you set the --notify CLI argument as part of your Cypress 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
Passing along options
Any options you pass to happoScreenshot or happoHideDynamicElements that we
don't recognize we will pass along to the underlying Cypress tasks. This means
you can for instance suppress logs by using log: false:
cy.get('.header').happoScreenshot({
component: 'Header',
variant: 'large',
log: false,
});
Transforming the DOM
If you need to transform the DOM in some way before a snapshot is taken, you can
use the transformDOM option. Here's an example where <iframe>s are replaced
with a div placeholder.
cy.get('.main').happoScreenshot({
component: 'Main',
transformDOM: {
selector: 'iframe',
transform: (element, doc) => {
// element here is an iframe
const div = doc.createElement('div');
div.innerHTML = '[iframe placeholder]';
return div; // return the element you want to replace the iframe with
},
},
});
The options passed to transformDOM are:
selector: an argument passed toquerySelectorAllthat describes what elements to transform.transform: a function that is called for each element matching the selector (there can be more than one). Make sure you return a replacement element.
Download all assets
By default, happo will download assets found locally and include them in an
assets package sent to happo.io. Any external URL will be left as-is, which
means they are expected to be publicly accessible by Happo workers. To include
external assets in the assets package as well, set a HAPPO_DOWNLOAD_ALL
environment variable.
HAPPO_DOWNLOAD_ALL=true npx happo -- cypress run
With this environment variable set, all assets are assumed to be private (i.e. not publicly accessible).
Clip snapshot strategy
By default, Happo uses the hoist snapshot strategy. This works by selecting the element for snapshotting and hoisting it out of the document to be sent to the workers. This element is then rendered in isolation in your configured browsers for screenshots.
For flexible elements, the default hoist snapshot strategy may cause the dimensions of the element to be different than it was on the page in the context that it was rendered.
If you want your screenshot to be taken of the element as it was rendered in its context on the page, and not in isolation, you may use the clip snapshot strategy.
To use the clip snapshot strategy, pass snapshotStrategy: 'clip' as an option
to happoScreenshot:
cy.get('.header').happoScreenshot({
component: 'Header',
variant: 'large',
snapshotStrategy: 'clip',
});
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
The happo/cypress module will disable itself if it can't detect any api tokens
(apiKey and apiSecret in config). Check to make sure your happo.config.ts
config is properly set up. There might also be more information in the console
logs from Cypress. Look for lines starting with [HAPPO].
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.headerelement, never the surrounding page. - There could be a bug in how
happo/cypresscollects styles and assets. Reach out to support@happo.io and we'll triage.