How to set up Percy with Playwright
Connect Percy to a Playwright suite with either the toHaveScreenshot() drop-in or explicit snapshots, then run builds safely in CI.
Percy integrates with Playwright through its CLI and Playwright package. If your tests already use Playwright’s toHaveScreenshot(), install the Percy packages and load its drop-in integration. If you want to choose capture points yourself, call percySnapshot() in your test. In both cases, set a Percy project token and run the test command through percy exec.
This guide covers both JavaScript and TypeScript projects, local runs, CI secrets, configuration, review, troubleshooting, and an alternative for taking website screenshots without setting up a browser test suite.
1. Choose an integration route
| Route | Choose it when | Test changes |
|---|---|---|
toHaveScreenshot() drop-in |
Your tests already use Playwright screenshot assertions and you want Percy to collect those captures. | Add one import to the Playwright configuration. |
Explicit percySnapshot() |
You want to decide exactly where Percy snapshots happen or are writing a capture script. | Call the Percy SDK at selected points in the test. |
Percy also provides a sample project if you want to see a Percy build before connecting an existing suite. The basic setup below does not require BrowserStack-hosted browser execution; follow the optional hosted-browser path only if your project needs it.
2. Prerequisites
- A JavaScript or TypeScript project with Playwright installed. The examples use
@playwright/test. - A Percy project and its project token. Treat the token as a secret; it is project-specific.
- For the documented
toHaveScreenshot()drop-in, the reference lists Node.js 18 or later,@playwright/test1.60 or later,@percy/cli1.32.6 or later, and@percy/playwright1.1.2 or later. These minimums can change, so check the current Percy Playwright reference before upgrading or publishing a locked setup.
3. Route A: collect existing toHaveScreenshot() assertions
Install Percy
npm install --save-dev @percy/cli @percy/playwright
Load the drop-in integration
Add the import before the Playwright configuration is loaded. For a CommonJS configuration such as playwright.config.js:
require('@percy/playwright/dropin');
const { defineConfig } = require('@playwright/test');
module.exports = defineConfig({
testDir: './tests',
use: {
baseURL: 'http://127.0.0.1:3000'
}
});
For an ES module configuration, use an import at the top of the config file:
import '@percy/playwright/dropin';
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
use: {
baseURL: 'http://127.0.0.1:3000'
}
});
With this route, existing test files do not need Percy-specific edits: Percy applies to each Playwright toHaveScreenshot() call. For example:
import { test, expect } from '@playwright/test';
test('home page visual check', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('home.png');
});
Run the suite through Percy
Set the project token for the command and wrap the Playwright runner:
PERCY_TOKEN=<your-project-token> npx percy exec -- npx playwright test
In a local shell, replace the placeholder with your actual token. Avoid committing a token in a script or configuration file. On Windows or when shell-specific environment syntax is inconvenient, set PERCY_TOKEN in the environment first, then run:
npx percy exec -- npx playwright test
4. Route B: take explicit Percy snapshots
Use explicit SDK calls when each snapshot should be named and placed deliberately in the test flow.
Install packages and create a Percy project
npm install --save-dev @percy/cli @percy/playwright
Create a Percy project, then set its token as PERCY_TOKEN in your shell or CI secret store. Use a recognizable name for each snapshot so reviewers can identify the page and state in the Percy build.
Runnable Playwright example
import { test } from '@playwright/test';
import percySnapshot from '@percy/playwright';
test('capture the pricing page in Percy', async ({ page }) => {
await page.goto('https://example.com/pricing', { waitUntil: 'networkidle' });
await percySnapshot(page, 'Pricing page');
});
Run the test file or suite through Percy:
PERCY_TOKEN=<your-project-token> npx percy exec -- npx playwright test
The percy exec wrapper starts the Percy CLI, runs the command, and collects snapshots into a Percy build. If your environment cannot wrap the test command, the CLI also documents percy exec:start and percy exec:stop for starting and stopping the capture session around the test process. Consult the Percy JavaScript integration guide for the current invocation details.
5. Run Percy in continuous integration
- Create a protected CI secret named
PERCY_TOKENand store the project token there. - Install dependencies using the repository’s lockfile.
- Start any app server the tests need, using your existing CI workflow.
- Run the Playwright tests through
percy exec. - Open the resulting Percy build and review visual changes; approve or update the baseline according to your team’s process.
For example, a CI job that already exposes the protected variable can run:
npx percy exec -- npx playwright test
Keep the token out of pull request output and avoid exposing it to untrusted fork builds. Percy documents support for most parallelized test environments: snapshots produced by multiple processes or machines can be collected into the same build. Configure your CI job so those processes participate in the same Percy execution rather than unintentionally creating unrelated builds.
A passing Playwright run is not equivalent to visual approval. In the drop-in flow, a Playwright check can pass while the visual change remains for review in Percy. Make build review part of the setup’s acceptance criteria.
6. Configure snapshots and baselines
The smallest working setup needs the packages, integration code, token, and Percy-wrapped test command. Add a Percy configuration file only when the project needs to tune capture behavior. The CLI provides percy config:create to generate a configuration file. The options reference covers snapshot widths, minimum height, Percy CSS, asset discovery, hostnames, and uploads.
- Snapshot widths: set the viewport widths your team wants Percy to compare.
- Minimum height: control the minimum snapshot height for pages shorter than the chosen value.
- Percy CSS: apply capture-only CSS when a page needs a stable visual state.
- Asset discovery and hostnames: configure how Percy identifies and resolves page assets when the defaults do not fit your app.
- Uploads: tune upload behavior for your environment as needed.
Use Percy’s documented option names and defaults from the configuration reference; do not copy an old configuration from another project without checking whether the option still exists and applies to your integration.
Percy also documents a baseline setup command for projects that meet its conditions. The documented path depends on project setup, including the default config file and certain screenshot naming and path settings. Check those conditions before using committed screenshots as a baseline; it is not a universal migration command for every custom Playwright configuration.
7. Make captures stable and useful
- Wait for the page state you intend to compare before calling
percySnapshot(). For a deterministic test, wait for a specific locator or app-ready signal when possible. - Give snapshots names that describe page and state, such as
Pricing - annual billingrather thanSnapshot 1. - Control dynamic content in the application or through a documented Percy CSS/configuration approach, so timestamps, rotating content, and user-specific data do not create noise.
- Use consistent viewport and browser settings across baseline and later builds.
- Keep the visual review loop explicit: inspect intentional changes, investigate unexpected diffs, then update the baseline only when the new appearance is accepted.
networkidle can be useful for a simple page, but applications with polling or long-lived requests may never become idle. In those cases, wait for a meaningful selector or application-ready condition rather than relying on network quiet.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No Percy build or snapshots appear | The tests ran directly with Playwright, the token was missing, or the drop-in was not loaded before the Playwright config. | Run the command with percy exec, verify PERCY_TOKEN is present in that process, and move the drop-in import to the top of the config. |
| Authentication or token error | The token is wrong, belongs to another project, or is unavailable in the current CI job. | Set the project’s current token as a protected secret and ensure the job exposes it to the Percy command. Do not print it in logs. |
| Package or runtime compatibility error | The Node.js or package versions do not meet the integration’s requirements or are mismatched. | Check the current Percy reference’s prerequisites, use a supported Node version, and reinstall from the lockfile after aligning package versions. |
| Explicit snapshots are missing | The test never reached percySnapshot(), imported the wrong package entry for the installed version, or the test command was not wrapped. |
Confirm the import against the current JavaScript guide, ensure the test reaches the call, and run it under percy exec. |
| Different machines create separate or incomplete builds | Parallel workers are not participating in the same Percy execution context. | Use the documented parallel execution setup so the CLI can collect snapshots from the processes or machines into one build. |
| Visual diffs change on every run | Dynamic page data, animations, timing, viewport differences, or unstable assets affect the captured state. | Stabilize test data and page readiness, use consistent browser and viewport settings, and apply documented Percy CSS/configuration where appropriate. |
| Tests pass but a visual change is still shown | Playwright’s assertion result and Percy’s visual review serve different steps in the drop-in workflow. | Review the Percy build and approve or update the baseline only after checking the visual change. |
9. Performance, reliability, and cost
Visual capture adds work to a test run: pages must reach a capture point and snapshots must be collected and uploaded. Keep the suite focused on states whose visual appearance matters, use stable readiness conditions, and avoid unnecessary duplicate captures. Parallel execution can distribute test work, but configure it according to Percy’s documented build collection workflow.
Reliability depends on both the browser test and the Percy collection step. Preserve the CI logs for failed runs, keep the token available only where required, and make build review part of the team’s process. A flaky page state creates noisy comparisons regardless of the snapshot service.
Percy pricing and plan limits are not specified in the setup references used here; check Percy’s current pricing before estimating team cost. The Percy workflow is appropriate when you need visual regression builds connected to Playwright tests. For a one-off website screenshot or a capture in an application that does not need a browser test suite, a screenshot API may involve less setup.
10. Or skip the browser setup
If you need a website screenshot rather than a Percy visual regression test, ScreenshotNeo is a screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for request options.
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', new Uint8Array(await res.arrayBuffer()));
With Node.js versions that do not provide Bun’s file writer, save the response using Node’s filesystem API:
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 accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; 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 gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.
Start with 1,000 free screenshots a month, no card required.
FAQ
Do I need to rewrite my Playwright tests?
No, if they already use toHaveScreenshot() and you choose the documented drop-in route. Explicit snapshots require adding percySnapshot() calls where you want captures.
Does a passing Playwright run approve the Percy visuals?
No. Review the Percy build and handle visual changes through your team’s baseline process.
Can Percy collect snapshots from parallel workers?
The documentation says Percy supports most parallelized test environments. Configure workers to join the same Percy execution so their snapshots are collected into the intended build.
Is a Percy configuration file required?
Not for the basic setup. Generate one when you need to tune documented capture or upload settings.
Do I need BrowserStack Automate?
The basic JavaScript SDK workflow does not make hosted browser execution a prerequisite. Choose the hosted-browser setup only if it fits your test environment.


