How to Add Visual Testing to Cypress Component Tests
Add visual regression checks to Cypress component tests: choose a comparison tool, capture stable component states, and keep snapshots reliable.
A Cypress component test can verify that a button works while missing that its color, spacing, font, or icon has changed. To catch those regressions, mount the component in a known state, assert that the intended state is visible, then use a Cypress-compatible visual testing integration to capture it and compare it with an approved baseline. Cypress provides screenshot capture, but it does not compare screenshots by itself. Cypress documents the visual testing workflow and integrations.
Component tests are a useful place for focused visual checks: they render a component without visiting the whole application, and controlled props and fixtures make the source of a visual difference easier to narrow down. They complement functional and end-to-end tests; they do not prove that every application layer works together. See the Cypress component testing guide.
1. Choose how snapshots will be compared
First choose a Cypress-compatible plugin or hosted service. Cypress’s cy.screenshot() produces an image; it does not establish a visual assertion or compare that image to an approved baseline. The comparison tool supplies that behavior, and each integration has its own installation steps, commands, configuration, and baseline workflow.
| Approach | What your team handles | What to weigh |
|---|---|---|
| Open-source, self-managed plugin | Baseline files, comparison runs, diff review, and baseline updates in your environment | Local control and infrastructure ownership; rendering consistency is your responsibility |
| Hosted visual testing service | Service-managed baseline storage and review workflow, according to the provider | Subscription and service-managed infrastructure; check browser coverage, review flow, CI integration, and terms |
The Cypress visual testing guide lists commercial integrations including Applitools Eyes, Argos, Chromatic, Happo, LambdaTest SmartUI, Percy, Sauce Labs Visual, SmartBear VisualTest, and Wopee.io. For self-managed comparison, it lists Cypress Image Diff, Cypress Image Snapshot, Cypress Visual Regression, and Visual Regression Diff, and also describes Pixeleye as a self-hostable review platform. These are options to evaluate, not a universal ranking: check each provider’s current Cypress compatibility, setup instructions, pricing, security terms, and supported browsers before adopting it. See the current Cypress visual testing guide and plugin catalog.
2. Mount a deterministic component state
The example below uses React, TypeScript, and a generic visual command called cy.visualCompare(). That command is deliberately a placeholder: Cypress does not supply it. Replace it with the command and setup documented by the comparison integration you selected. The component test structure—mount, establish state, assert readiness, compare—is the reusable part.
Assume the application has a TodoItem component that displays a completed task. Adjust imports and props to match your component and Cypress component-test setup. Cypress’s React examples show mounting components and passing props.
// cypress/component/todo-item.cy.tsx
import React from 'react'
import { TodoItem } from '../../src/TodoItem'
// Replace this declaration with the command type and registration
// required by your visual testing integration.
declare global {
namespace Cypress {
interface Chainable {
visualCompare(name: string): Chainable<void>
}
}
}
describe('TodoItem visual states', () => {
it('matches the approved completed state', () => {
cy.mount(
<TodoItem
id="task-1"
title="Write component tests"
completed={true}
/>,
)
// Confirm the exact state before capturing it.
cy.get('[data-cy="todo-item"]')
.should('be.visible')
.and('have.class', 'completed')
cy.contains('Write component tests').should('be.visible')
// Provider-specific: replace with the selected integration's command.
cy.visualCompare('todo-item-completed')
})
})
For a component with an interactive state, mount its default state, use Cypress commands to reach the target state, and assert the result before the visual comparison:
it('matches the expanded panel', () => {
cy.mount(<DetailsPanel title="Shipping" />)
cy.findByRole('button', { name: 'Shipping' }).click()
cy.findByRole('region', { name: 'Shipping' }).should('be.visible')
// Replace with the selected integration's actual snapshot command.
cy.visualCompare('shipping-panel-expanded')
})
Use meaningful names that remain stable across runs. If your integration uses a snapshot API with options, element targeting, or a baseline update flag, follow its official documentation rather than assuming the placeholder accepts those options.
3. Configure styles and providers for component mounting
A snapshot is only useful if the test renders the same styles and state that you intend to protect. Cypress’s component test setup is framework-specific. Import the application’s global stylesheet and any required theme or font setup from the component support file (commonly cypress/support/component.ts or .js), and configure the framework’s provider wrappers there or in a custom mount command.
// cypress/support/component.ts
import '../../src/styles.css'
import { mount } from 'cypress/react'
Cypress.Commands.add('mount', (component, options = {}) => {
return mount(component, {
...options,
// Wrap with the same theme, router, or context providers
// required for the component to render correctly.
})
})
This is a sketch of a custom mount hook; retain the mount implementation generated for your Cypress version and framework, and add the providers your app actually uses. Do not copy a provider-free sketch if the component depends on theme, localization, routing, or application context. See Cypress’s guide to styling components.
4. Establish and review baselines
- Install and configure the chosen integration using its official current Cypress setup guide. Confirm its supported Cypress version and whether it supports component tests.
- Run the focused component spec in the intended browser and viewport. The first capture may create a candidate baseline, depending on the tool.
- Review the candidate image before accepting it. Check that fonts, styles, fixture data, and the component state are correct; an incorrectly rendered first capture becomes a misleading reference.
- Commit or approve the baseline using the integration’s documented workflow. Keep the baseline review tied to the code change that explains the intended appearance.
- On later runs, inspect the expected image, current image, and diff when the comparison fails. Fix unintended UI changes; approve a replacement baseline only when the visual change is intentional.
Keep ordinary Cypress failure screenshots distinct from visual assertions. A screenshot attached to a failed functional test can help diagnose the failure, but by itself it does not compare a state against an approved visual baseline.
5. Keep visual component tests stable
- Assert readiness first. Cypress retries assertions, but a snapshot still records the rendered state at capture time. Assert that the intended text, class, selected state, or panel is visible before calling the visual command.
- Control network data. Stub changing API responses with
cy.intercept()and fixtures. Wait for the aliased request and assert the rendered result before snapshotting. - Control time. Use
cy.clock()when the component displays dates, relative time, countdowns, or time-dependent states. Supply stable locale and timezone settings if the test environment or application supports them. - Set a fixed viewport and browser. Use a consistent viewport for a given baseline. Where possible, pin browser and operating-system versions in local and CI runs. Font availability, browser rendering, and display scaling can change pixels.
- Remove motion at the source where practical. Disable or finish component transitions and animations for the visual test state. Cypress action-command animation settings do not stop unrelated CSS animations elsewhere from changing during a capture.
- Mask narrowly. If an uncontrollable third-party widget remains, use the integration’s masking or ignore-region support for that small area. A broad tolerance can hide real regressions across the component.
- Choose the right capture area. Prefer an element snapshot when the component’s appearance is the contract. Use a page-level capture when the layout among multiple components is what the test must protect.
- Pair image checks with accessibility checks. Image comparison cannot determine whether text meets a defined contrast standard or whether a control has an accessible name. Keep those assertions in accessibility and functional checks.
Example with stable fixture data (the final comparison call remains provider-specific):
it('matches the loaded results state', () => {
cy.intercept('GET', '/api/items', { fixture: 'items.json' }).as('items')
cy.mount(<ItemList />)
cy.wait('@items')
cy.findByRole('list', { name: 'Items' }).should('be.visible')
cy.contains('First fixture item').should('be.visible')
cy.visualCompare('item-list-loaded')
})
6. Troubleshoot common failures
| Symptom | Likely cause | What to do |
|---|---|---|
| The command is unknown or undefined | The selected integration’s command was not registered, its support file was not loaded, or the example placeholder was copied as if it were built into Cypress. | Check the provider’s installation steps, import its support module in the component support entry point, and use its actual command name. |
| The first baseline looks blank or incomplete | The snapshot ran before the component, styles, data, or fonts finished loading. | Check the component setup and asset requests; assert the target state and wait for required data before capture. |
| Diffs appear on every run | Uncontrolled time or data, motion, varying fonts, browser/OS changes, or an unstable third-party region. | Stabilize fixtures and clock, disable the relevant animation, standardize the render environment, and mask only the smallest uncontrollable region. |
| Many unrelated specs fail after a small UI edit | Snapshots cover a broad page or duplicate the same shared component in many incidental states. | Keep deliberate checkpoints, use element-level captures where suitable, and place shared visual contracts with the tests that own them. |
| A test passes locally but fails in CI | Local and CI rendering environments differ, or CI lacks an asset such as a font. | Compare browser, OS, viewport, device scale, font installation, and fixture setup; use the provider’s documented CI environment. |
| A diff is reported after an intentional redesign | The baseline still represents the prior design. | Review the before/after images and approve the new baseline through the normal code-review workflow. |
| A full-page or element capture has unexpected dimensions | The tool’s capture target, component overflow, viewport, or scaling differs from the intended visual contract. | Check whether the integration captures the viewport, full page, or selected element, and set a fixed viewport and target according to its docs. |
7. Performance, reliability, and cost
Visual checks add image capture and comparison work to a test run, and hosted services may add upload, rendering, review, and subscription costs. The exact time and price depend on the selected integration and configuration; the Cypress documentation does not establish one universal benchmark or cost. Start with a small number of meaningful component states, then expand when the review value justifies the extra snapshots.
Self-managed plugins avoid a hosted review dependency but require the team to maintain baselines, comparison behavior, CI artifacts, and consistent rendering environments. Hosted tools can centralize baselines and reviews and may offer cross-browser or viewport rendering, but verify the current plan limits, data handling, browser matrix, and service availability directly with each vendor. Component-level snapshots can keep diffs focused and reduce the amount of unrelated UI that must be reviewed.
Or skip the browser setup
For a standalone website screenshot, ScreenshotNeo provides a screenshot API and MCP server. It is not a Cypress visual-baseline comparator: use your Cypress-compatible integration for automated baseline assertions, and use ScreenshotNeo when you want to capture a URL without setting up browser automation in that workflow. Its API options include PNG, JPEG, or WebP capture, full-page capture, element selection, custom viewport, and PDF output. See the ScreenshotNeo API documentation.
// cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
# Python
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)
// Node.js
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}`)
await Bun.write('shot.webp', res) // In Node.js, save res.arrayBuffer() with your preferred filesystem API.
For Node.js, here is a complete save-to-file version using the built-in filesystem module:
import { writeFile } from 'node:fs/promises'
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}`)
await writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before the capture; those cleanup steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000, and yearly billing gives two months free. Sign up for ScreenshotNeo’s free plan.
FAQ
Does Cypress compare screenshots automatically?
No. Cypress captures screenshots, while a visual testing integration supplies baseline comparison and review behavior. Cypress states: “Cypress does not perform image comparison itself.” See its visual testing guide.
Should every component test have a visual snapshot?
No. Add snapshots for shared components and meaningful states whose appearance matters. Snapshotting every incidental state creates more review work without necessarily protecting a distinct visual contract.
Can a visual snapshot replace accessibility or functional assertions?
No. A pixel comparison checks rendered appearance against an image; it does not establish behavior or evaluate accessibility requirements such as text contrast.
Can I use ScreenshotNeo as the Cypress baseline tool?
ScreenshotNeo’s documented product facts here describe URL screenshot capture and an MCP server, not a Cypress baseline comparison integration. Use a Cypress-compatible visual testing tool for baseline assertions.


