How to Compare Screenshots Across Chrome, Firefox, and WebKit in Playwright
Compare Chromium, Firefox, and WebKit screenshots with Playwright Test. Set browser-specific baselines, stabilize captures, and tune diffs without hiding real regressions.
Use Playwright Test’s built-in expect(page).toHaveScreenshot() in separate Chromium, Firefox, and WebKit projects. Keep a reviewed baseline for each browser project and relevant platform, and compare each run with the baseline from the same controlled environment. These assertions detect changes within each browser’s rendering; they do not make screenshots from different browser engines identical.
In Playwright configuration the project is called chromium, not Chrome. Playwright’s Chromium browser is not branded Google Chrome by default. Likewise, Playwright WebKit is not the Safari app. This guide sets up per-engine regression checks, explains how to inspect cross-browser differences, and covers the controls that make image comparisons useful.
1. Configure Chromium, Firefox, and WebKit projects
Install Playwright Test and its browser binaries, then define one project per engine. The example uses TypeScript and works as a small runnable setup.
npm init -y
npm install --save-dev @playwright/test
npx playwright install
Create playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
reporter: 'html',
use: {
baseURL: 'http://127.0.0.1:4173',
headless: true,
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 1,
},
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
{ name: 'firefox', use: { browserName: 'firefox' } },
{ name: 'webkit', use: { browserName: 'webkit' } },
],
});
Set baseURL to your application’s local or test server and start that server in your project’s normal way before running the test. If it is not already started by your test workflow, add Playwright’s webServer configuration with your app’s actual start command and readiness URL. Avoid copying a guessed command: development and preview servers differ by framework.
Install browser binaries whenever you add or update Playwright. In CI, use the same Playwright package and browser installation for baseline generation and comparison. The Playwright Firefox build tracks recent Firefox Stable but includes Playwright patches; its WebKit build follows upstream WebKit and may contain changes before they reach Safari.
2. Add one screenshot assertion for all three engines
Create tests/homepage.spec.ts. The same test runs once in each configured project:
import { test, expect } from '@playwright/test';
test('homepage visual baseline', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
await expect(page).toHaveScreenshot('homepage.png', {
fullPage: true,
});
});
Replace the heading and route with elements that exist in your application. Waiting for a meaningful UI condition is better than taking the screenshot immediately after navigation: it makes the intended state explicit. The first run creates reference images; subsequent runs compare actual captures against those references. With multiple projects, Playwright uses the project name in the snapshot path, so each engine gets its own reference.
Generate the initial references, inspect them, and commit approved images with the test code. A baseline is an expected result, not an automatically correct result: review the screenshot for each project before accepting it.
npx playwright test tests/homepage.spec.ts --project=chromium
npx playwright test tests/homepage.spec.ts --project=firefox
npx playwright test tests/homepage.spec.ts --project=webkit
To run all configured projects:
npx playwright test tests/homepage.spec.ts
To intentionally refresh references after reviewing a UI change:
npx playwright test tests/homepage.spec.ts --update-snapshots
Do not update snapshots just to make a failing job green. Open the expected, actual, and diff images in the Playwright report, determine whether the change is intended, then update and review the baseline changes in code review.
3. Understand what cross-browser comparison means
Keep two questions separate:
- Did this browser regress? Compare Chromium’s current screenshot with its approved Chromium baseline, and do the same independently for Firefox and WebKit.
- Does the UI behave or render differently across browsers? Compare the three actual outputs with each other and investigate meaningful differences.
Per-browser baselines answer the first question. They do not fail merely because Firefox’s font rasterization or layout differs from Chromium’s approved appearance. If your goal is compatibility, inspect browser-to-browser differences too and decide which differences are acceptable for your product.
Playwright’s WebKit is derived from upstream WebKit, not the branded Safari application. Playwright’s documentation notes that its WebKit can include changes before they reach Safari and other WebKit browsers. If a requirement depends on branded Safari behavior, test Safari itself in the environment where it is available; a WebKit project is useful engine coverage but is not a guarantee of identical Safari output. See the official Playwright browser documentation.
4. Make screenshots repeatable
Rendering can vary with the operating system, browser version, settings, hardware, power source, headless mode, and fonts. Generate and compare baselines in the same environment wherever practical. Microsoft’s visual comparison documentation explicitly calls out these environment differences.
Control the page state
- Use fixed test data and deterministic account state. Reset records or seed the same fixture before each run.
- Wait for a visible signal that the page reached the state you intend to capture. For data loaded asynchronously, wait for the relevant result, not an arbitrary short timeout.
- Keep the same viewport, device scale factor, locale, color scheme, and other context settings in baseline and comparison runs.
- Disable or settle animations. Screenshot assertions disable animations and hide the caret by default, but page timers, rotating content, video, and other changing elements may still need explicit control.
- Use screenshot-specific styles to hide or normalize volatile areas such as timestamps, rotating promotions, or third-party embeds. Do not hide application areas whose regressions you need to catch.
For example, pass a stylesheet to the assertion to hide a known changing timestamp. Use a selector that is specific to your own page:
await expect(page).toHaveScreenshot('account.png', {
fullPage: true,
style: `
.last-updated,
.rotating-promotion {
visibility: hidden !important;
}
`,
});
Screenshot styles can also be used to neutralize unstable embedded content when appropriate. Be deliberate: hiding an iframe or dynamic region improves repeatability only if that region is outside the behavior this check is intended to protect. Review the PageAssertions API for the current screenshot assertion options.
5. Tune image-diff sensitivity
Playwright supports a color difference threshold and limits on the number or proportion of changed pixels. The documented default YIQ color threshold is 0.2. The pixel limits are useful when a small amount of noise is acceptable, but generous limits can hide real UI changes.
await expect(page).toHaveScreenshot('checkout.png', {
maxDiffPixels: 120,
// Alternatively, set a ratio for differently sized images:
// maxDiffPixelRatio: 0.001,
// Adjust only after reviewing representative diffs:
// threshold: 0.2,
});
Use either maxDiffPixels or maxDiffPixelRatio to define an acceptable changed area; do not set both without checking the behavior documented for your installed version. The threshold controls how different a pixel’s color must be before it counts as different, while the maximum pixel count or ratio limits the overall differing area. Start with strict comparisons, review recurring diffs, and tune for known rendering noise rather than for a particular failing run. Consult the current API reference for option details and version-specific behavior.
6. Keep capture dimensions and scope consistent
A screenshot’s dimensions and scale affect the comparison. Keep viewport and device scale factor fixed in the project configuration. Playwright screenshot scale can be CSS scale, which produces one output pixel per CSS pixel, or device scale, which produces one output pixel per device pixel and can create larger images on high-DPI contexts. Use the same scale for baselines and actual captures.
Choose the screenshot scope deliberately:
fullPage: truecaptures the full scrollable page and is useful for long-page layout checks. It can make snapshots larger and expose more content that must be stable.- Default viewport capture focuses on what is initially visible, making it a smaller and often more targeted check.
- Use a locator screenshot assertion when the component is the unit under test, so unrelated page regions do not create noise.
See PageAssertions and the LocatorAssertions API for supported assertion forms and options. Keep baseline names descriptive and stable; a rename can create a new reference rather than updating the existing one.
7. Run in CI and review changes
- Pin the Playwright dependency in the lockfile and install the browser binaries associated with that version.
- Run the same projects, viewport, scale, test data, and headless settings used to create the references.
- Archive or inspect the Playwright report when a visual assertion fails; use the expected, actual, and diff images to identify the changed region.
- When a product change is intended, regenerate snapshots in the controlled baseline environment and review the resulting image files alongside code changes.
- If a failure occurs only on one operating system or CI image, treat that as a separate rendering context and decide whether it needs its own project and baselines.
Separate operating system baselines may be appropriate when your CI matrix deliberately tests multiple platforms. Avoid comparing a baseline created on one OS with a run on another and interpreting font or rasterization differences as an application regression.
8. Troubleshooting visual comparison failures
| Symptom | Likely cause | What to do |
|---|---|---|
| Every screenshot fails after moving to CI | Different OS, fonts, browser build, headless mode, viewport, scale, or rendering environment. | Align the baseline and CI environment, including Playwright version and browser binaries. If multiple platforms are intentional, keep platform-specific references. |
| Only Firefox or WebKit fails | Engine-specific layout, font, or rendering behavior; alternatively the test reaches a different state in that engine. | Inspect that engine’s actual and diff images, confirm the expected UI state, then fix the compatibility issue or approve a browser-specific baseline if the difference is intended. |
| Diffs appear intermittently | Uncontrolled data, animation, timers, asynchronous content, or third-party embeds. | Use stable fixtures, wait for the intended state, disable or normalize volatile content, and keep the screenshot style narrowly scoped. |
| Reference image is missing | The baseline was never generated, was not committed, or the test/project/name changed. | Run the test in the intended project to generate the first reference, inspect it, and commit the approved snapshot. Check the project name and snapshot path. |
| Too many small pixel differences | Anti-aliasing or environment-level rendering variation, or a threshold that is too strict for the controlled setup. | First align environment and inspect the diff. Adjust threshold or a small pixel allowance only after confirming the changed area is not meaningful. |
| A large change passes unexpectedly | The allowed pixel count or ratio is too high, or the color threshold is too permissive. | Reduce tolerance and add a focused assertion for the important component. Check that volatile-region styles are not hiding the defect. |
| WebKit passes but Safari still differs | Playwright WebKit is not the branded Safari app and may track newer upstream WebKit. | Run a Safari-specific check for requirements that depend on Apple’s browser behavior. |
| Full-page capture is inconsistent | Lazy-loaded content or page state changes as the page is scrolled or captured. | Ensure content is loaded and stable before capture; compare a focused viewport or locator screenshot if the full page is not the behavior under test. |
9. Or skip the browser setup
If you need a clean screenshot of a public URL rather than a Playwright regression assertion, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns an image or PDF; its screenshot features include full-page capture, CSS selector capture, viewport and device presets, custom CSS and JavaScript, and configurable waits. See the ScreenshotNeo API docs for the full options and setup.
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);
In Node.js environments without Bun, read the response as bytes and write them with your preferred filesystem API. ScreenshotNeo accepts familiar screenshot API parameter names to make migration easier. It removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up free for 1,000 screenshots a month, no card required.
10. Frequently asked questions
Should I use the same baseline for Chrome, Firefox, and WebKit?
No. Keep a separate expected image for each configured browser project because rendering can differ by engine.
Does the Chromium project launch Google Chrome?
It launches Playwright’s Chromium build by default, not branded Chrome. Use the browser and channel setup supported by your Playwright version if branded Chrome is a specific requirement.
Is Playwright WebKit equivalent to Safari?
No. It is Playwright’s build derived from upstream WebKit. Test branded Safari when your requirement specifically depends on Safari.
Can I compare output from different operating systems?
You can inspect those outputs, but use platform-specific baselines when you need regression checks on multiple operating systems because fonts and rendering can vary.
Do per-browser baselines prove the page looks consistent across engines?
No. They detect changes against each engine’s own approved reference. Inspect the engines’ outputs against one another to evaluate compatibility.


