ScreenshotNeo

BlogHow-to

How to Automate Visual Testing with Percy

Add Percy snapshots to Playwright or Cypress, run them in CI, and review visual changes against an approved baseline.

By the ScreenshotNeo team4 October 20268 min read

Percy automates visual checks by taking snapshots of meaningful application states from your existing test suite, comparing them with an approved baseline, and presenting visual differences for review. Add the Percy SDK for your test framework, set the project token in the run environment, and launch the tests through percy exec. Keep your functional assertions: Percy complements them by checking appearance.

How the Percy workflow fits together

  1. Choose an integration. Start with your app type and test framework, then confirm current SDK support on Percy’s integrations page. Percy documents integrations for web and native apps, static sites, styleguides, and component libraries, as well as CI/CD and review workflows.
  2. Install the CLI and framework SDK. The CLI wraps the test command; the SDK adds snapshot calls to your tests.
  3. Capture stable UI states. Place snapshots after navigation, interactions, and relevant asynchronous rendering have completed. Give each snapshot a descriptive, consistent name.
  4. Provide the project token. Set PERCY_TOKEN in the environment used by the local or CI process. Treat it as a credential and keep it out of source control.
  5. Run tests through Percy. The CLI process receives snapshots from the SDK and finalizes a Percy build.
  6. Review the build. Compare the result with the reference baseline. Approve expected design changes; investigate unexpected differences and fix the UI before updating the baseline.

The first run establishes a reference that must be reviewed and approved. Later runs compare snapshots with that approved expectation. A detected difference is a prompt for review, not automatically a defect.

Playwright setup

1. Install Percy packages

npm install --save-dev @percy/cli @percy/playwright

2. Add a snapshot to a test

Call percySnapshot when the page is in the state you want to check. For example, add this to a Playwright test file:

import { test, expect } from '@playwright/test';
import { percySnapshot } from '@percy/playwright';

test('home page visual state', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page.getByRole('heading', { name: 'Example Domain' })).toBeVisible();
  await percySnapshot(page, 'Home page');
});

The navigation and assertion make the intended state explicit. For an interactive state, perform the action and wait for the expected result before taking the snapshot:

await page.getByRole('button', { name: 'Open menu' }).click();
await expect(page.getByRole('navigation')).toBeVisible();
await percySnapshot(page, 'Home page — menu open');

Use names that remain recognizable across runs, such as a page plus state. If a snapshot name changes accidentally, reviewers may have trouble matching it to the prior state.

3. Set the token and run the suite

In a shell, supply the Percy project token through the environment, then wrap the normal Playwright command:

export PERCY_TOKEN='YOUR_PERCY_PROJECT_TOKEN'
npx percy exec -- npx playwright test

In CI, store the token in the CI provider’s secret settings and expose it to the job that runs Percy. Do not commit a real token in a test file, package script, or repository configuration.

These package names, snapshot call, and command are documented in the Percy Playwright SDK repository. Check the current instructions there when implementing because package guidance can change.

Cypress setup

1. Install Percy packages

npm install --save-dev @percy/cli @percy/cypress

2. Load the SDK and add snapshot points

Import the Cypress integration in your Cypress support file:

import '@percy/cypress';

Then add cy.percySnapshot() after the test has reached the intended state:

describe('home page visual state', () => {
  it('captures the home page', () => {
    cy.visit('https://example.com');
    cy.findByRole('heading', { name: 'Example Domain' }).should('be.visible');
    cy.percySnapshot('Home page');
  });
});

The example uses Cypress Testing Library’s findByRole command. If that command is not installed in your project, use an assertion supported by your existing Cypress setup, such as cy.get('h1').should('be.visible').

3. Set the token and run Cypress

export PERCY_TOKEN='YOUR_PERCY_PROJECT_TOKEN'
npx percy exec -- cypress run

Set the token in your CI secret/environment settings for CI runs as well. Percy’s documented Cypress flow uses the SDK snapshot call and CLI wrapper; see the Percy Cypress SDK repository and Percy’s Cypress guide for current setup details.

