Skip to main content
Back to Blog
Guide
2026-05-22

cypress-image-snapshot: Self-Managed Visual Testing for 2026

Complete guide to cypress-image-snapshot for self-managed visual regression in 2026. Setup, baselines, thresholds, viewport matrix, CI, and best practices.

cypress-image-snapshot: Self-Managed Visual Testing for 2026

cypress-image-snapshot is the open-source, self-managed approach to visual regression testing in Cypress. Unlike Percy and Applitools, which send screenshots to a cloud service for comparison and storage, cypress-image-snapshot does everything locally: takes screenshots, stores baselines in your repository, compares pixel-by-pixel with the baseline, and fails the test if differences exceed a threshold. The trade-off is no cloud dashboard, no historical trends, no cross-browser rendering grid, but it is free, fully under your control, and works in air-gapped environments.

This guide is the complete 2026 reference for cypress-image-snapshot. The original maintainer archived the project, but a community fork (@simonsmith/cypress-image-snapshot) is the active package as of 2026. We cover installation, your first snapshot, baseline management, threshold tuning, viewport matrices, the cross-platform pixel-rendering problem, CI integration, and best practices distilled from running real self-managed visual suites.

For broader Cypress references, browse the blog index. For visual testing skills, see the QA Skills directory.

When to use cypress-image-snapshot

Self-managed visual testing fits when:

  1. Budget is tight. No per-snapshot fees.
  2. Air-gapped environments. No external network calls.
  3. Compliance constraints. Some industries require screenshots to never leave the network.
  4. Small suites. Maintaining baselines manually is feasible.

Choose Percy or Applitools when:

  1. You need cross-browser rendering. The Ultrafast Grid and Percy's render farms.
  2. You need historical trends. Cloud dashboards.
  3. Anti-aliasing differences hurt. Visual AI tolerates them; pixel diff does not.

Installation

npm install --save-dev @simonsmith/cypress-image-snapshot

In cypress/support/e2e.ts:

import { addMatchImageSnapshotCommand } from '@simonsmith/cypress-image-snapshot/command';

addMatchImageSnapshotCommand({
  failureThreshold: 0.03,            // 3% pixel difference
  failureThresholdType: 'percent',
  customDiffConfig: { threshold: 0.1 },
  capture: 'viewport',
});

In cypress.config.ts:

import { defineConfig } from 'cypress';
import { addMatchImageSnapshotPlugin } from '@simonsmith/cypress-image-snapshot/plugin';

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      addMatchImageSnapshotPlugin(on, config);
    },
  },
});

Your first snapshot

describe('Home page', () => {
  it('matches the visual baseline', () => {
    cy.visit('/');
    cy.matchImageSnapshot('home');
  });
});

The first run creates a baseline at cypress/snapshots/home.png. Subsequent runs compare against it.

Updating baselines

When you intentionally change the UI, regenerate the baseline:

cypress run --env updateSnapshots=true

Inspect the new baselines, commit them, and continue.

Threshold tuning

The defaults work for most cases. Key options:

OptionDefaultPurpose
failureThreshold0.001How much difference is acceptable
failureThresholdType'percent''pixel' or 'percent'
customDiffConfig.threshold0.1Pixel-level color tolerance
capture'viewport''viewport' or 'fullPage'

A common config:

{
  failureThreshold: 0.03,            // 3% of pixels can differ
  failureThresholdType: 'percent',
  customDiffConfig: { threshold: 0.2 },  // a pixel must differ by 20% to count
}

Lower threshold = stricter; more false positives. Higher threshold = more tolerant; potentially miss real bugs.

Element snapshots

Snapshot a specific element instead of the viewport.

cy.get('[data-testid=card]').matchImageSnapshot('card');

The screenshot is cropped to the element's bounding box.

Viewport matrix

Snapshot at multiple viewports to catch responsive bugs.

const viewports = [
  { name: 'desktop', width: 1280, height: 720 },
  { name: 'tablet', width: 768, height: 1024 },
  { name: 'mobile', width: 375, height: 667 },
];

viewports.forEach((vp) => {
  it(`renders correctly on ${vp.name}`, () => {
    cy.viewport(vp.width, vp.height);
    cy.visit('/');
    cy.matchImageSnapshot(`home-${vp.name}`);
  });
});

Each viewport gets its own baseline.

The cross-platform problem

The biggest pain point with pixel-diff tools is platform differences. A baseline taken on macOS will not match a screenshot taken on Linux due to font rendering, anti-aliasing, and subpixel positioning. Three strategies:

Strategy 1: Docker for consistency

Run Cypress in a fixed Docker container so all baselines and comparisons happen in the same environment.

- container:
    image: cypress/included:13.x
  run: npx cypress run

This is the most common solution.

Strategy 2: Per-OS baselines

Maintain separate baselines for each OS your team runs.

cypress/snapshots/
  linux/
    home.png
  darwin/
    home.png

Configure the snapshot path dynamically:

