If your end-to-end tests already run in Cypress, adding visual checks to them sounds like one more assertion. It isn’t quite. Cypress’s own docs are direct about it: “Cypress does not perform image comparison itself.” cy.screenshot() saves an image, and that’s all. To compare it with a baseline, you add a plugin or send it to a service.
This guide covers the options as of November 2026, how to set up the most common open-source plugin, and how to keep screenshots stable enough that the tests mean something.
Your options at a glance
There are two kinds of tool. Open-source plugins compare screenshots inside your Cypress run and keep the baselines in your repository. Hosted services capture during the run, upload, and give you a review UI on top.
| Tool | Type | Command in your tests | Latest release on npm | Cost |
|---|---|---|---|---|
| @simonsmith/cypress-image-snapshot | Open-source plugin | cy.matchImageSnapshot() | 11.0.0, 3 September 2026, needs Cypress 15.10 or later | Free, MIT |
| cypress-visual-regression | Open-source plugin | cy.compareSnapshot() | 6.0.1, 8 September 2026, needs Cypress 15 or later | Free, MIT |
| cypress-image-diff-js | Open-source plugin | cy.compareSnapshot() | 2.8.0, 23 March 2026 | Free, MIT |
| Percy | Hosted service | cy.percySnapshot() | @percy/cypress 3.1.9 | Free for 5,000 screenshots a month, then from $199/month billed annually |
| Argos | Hosted service | cy.argosScreenshot() | @argos-ci/cypress 7.4.0 | Free up to 5,000 screenshots for one person, Pro from $100/month |
| Chromatic | Hosted service | Captures during your tests | @chromatic-com/cypress 1.0.0, needs Cypress 15.10 or later | Free for 5,000 snapshots a month in Chrome, Starter $179/month |
| Happo | Hosted service | .happoScreenshot() | happo 7.3.1 | Free for 5,000 snapshots a month in Chrome, Starter $149/month |
| Applitools | Hosted service | Through @applitools/eyes-cypress | 3.64.0 | $667/month paid annually |
Release numbers and dates are from the npm registry, checked on 9 November 2026. Prices are from each vendor’s pricing page, checked on 4 and 5 November 2026. See visual testing pricing compared for how each service counts screenshots.
One package to avoid: the original cypress-image-snapshot (without the @simonsmith/ scope). Its latest release, 4.0.1, was published in January 2021 and declares support for Cypress 4. The @simonsmith/ package is the maintained fork.
Open-source plugin or hosted service?
A plugin is free, keeps everything in your repository and CI, and needs no account. You also own everything around it: storing baselines, reviewing diff images, deciding when to update, and keeping rendering consistent between machines.
A hosted service gives you a review UI where someone approves or rejects each change, usually on the pull request, and some render in several browsers. You pay once you pass the free tier, and the screenshots leave your network.
A reasonable rule: if one or two developers own the tests and review the diffs themselves, start with a plugin. If designers, QA or product people need to approve visual changes, or you need several browsers, a service saves time.
Setting up @simonsmith/cypress-image-snapshot
This plugin uses jest-image-snapshot’s diffing inside Cypress. The steps below follow its README.
1. Install it.
npm install --save-dev @simonsmith/cypress-image-snapshot
2. Register the plugin in setupNodeEvents:
// cypress.config.ts
import { defineConfig } from 'cypress';
import { addMatchImageSnapshotPlugin } from '@simonsmith/cypress-image-snapshot/plugin';
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000', // your app's URL
setupNodeEvents(on) {
addMatchImageSnapshotPlugin(on);
},
},
});
3. Add the command in your support file:
// cypress/support/e2e.ts
import { addMatchImageSnapshotCommand } from '@simonsmith/cypress-image-snapshot/command';
addMatchImageSnapshotCommand();
4. Take a snapshot in a test:
// cypress/e2e/home.cy.ts
describe('home page', () => {
it('looks right', () => {
cy.viewport(1280, 720);
cy.visit('/');
cy.matchImageSnapshot('home');
});
});
The first run saves a baseline in a folder named after the spec file, here cypress/snapshots/home.cy.ts/home.snap.png. Later runs compare against it and, when they differ, save a diff image in a __diff_output__ folder next to the baseline and fail the test.
A run with no baseline passes and creates one. In CI, where baselines should already be committed, add --expose requireSnapshots=true so a missing baseline fails the test instead.
5. Update baselines when a change is intended:
npx cypress run --expose updateSnapshots=true
Commit the baseline images with your code, so a pull request that changes the UI also shows the new screenshots.
Making Cypress screenshots stable
A visual test that fails at random gets ignored. Most flaky screenshots come from the same few causes, and Cypress’s visual testing guide covers them.
Set the viewport explicitly. Call cy.viewport() or set viewportWidth and viewportHeight in the config. The screenshot docs note that in a headless run the browser’s screen defaults to 1280×720, and screenshots are cut off at the screen’s edges.
Freeze the date. cy.clock() overrides Date, timers, requestAnimationFrame and a few other time APIs. To change only the date and leave timers running, override just Date:
cy.clock(Date.UTC(2026, 0, 1), ['Date']);
cy.visit('/');
Let Cypress pause animations. cy.screenshot() has disableTimersAndAnimations set to true by default, which pauses JavaScript timers and CSS animations while it captures. Cypress’s guide still suggests turning off CSS animations and transitions in your test environment, or waiting for them to finish, before the screenshot.
Black out what changes on every load. The blackout option paints over every element that matches the selectors you give it. Plugins built on cy.screenshot() usually pass screenshot options through:
cy.matchImageSnapshot('home', {
blackout: ['.ad-slot', '[data-testid="live-chat"]'],
});
Generate baselines where you compare them. Rendering depends on installed fonts, the operating system and the browser version. Cypress’s guide recommends creating baselines and running comparisons in the same environment, such as the same Docker image, with pinned browser versions. A baseline made on a Mac and compared on a Linux CI runner will usually differ in text rendering even when nothing changed.
Running it in Docker and CI
Cypress publishes official Docker images. cypress/included has a fixed Cypress version plus Chrome, Firefox and Edge on amd64, and its entrypoint runs cypress run. To pass run options, its README overrides the entrypoint:
docker run -it --rm -v .:/app -w /app --entrypoint cypress \
cypress/included:16.2.1 run --browser chrome
And to update baselines in the same container:
docker run -it --rm -v .:/app -w /app --entrypoint cypress \
cypress/included:16.2.1 run --browser chrome --expose updateSnapshots=true
A few things to watch:
- Pin the full tag for baselines. The short tag
16.2.1fixes the Cypress version, but the README warns its contents “may be updated with newer browser versions without notice”. For screenshots, use the full tag, which also names the Node, Chrome, Firefox and Edge versions (cypress-<version>-node-<version>-chrome-<version>-ff-<version>-edge-<version>). - Match the architecture. On an Apple Silicon Mac, Docker pulls the arm64 image, which has no Edge and only recent Chrome versions. If CI runs on amd64, add
--platform linux/amd64when you create baselines. - Reach your app. Inside the container,
localhostis the container itself. If the app runs on your machine, pointbaseUrlathost.docker.internal(on Docker Desktop) or run the app in the container too. - CI. The README says the
includedimages aren’t primarily meant for CI and recommendscypress/browsersorcypress/basethere. Whichever you use, keep the browser versions the same as where you made the baselines. If you do usecypress/includedon GitLab CI, setentrypoint: [""].
Where Diffy fits
Diffy doesn’t have a Cypress integration. It works from URLs instead of from tests: you give it a list of pages, and it screenshots them at every breakpoint you choose and compares two environments, such as staging and production, or the same site before and after a deployment.
That makes it a complement rather than a replacement. Keep Cypress for flows that need a script, such as logging in, filling a form or going through checkout, and let Diffy check how every page looks after a deployment. It can run on a schedule, from GitHub Actions or GitLab CI, and the free plan covers 500 screenshots a month.
Frequently asked questions
Does Cypress have built-in visual regression testing?
No. cy.screenshot() takes screenshots but doesn’t compare them. Cypress’s docs say it “does not perform image comparison itself”, and point to open-source plugins and commercial services for the comparison.
Which Cypress visual regression plugin should I use?
For a free, self-hosted option, @simonsmith/cypress-image-snapshot and cypress-visual-regression both had releases in September 2026 and support current Cypress versions. cypress-image-diff-js has a companion package, cypress-image-diff-html-report, with an HTML report where you can update baselines. Avoid the original cypress-image-snapshot package, which hasn’t had a release since January 2021.
Why do my Cypress screenshots differ between my machine and CI?
Fonts, the operating system and the browser version all change how a page renders. Create baselines and run comparisons in the same environment, ideally the same Cypress Docker image, pinned to a full tag that fixes both the Cypress and browser versions.
How do I update Cypress snapshot baselines?
It depends on the plugin. With @simonsmith/cypress-image-snapshot, run npx cypress run --expose updateSnapshots=true. With cypress-visual-regression, run in base mode, npx cypress run --expose visualRegressionType=base, to generate new baselines.
Can I use Percy or Chromatic with Cypress?
Yes. For Percy, install @percy/cli and @percy/cypress, add cy.percySnapshot() to your tests, and run npx percy exec -- cypress run. Chromatic’s @chromatic-com/cypress (version 1.0.0 needs Cypress 15.10 or later and Node 22 or later) captures an archive of the page during your tests, which it renders in its cloud. Chromatic’s TurboSnap doesn’t work with Cypress, so every snapshot is billed at the full rate.
Related reading: Playwright visual regression testing · Why Playwright screenshots differ between local, CI and Docker · Chromatic alternatives · What is visual regression testing?
