Cypress Screenshot Testing with Percy: Setup Guide
Set up Percy visual testing in Cypress, run it in CI, and troubleshoot unstable snapshots, missing tokens, and baseline changes.
To add Percy visual testing to Cypress, install @percy/cli and @percy/cypress, import the Cypress integration from your support file, add cy.percySnapshot() after the page reaches a stable state, set your Percy project token as PERCY_TOKEN, and run Cypress through npx percy exec -- cypress run. Cypress can capture screenshots on its own; Percy adds visual comparison and a hosted review workflow for the snapshots you choose.
1. Install Cypress and Percy
If the project already has Cypress, keep its installed version and package manager. Otherwise, install Cypress using the package manager used by the project; Cypress documents installation with npm, Yarn, pnpm, and Bun.
npm install --save-dev cypress
npm install --save-dev @percy/cli @percy/cypress
The Percy setup walkthrough uses these package names and commands. Check the current Percy documentation and the versions recorded in your lockfile before copying the setup into a project: SDK syntax and supported versions can change.
2. Register the Percy Cypress command
Import the integration in the Cypress support file that is configured for your test type. For many end-to-end projects, that file is cypress/support/e2e.js; component testing may use a different support file. Keep the import in the support file that Cypress actually loads.
// cypress/support/e2e.js
import '@percy/cypress';
If your project uses CommonJS support files, use the module syntax supported by its Cypress configuration and Percy SDK version. Confirm that Cypress loads the support file before debugging a test that reports percySnapshot is not a function.
3. Add snapshots at useful checkpoints
Call cy.percySnapshot('Descriptive state name') after assertions show that the intended page or component state is ready. Use stable, meaningful names, such as a page name and state, so reviewers can identify snapshots in the Percy review workflow.
// cypress/e2e/pricing.cy.js
describe('pricing page', () => {
it('shows the annual pricing state', () => {
cy.intercept('GET', '/api/plans', { fixture: 'plans.json' });
cy.visit('/pricing');
cy.get('[data-cy="pricing-table"]').should('be.visible');
cy.contains('Annual billing').click();
cy.get('[data-cy="billing-period"]').should('contain', 'Annual');
cy.percySnapshot('Pricing page - annual billing');
});
});
Replace the route, selectors, and fixture with those from your application. The example stubs a variable API response so the captured content can remain consistent between runs. A snapshot after an assertion is generally more reliable than one after an arbitrary delay.
Choose the right snapshot scope
- Use a page snapshot when page layout and the relationship between sections are under test.
- Use a focused component or element snapshot when unrelated page content would make review noisy, if supported by the installed integration version.
- Capture a small set of important states, such as a default view and a state reached after a key interaction. Avoid snapshots at every incidental step.
- Give each state a descriptive name, and avoid names that change on every run.
Consult the API documentation for your installed Percy Cypress SDK version for supported snapshot options. The setup guide establishes the basic command, but should not be treated as a complete versioned reference for optional arguments.
4. Configure the Percy token
Create a Percy project and provide its project token through the PERCY_TOKEN environment variable. Keep the value in your local environment or CI secret store; do not commit it to test source or a checked-in configuration file.
# macOS or Linux shell
export PERCY_TOKEN="your-project-token"
npx percy exec -- cypress run
In CI, add PERCY_TOKEN as a protected secret or environment variable using that platform’s secret settings. Make it available to the job that runs Percy, and avoid printing the environment in logs.
5. Run Percy with Cypress
Run the test command under Percy’s CLI so the Cypress integration can send snapshots into Percy’s workflow:
npx percy exec -- cypress run
For a project with a package script, use the script after the separator:
npx percy exec -- npm run e2e
The application server must be running and responding before Cypress starts. Starting a server in the background and immediately launching the test command can create a race: Cypress may visit the app before it is ready. Use the project’s existing server-ready mechanism or CI orchestration to wait until the app responds, then run Percy.
6. Review and approve visual changes
After the run, review the snapshots and visual changes in Percy’s dashboard. A difference is a review prompt: check whether the UI change is intended before accepting an updated baseline. If a change is unexpected, investigate rendering, test data, timing, fonts, viewport, and browser conditions before changing the baseline.
Keep snapshots stable and useful
| Source of variation | What to do |
|---|---|
| API or fixture data | Use cy.intercept() with controlled fixtures where appropriate, and make sure the page has consumed the response before snapshotting. |
| Page readiness | Wait for a meaningful selector or assert the rendered state. Avoid relying on fixed delays when a functional condition is available. |
| Animations | Capture after relevant transitions finish. Cypress screenshot options can control timers and CSS animations in some cases, but interaction-specific animation settings do not guarantee that unrelated animations elsewhere have stopped. |
| Fonts and assets | Wait for the page’s relevant content and assets to render; check whether a font or image loads inconsistently across runs. |
| Changing content | Mask or otherwise stabilize dynamic regions only where needed. Keep masking narrow so real regressions remain visible. |
| Browser and viewport | Keep the Cypress browser, viewport, and rendering conditions consistent between baseline and comparison runs. |
| Snapshot scope | Prefer a focused snapshot to reduce unrelated visual changes, unless page-level layout is the behavior being checked. |
What is the difference between cy.screenshot() and cy.percySnapshot()?
cy.screenshot() captures a Cypress screenshot, commonly saved with test output. Cypress also captures screenshots for test failures during cypress run by default. That is useful for inspecting test execution, but local screenshot capture alone is not Percy’s visual baseline comparison and review workflow.
cy.percySnapshot() marks a state for Percy’s visual testing workflow. You run Cypress through Percy’s CLI, and Percy provides a hosted place to compare and review visual changes. Cypress’s own screenshot capability and Percy snapshots serve related but distinct purposes. See the official [Cypress screenshots documentation](https://docs.cypress.io/app/guides/screenshots-and-videos) and [Percy Cypress setup walkthrough](https://www.browserstack.com/docs/percy/integrate/cypress).
CI, reliability, and operating cost
- Make startup deterministic. Start the application and wait for it to respond before invoking Cypress. A background process alone does not prove the app is ready.
- Protect credentials. Supply the project token as a CI secret. If the token is missing or unavailable to the job, the Percy run cannot authenticate as configured.
- Keep rendering conditions consistent. Browser, viewport, data, and timing changes can create diffs unrelated to a code regression.
- Control snapshot volume. Every snapshot adds review surface. Choose checkpoints that cover meaningful user-visible states.
- Plan for review. A visual diff needs a human decision about whether the change is intended; do not automatically approve changed baselines without review.
- Account for the extra service workflow. Percy adds packages, a token, a CLI invocation, and an external dashboard to Cypress’s local screenshot workflow. Check Percy’s current plan and pricing information directly; the setup references here do not establish current prices.
Or skip the browser setup
If your goal is to capture a webpage image outside a Cypress test, ScreenshotNeo is a website screenshot API and MCP server. Its API returns an image or PDF from one GET request. See the ScreenshotNeo API documentation.
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}`);
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers identify the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for AI agents and MCP clients. - The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. All features are on every plan.
ScreenshotNeo captures web pages; it does not replace Cypress assertions or Percy’s visual baseline review. Sign up for 1,000 free screenshots a month, with no card required.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
cy.percySnapshot is undefined |
The support file did not import @percy/cypress, Cypress is using a different support-file path, or the integration package is missing. |
Install the SDK, verify the configured support file for the test type, and add the import there. Confirm Cypress loads that file. |
| Percy reports a missing token or authentication failure | PERCY_TOKEN is unset, misspelled, or unavailable in the CI job. |
Set the project token in the local shell or CI secret settings and make it available to the Percy command. Keep it out of source code. |
| Cypress cannot reach the application | The server has not started or is not responding when Cypress begins. | Start the server and wait for a successful response before running npx percy exec -- cypress run. |
| Snapshots differ on every run | Data, page readiness, animation, fonts, viewport, or browser rendering varies. | Stub changing API responses, wait for a meaningful ready condition, stabilize the rendering setup, and narrowly mask unavoidable dynamic regions. |
| A snapshot is blank or incomplete | The snapshot happened before the target content rendered, or the expected state was never reached. | Add an assertion for the relevant content and interaction state immediately before the snapshot; check failed network requests and selectors. |
| Too many unrelated visual changes appear | The snapshot includes dynamic or unrelated regions, or its scope is wider than the behavior under test. | Choose a more focused snapshot where supported, stabilize or narrowly mask volatile content, and keep full-page capture for page-level checks. |
| Local Cypress screenshots exist, but no Percy review appears | cy.screenshot() captures locally; the Percy command and snapshot integration were not used for that run. |
Add cy.percySnapshot() at the intended checkpoint and run the suite through npx percy exec -- cypress run with the token configured. |
FAQ
Does Percy replace Cypress?
No. Cypress runs the browser tests and checks application behavior. Percy adds visual comparison and review for selected snapshots.
Do I need Percy for Cypress to take screenshots?
No. Cypress has built-in screenshot support, including automatic screenshots for failed tests in cypress run. Percy is for the hosted visual comparison workflow.
Should every test include a Percy snapshot?
No. Add snapshots to representative, valuable UI states where a visual change should be reviewed.
Can I use ScreenshotNeo to run Percy visual regression tests?
No. ScreenshotNeo is a website screenshot API and MCP server, while this Percy setup covers visual snapshots in a Cypress test workflow. Use ScreenshotNeo when you need direct webpage captures or agent-accessible screenshot tools.


