ScreenshotNeo

BlogHow-to

How to Run Visual Regression Tests on a Next.js App with Cypress

Cypress captures screenshots, but a visual testing integration compares them with approved baselines. Build a stable Next.js workflow for local runs and CI.

By the ScreenshotNeo team4 October 20269 min read

Cypress can take screenshots of a Next.js app, but cy.screenshot() alone does not detect visual regressions. You need a visual testing integration that compares each new image with an approved baseline and lets your team review and accept intentional changes.

A dependable workflow has four parts: run the app in a consistent environment, drive it into a deliberate UI state, capture focused screenshots, and review diffs before updating baselines. Use E2E tests for routes and server-rendered flows; use Cypress Component Testing for supported individual components when a smaller, more controlled comparison is useful.

1. Set up Cypress in a Next.js project

Follow the current Next.js Cypress guide for the project’s router and package manager. For a manual setup with pnpm, install Cypress as a development dependency:

pnpm add -D cypress

Add scripts suited to your project. This example uses a production build and server for E2E tests:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "cypress:open": "cypress open",
    "cypress:run:e2e": "cypress run --e2e"
  }
}

Start Cypress once to create its configuration, then select E2E Testing, Component Testing, or both as appropriate:

pnpm exec cypress open

The examples below use Cypress E2E specs in cypress/e2e. Adjust file placement and configuration to match the choices made in the Cypress setup flow and the current project configuration.

2. Choose E2E or Component Testing

Test level Use it for Tradeoffs
E2E Routes, navigation, server-rendered pages, and user flows that need the running application. Exercises more of the real app, but requires starting the app and controlling data and environment.
Component Testing Individual supported components with controlled props and a small visual surface. Fast and focused, but server-dependent behavior may need a different approach.

Next.js recommends E2E for async Server Components because Cypress Component Testing does not support them. Cypress also notes that features requiring the Next.js server, such as next/image, may not work out of the box in Component Testing. See the Cypress React Component Testing overview and the Next.js guide for current compatibility details.

3. Add a visual comparison integration

Cypress’s documentation is explicit: “Cypress does not perform image comparison itself.” Its built-in screenshot command captures an image; an integration supplies baseline comparison and review. See Cypress visual testing and the Cypress screenshot guide.

Choose the comparison workflow before writing snapshot assertions. A local or open-source plugin can keep baselines in the repository and run pixel comparisons locally or in CI. Your team then owns baseline updates, environment consistency, and CI artifacts. A hosted service may provide managed rendering, baseline approval, cross-browser or viewport capture, and a review interface. Cypress lists integrations including Applitools Eyes, Argos, Chromatic, Happo, LambdaTest SmartUI, Percy, Sauce Labs Visual, SmartBear VisualTest, and Wopee.io; the list is a starting point, not a guarantee of current features or endorsement. Check each provider’s official documentation for current installation, compatibility, pricing, data handling, and review workflow before selecting it.

Whichever tool you choose, confirm how it stores baselines, what browsers and viewports it renders, how it handles pixel thresholds and ignored regions, how reviewers approve changes, and whether the same rendering environment can be used to generate and compare snapshots. The examples here use a placeholder visualMatch command: replace it with the command documented by your chosen integration. Cypress’s built-in screenshot command remains available for captures and debugging, but is not a substitute for that comparison command.

4. Write a stable visual E2E test

A visual snapshot should represent a meaningful, repeatable state. Navigate to the route, control changing network data, wait for an assertion that proves rendering is complete, then compare a focused element or the full page.

// cypress/e2e/pricing.cy.js

describe('pricing page visual states', () => {
  beforeEach(() => {
    cy.clock(new Date('2026-01-15T12:00:00Z'));

    cy.intercept('GET', '/api/plans', {
      fixture: 'plans.json'
    }).as('plans');

    cy.visit('/pricing');
    cy.wait('@plans');
    cy.findByRole('heading', { name: /pricing/i }).should('be.visible');
    cy.findByTestId('plan-card-pro').should('be.visible');
  });

  it('matches the approved pricing card appearance', () => {
    // Replace with the assertion supplied by the selected visual integration.
    cy.get('[data-testid="plan-card-pro"]').visualMatch('pricing-pro-card');
  });
});

The sample uses a fixture named plans.json and a testing-library query for readability. Use selectors and fixture setup already supported by your project; if you use an integration with a different assertion API, change only the comparison call to its documented syntax. Keep the heading and data assertions: they distinguish an incomplete load from a genuine visual change.

For a full-page comparison, use the integration’s full-page option if it has one. Cypress also has cy.screenshot() for capturing screenshots, including options documented in the screenshot command reference. A raw screenshot is useful for artifacts or diagnosis; it does not itself fail when pixels differ from a baseline.

5. Prevent flaky visual diffs

  • Fix the viewport. Set the same width and height for baseline and comparison runs. Use separate snapshots for materially different responsive layouts.
  • Control data. Intercept changing API responses with fixtures or deterministic stubs. Wait for the relevant request and assert the rendered state before capture.
  • Control time. Freeze the clock for dates, countdowns, or time-sensitive greetings. Use a deliberate fixed date and timezone where the application supports it.
  • Stop motion. Disable transitions and animations in a test-only stylesheet or wait for the particular motion to finish. Cypress’s waitForAnimations and animationDistanceThreshold settings apply to action commands; they do not guarantee that an unrelated animation is settled when a screenshot is captured.
  • Match the rendering environment. Generate and compare baselines using the same pinned CI image, browser version, operating system, display scale, and fonts where possible. Differences in these can change pixels without a code change.
  • Mask sparingly. If an uncontrolled ad or third-party widget cannot be stabilized, mask only its small region when the chosen integration supports masking. A broad page-wide threshold can hide meaningful regressions.
  • Choose useful capture boundaries. Prefer an element snapshot when a component has a clear owner. Use full-page capture for layout concerns such as page structure, spacing, and overflow.

