Skip to main content
Version: Next

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:

cypress/support/commands.js
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.config.js
const { defineConfig } = require('cypress');
import happoTask from 'happo/cypress/task';

export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
happoTask.register(on);
return config;
},
},
});

Add a happo.config.ts file with some minimal/required configuration:

happo.config.ts
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:

happo.config.ts
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:

cypress/e2e/spec.cy.js
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:

happo.config.ts
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 main branch
  • On main branch 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:

  • --beforeSha the commit SHA that the branch/PR is based on (usually a commit on main). Only set this for PR builds.
  • --afterSha the SHA of the commit currently under test (typically the SHA at HEAD). Always set this.
  • --baseBranch the default/base branch you use, e.g. origin/dev. Defaults to origin/main, so you only need to set this if you are using a different base branch.
  • --link a 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.

.github/workflows/cypress.yml
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.

.circleci/config.yml
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

.travis.yml
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.

azure-pipelines.yml
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 --nonce CLI 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 --nonce is 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):

.circleci/config.yml
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 to querySelectorAll that 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 .header element, never the surrounding page.
  • There could be a bug in how happo/cypress collects styles and assets. Reach out to support@happo.io and we'll triage.