addMatchImageSnapshotCommand({
  customSnapshotsDir: `cypress/snapshots/${process.platform}`,
});

Strategy 3: Run only in CI

Only run visual tests in CI; never compare against local baselines.

Dynamic content

For pages with dynamic content (timestamps, randomized data), stabilize before snapshotting.

beforeEach(() => {
  cy.clock(new Date('2026-01-01T00:00:00Z'));
  cy.window().then((win) => {
    cy.stub(win.Math, 'random').returns(0.5);
  });
});

Or hide dynamic regions with CSS:

cy.get('[data-dynamic]').invoke('css', 'visibility', 'hidden');
cy.matchImageSnapshot('home');

Animations and transitions

Disable CSS animations for stable snapshots.

beforeEach(() => {
  cy.visit('/', {
    onBeforeLoad(win) {
      win.document.head.insertAdjacentHTML(
        'beforeend',
        '<style>*, *::before, *::after { transition: none !important; animation: none !important; }</style>'
      );
    },
  });
});

CI configuration

- container:
    image: cypress/included:13.x
  run: npx cypress run --env updateSnapshots=false
- uses: actions/upload-artifact@v4
  if: failure()
  with:
    name: snapshot-diffs
    path: cypress/snapshots/__diff_output__

The __diff_output__ directory contains the visual diffs from failed comparisons. Upload as an artifact for debugging.

Reviewing diffs

When a test fails, cypress-image-snapshot writes three files to __diff_output__:

  1. <test>.received.png - the new screenshot
  2. <test>.diff.png - the visual diff overlay
  3. <test>.snap.png - the expected baseline

Review them locally:

open cypress/snapshots/__diff_output__/home-diff.png

Baseline management

Baselines are committed to git. For large suites, this can bloat the repo. Strategies:

  1. Git LFS for snapshot PNGs. Keeps the repo size manageable.
  2. External baseline store. S3 with sync scripts.
  3. Snapshot per branch. Risky; baselines drift.

Most teams use Git LFS once snapshot count exceeds 100.

Best practices

  1. Run in Docker. Cross-platform consistency.
  2. Stabilize dynamic content. Clocks, random, animations.
  3. Per-test threshold tuning. Default for simple pages, looser for content-rich.
  4. Snapshot named states. "Default", "Hover", "Open".
  5. Review diffs carefully. A 4-pixel shift is sometimes a bug.
  6. Commit baselines deliberately. updateSnapshots=true is a manual step.
  7. Upload diffs as artifacts. Critical for CI debugging.
  8. Use Git LFS for large suites. Keeps repo size manageable.
  9. Document the visual contract. Which pages, which states.
  10. Pair with functional tests. Visual and functional both protect the product.

Gotchas

  1. Cross-OS pixel differences. Run in Docker.
  2. failureThreshold interpretation. pixel and percent types differ.
  3. capture: 'fullPage' is slow. Use viewport when possible.
  4. First run always passes. Creates the baseline.
  5. Git LFS in CI. Requires LFS install in the workflow.
  6. updateSnapshots updates all snapshots. No targeted regeneration.
  7. __diff_output__ is gitignored. Add it explicitly to your .gitignore if needed.
  8. Element snapshots include surrounding pixels. Box-shadow and outline may bleed.
  9. Anti-aliasing of fonts. customDiffConfig.threshold tunes tolerance.
  10. @simonsmith/cypress-image-snapshot is the active fork. The original is archived.

API quick reference

Use caseSnippet
Viewport snapshotcy.matchImageSnapshot('name')
Element snapshotcy.get('.x').matchImageSnapshot('x')
Update baselinecypress run --env updateSnapshots=true
Loose threshold{ failureThreshold: 0.05, failureThresholdType: 'percent' }
Full-page{ capture: 'fullPage' }
Per-OS path{ customSnapshotsDir: \snapshots/${process.platform}` }`

Comparison: image snapshot vs Percy vs Applitools

Dimensioncypress-image-snapshotPercyApplitools
CostFreePer-snapshotEnterprise pricing
Baseline storageRepoCloudCloud
Cross-browserNo (single browser)Yes (render farms)Yes (Ultrafast Grid)
Diff algorithmPixel-by-pixelPixel-by-pixelVisual AI
False positive rateHigh (cross-platform)LowLowest
Historical trendsNoYesYes
Setup complexityLowLowModerate
Air-gappedYesNoNo

Conclusion and next steps

cypress-image-snapshot is the right tool for budget-conscious, air-gapped, or compliance-constrained visual testing. It is not as sophisticated as Percy or Applitools, but with Docker for consistency and a sensible threshold, it provides real visual safety nets without per-snapshot fees.

Start with one snapshot of your home page. Get it green in CI inside Docker. Add a few critical pages. Tune thresholds based on the false-positive rate. Use Git LFS once you exceed 100 snapshots.

Next read: explore the QA Skills directory for visual testing skills, and the blog index for Percy, Applitools, and CI guides.

cypress-image-snapshot: Self-Managed Visual Testing for 2026 | QASkills.sh