Do not add a visual assertion to every test. Cover the key pages, shared components, and user-visible states where a screenshot diff is actionable. Cypress’s visual testing guidance discusses stable state, viewport, environment, and snapshot scope.

6. Run the tests in CI

For E2E coverage, a production build and server provide a production-like target. Install a server orchestration utility as a development dependency if the project does not already have one:

pnpm add -D start-server-and-test

Then add a script to start the server, wait for its URL, and run Cypress headlessly:

{
  "scripts": {
    "build": "next build",
    "start": "next start",
    "test:visual:e2e": "start-server-and-test start http://localhost:3000 \"cypress run --e2e\""
  }
}

Run the build first, then the visual E2E script:

pnpm build
pnpm test:visual:e2e

The Next.js guide documents the start-server-and-test pattern and also shows running Cypress against a development server. Development-server testing can be convenient, but choose one mode for baseline generation and comparison and keep that mode consistent. In CI, use cypress run for headless execution. With a local comparison plugin, retain screenshots and diffs as CI artifacts so a reviewer can inspect failures. Hosted services may have their own CI command and baseline approval process; follow the provider’s current official setup instructions.

7. Review and update baselines deliberately

  1. When a comparison fails, inspect the actual image and diff artifact alongside the code change.
  2. Decide whether the visual change is intended and whether it affects the user-visible result.
  3. If it is a bug, fix the app and rerun the comparison against the existing baseline.
  4. If it is intentional, update the baseline through the selected tool’s documented review workflow and include that change with the code review.
  5. Keep the baseline environment and snapshot naming stable so later diffs remain attributable.

Accepting every new image automatically removes the regression signal. Baseline updates should be a reviewable change, especially when shared styles or layout primitives are involved.

Common errors and fixes

Symptom Likely cause Fix
No diff failure even though a screenshot was captured The test only called cy.screenshot(); Cypress does not compare that image to a baseline. Install and configure a visual comparison integration, then use its documented comparison assertion.
Page is blank or incomplete in CI The app was not started, the server was not ready, or the test captured before data rendered. Build and start the app, wait for the URL and relevant network request, then assert on visible content.
Diffs change between local and CI Browser, OS, fonts, display scaling, or viewport differs. Generate and compare in the same pinned environment with an explicit viewport.
Intermittent differences in animated areas A transition or animation was still running at capture time. Disable motion in the test environment or wait for the specific animation to complete; action-command animation settings alone do not settle screenshots.
Dates or content differ on each run Live API data, current time, or timezone changes output. Stub network responses, freeze time, and set a consistent timezone.
Component test cannot render a server-dependent feature The component depends on Next.js server behavior; some features do not work out of the box in Component Testing. Use E2E against the running Next.js app for that behavior, or isolate the component behind a supported test boundary.
Snapshots are noisy or hard to review Captures cover too much unstable UI or are taken at arbitrary states. Capture fewer meaningful states, use element-level comparisons where appropriate, and mask only small uncontrollable regions.
CI baseline updates overwrite useful history Baselines are accepted without reviewing the diff. Require inspection and code review for intentional baseline changes; retain CI artifacts.

Performance, reliability, and cost

Visual tests add browser rendering and image comparison work to a suite, and E2E tests also need an app server. Keep the suite focused on high-value pages and components, use component-level tests for supported isolated surfaces, and reserve full-page comparisons for layout-level checks. Parallelism and hosted rendering can change runtime and cost, but exact limits and prices depend on the selected integration; check its current official terms rather than assuming them.

Reliability comes chiefly from controlling what the browser renders and keeping baseline generation aligned with CI. A passing image comparison means the selected pixels stayed within the integration’s configured rules; it does not prove that every interaction works or that the page is accessible. Pair visual checks with functional assertions and the rest of the project’s quality checks.

Or skip the browser setup

If you need a screenshot of a live page without configuring a browser capture pipeline, ScreenshotNeo provides a website screenshot API and MCP server. Its one-call capture is separate from Cypress baseline comparison: use it to obtain a clean image, and keep an approved-baseline integration for regression assertions.

See the ScreenshotNeo API documentation for options and response details.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use screenshot tools, and the free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo free: 1,000 screenshots a month, no card required.

FAQ

Can Cypress run visual checks without opening a browser window?

Yes. Cypress runs headlessly with cypress run; the app still needs to be available for E2E tests, and the comparison integration must support the chosen CI workflow.

Should every route have a full-page baseline?

No. Add comparisons where a visual change would be useful to review. A focused element can be clearer for component ownership; full-page images are useful when the page layout itself is the concern.

Does a passing visual test replace functional tests?

No. It checks rendered pixels within the integration’s comparison rules. Keep assertions for behavior, data, and accessibility alongside visual coverage.

Can I compare screenshots captured on different machines?

You can, but environmental rendering differences may create noise. For dependable diffs, generate and compare in the same pinned browser and operating-system environment.