Run Percy in CI and review changes

  1. Choose the Percy project for the application and make its token available as a protected CI secret.
  2. Run the same test command used locally, wrapped with percy exec. For example, use npx percy exec -- npx playwright test or npx percy exec -- cypress run.
  3. Make the CI job run after the app and any required test services are ready. Ensure the tests reach the same meaningful states on each run.
  4. Open the Percy build associated with the run and review each visual difference. Approve changes that match an intentional UI update; otherwise fix the regression and rerun.
  5. Choose a review and notification path that fits the team. Percy describes CI/CD and pull or merge request workflows, along with Slack notifications and webhooks on its integrations page.

Do not approve all diffs automatically just to make a build green. A baseline is the team’s reviewed expectation, so broad approval can hide an unintended change.

Snapshot placement and stability

  • Wait for meaningful readiness. A page load event alone may not mean that client rendering, fonts, images, or data-driven UI are settled. Wait for a user-visible condition that matters to the test before capturing.
  • Control asynchronous content. If a widget or data request changes the page during capture, make the test wait for the intended result or use a deterministic fixture supported by your test setup.
  • Capture states users see. Include important pages and interaction states, such as an opened menu or a completed form state, rather than taking snapshots at arbitrary points.
  • Keep names stable and descriptive. Consistent names make corresponding states easier to find in review.
  • Keep functional checks. A screenshot comparison does not replace assertions that links, forms, or application behavior work.

Common errors and fixes

Symptom Likely cause What to do
No Percy build or snapshots appear The SDK is not running under the Percy CLI process, or the token is missing from that process environment. Run the test command under percy exec, verify PERCY_TOKEN is available to that shell or CI job, and check that the SDK is installed and imported.
Token or authorization error The token is absent, invalid, or belongs to a different Percy project. Set the correct project token in the environment or CI secret settings. Avoid printing the token in logs while diagnosing.
Snapshot is blank or captures the wrong state The snapshot call runs before navigation, a user interaction, or asynchronous rendering has completed. Wait for a visible state with the framework’s normal assertions, then place the snapshot call after it.
Unexpected changes appear on every run Dynamic content or timing differences alter the captured page. Identify the changing region, make test data and state deterministic where practical, and wait for the UI condition that defines readiness.
Snapshot command is undefined in Cypress The Percy Cypress integration was not loaded by the support file, or the package install is incomplete. Confirm @percy/cypress is installed and imported from the configured support file; consult the current SDK setup instructions.
Snapshot function cannot be imported in Playwright The SDK dependency or import does not match the installed package instructions. Install @percy/playwright, check the current repository examples, and use the documented import for that SDK version.
Local run works, CI run does not The CI job may lack the token, run a different test command, or start before the app is ready. Compare local and CI commands, set the CI secret for the Percy step, and ensure app startup and test readiness are handled before snapshots.

Performance, reliability, and cost considerations

Percy adds snapshot processing and build review to the existing test workflow. The research sources do not establish a general runtime overhead or price, so evaluate those against your own suite and current Percy plan rather than relying on an assumed benchmark. Keep snapshots focused on states that provide useful regression coverage, and use the team’s normal CI workflow to decide when they run.

Reliability depends on repeatable test state, an available token, stable snapshot names, and a deliberate baseline review. A visual difference can be caused by a real UI change or by unstable page content; investigate before approving. Review current SDK compatibility and service details in the official integration documentation and Percy product information.

Or skip the browser setup

If your goal is to capture website screenshots for audits, reports, or an AI workflow rather than add Percy snapshots to a test suite, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Its API accepts common screenshot parameter names, which can make switching straightforward. See the ScreenshotNeo API documentation for options.

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}`);
await Bun.write('shot.webp', res);

Before capture, ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo’s free 1,000 screenshots a month with no card.

FAQ

Does Percy replace functional tests?

No. Keep functional assertions for behavior and use Percy snapshots to review visual appearance.

Do snapshots need to be added to every test?

No. Add them to the user-visible pages and states where appearance regressions matter to your team.

Should every visual difference update the baseline?

No. Review the change first. Approve the baseline when the UI change is intentional and correct.

Can teams use Percy with frameworks beyond Playwright and Cypress?

Percy lists multiple app and framework integrations. Check its current integration directory for the SDK that matches your project.