How to Run Screenshot Tests Across Chrome, Firefox, and WebKit
Set up Playwright screenshot tests for Chromium, Firefox, and WebKit with separate baselines, stable captures, and a practical workflow for reviewing visual diffs.
Use Playwright Test with one project for each browser engine, then add toHaveScreenshot() assertions for the page or component states you want to protect. Playwright creates reference images on the first run and compares later runs against them. Keep a separate reviewed baseline for Chromium, Firefox, and WebKit: their renderings are distinct targets and should not be compared as if they were interchangeable.
This guide uses Playwright Test and TypeScript. Playwright’s Chromium project targets Chromium; if your requirement is branded Google Chrome specifically, verify the installed Playwright version’s project and channel configuration rather than assuming Chromium automatically selects that branded browser. See the official Test projects documentation.
1. Install Playwright and the browser engines
In an existing Node.js project, install Playwright Test and its browser binaries:
npm init playwright@latest
npx playwright install chromium firefox webkit
If Playwright Test is already installed, install only the required browsers with the second command. Keep the package version and browser binaries consistent between baseline creation and CI runs. The exact installation workflow can vary with your repository and operating system; use Playwright’s installation guide if setup fails.
2. Configure Chromium, Firefox, and WebKit projects
Create or update playwright.config.ts so the same test suite runs once in each engine. This example uses Playwright’s built-in browser projects and a fixed viewport:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
snapshotPathTemplate: '{testDir}/__screenshots__/{projectName}/{arg}{ext}',
use: {
baseURL: 'http://127.0.0.1:3000',
viewport: { width: 1440, height: 900 },
colorScheme: 'light',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
The snapshotPathTemplate places expected images in project-specific directories, making it easier to see that each engine has its own baseline. Playwright also names snapshots by project when using its default snapshot path behavior. Snapshot path configuration is version-sensitive; consult the TestProject API for the version you use and adjust the template if needed. The devices presets supply device-like settings; for desktop testing, a directly specified viewport is also a valid choice.
If your app needs a development server, configure webServer in the Playwright config or start the server separately before running tests. For example, a project can add webServer: { command: 'npm run dev', url: 'http://127.0.0.1:3000', reuseExistingServer: !process.env.CI }. Match the command and URL to your app. Do not run a second server accidentally if your test environment already manages one.
3. Add a screenshot assertion
Create tests/home.visual.spec.ts. The first execution generates expected images; inspect them before accepting them into version control.
import { test, expect } from '@playwright/test';
test('home page visual appearance', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('home.png');
});
Run all configured projects:
npx playwright test tests/home.visual.spec.ts
Or run one engine while developing:
npx playwright test tests/home.visual.spec.ts --project=firefox
After a run, review the generated or changed expected images and commit only the baselines you have inspected. A baseline is part of the test’s expected result, so updating it without reviewing the diff can turn a real regression into the new reference.
4. Make the captured state repeatable
Visual comparisons are only useful when the test reaches the same user-visible state. Use stable fixtures, a fixed viewport and color scheme, deterministic content, and explicit readiness conditions for the content being captured. Avoid relying on a fixed sleep when an element or application state can provide a readiness signal.
import { test, expect } from '@playwright/test';
test('pricing page after plans load', async ({ page }) => {
await page.goto('/pricing');
await expect(page.getByRole('heading', { name: 'Plans' })).toBeVisible();
await expect(page.locator('[data-testid="plan-grid"]')).toHaveScreenshot('plan-grid.png');
});
For a component screenshot, target the component locator rather than capturing the entire page. This reduces unrelated changes in headers, footers, or surrounding content from affecting the assertion. Use full-page screenshots when the page as a whole is the behavior you intend to cover.
5. Control animation and dynamic regions
Playwright screenshot assertions support controls for animations, stylesheets, and pixel difference thresholds. Use these controls to address known variation, not to make unexplained failures disappear. The PageAssertions API documents the available assertion options.
await expect(page).toHaveScreenshot('home.png', {
animations: 'disabled',
stylePath: './tests/screenshot.css',
maxDiffPixels: 100,
});
A stylesheet such as tests/screenshot.css can hide or neutralize a known volatile region:
[data-testid="live-clock"],
[data-testid="rotating-promotion"] {
visibility: hidden !important;
}
Prefer a stable test fixture or a narrowly scoped screenshot stylesheet for known dynamic content. Set a pixel tolerance only after inspecting real diffs: a broad threshold can allow a meaningful layout change to pass. Browser rendering may vary with operating system, browser version, settings, hardware, power source, and headless mode, as Playwright documents in its visual comparisons guide.
6. Run, inspect, and update baselines
- Run the visual test in each configured project and let the initial reference images be generated.
- Open each browser’s expected image and confirm it shows the intended state.
- Commit reviewed baseline files with the test and config.
- When a later run fails, inspect the expected image, actual image, and diff before deciding what changed.
- If the design change is intentional, update references with
npx playwright test --update-snapshots, review the replacements, and commit them with the code change.
Keep baseline creation and CI comparisons aligned in operating environment and browser version. A change from Chromium to Firefox, or a host OS change, can alter pixels independently of your application. Treat each browser project as its own rendering target and compare each run to the baseline for that project.
7. Add browser and viewport coverage deliberately
Start with the three engines at one representative viewport. Add mobile or tablet projects when responsive behavior matters; each additional project and page state creates more expected images to generate and review. A useful coverage matrix is:
| Axis | What to keep consistent | When to add another case |
|---|---|---|
| Browser project | Separate Chromium, Firefox, and WebKit baselines | When the application supports or must be checked in that engine |
| Environment | Operating system, Playwright version, browser build, and headless mode | When users or deployment targets require another environment |
| Viewport | Exact width and height for each baseline | When layout changes at a breakpoint are in scope |
| Page state | Stable data and the same loaded state | For distinct user-visible states such as empty, error, or populated views |
| Diff sensitivity | Reviewed screenshot options and thresholds | Only when a specific, understood rendering variation requires it |
The number of combinations grows as you add projects, viewports, and states, so prioritize combinations tied to supported behavior. The documentation supports multi-project testing and project-specific snapshots; managing the review workload is a practical consequence of adding more cases.
8. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Browser executable is missing | The project’s browser binary was not installed, or the installed Playwright package and browser set do not match. | Run npx playwright install chromium firefox webkit in the environment that runs the tests. |
| Snapshots are missing on the first run | No reference images exist yet. | Run the test to generate them, inspect each engine’s image, then commit the reviewed baselines. |
| One browser fails while another passes | The page may expose engine-specific rendering or behavior, or the project may not have reached the same state. | Inspect that project’s actual and expected images, verify readiness and browser-specific behavior, and keep its baseline separate. |
| CI reports a diff after a harmless environment change | Rendering can vary by OS, browser version, settings, hardware, power source, or headless mode. | Align CI and baseline environments. If the environment change is intentional, regenerate and review baselines in the new environment. |
| Repeated diffs around a clock, carousel, or ad slot | Dynamic content changes between captures. | Use deterministic test data, wait for the intended state, or hide only that region with a screenshot stylesheet. |
| Tests time out before capture | The page or readiness condition did not finish, or the app server is unavailable. | Check server startup and navigation errors, wait for a meaningful readiness signal, and configure an appropriate test timeout only if the operation legitimately needs longer. |
| Updating snapshots hides an unexpected change | New references were accepted without reviewing the diff. | Restore the prior baseline if needed, inspect the actual and diff images, and update only when the visual change is intentional. |
| A threshold makes a test pass but the page looks wrong | maxDiffPixels is too permissive for the change. |
Lower or remove the tolerance and stabilize the source of noise instead of accepting broad differences. |
9. Performance, reliability, and cost
Running three projects means the suite performs browser work for three engines; adding viewports and page states adds more runs and baseline files. Keep visual assertions focused on important pages and states, and use component screenshots where that gives meaningful coverage with less unrelated page content.
For reliable results, pin the Playwright dependency, install its matching browsers in CI, and use the same operating system and capture mode when generating and comparing references. Stabilize inputs and inspect artifacts on failure. Screenshot assertions are sensitive to rendering differences; they do not establish that an application is functionally correct, accessible, or identical in every real user environment.
Playwright is the test runner described here; the research sources do not establish a universal runtime benchmark or monetary cost for a particular CI setup. Actual compute cost depends on your CI provider, test volume, parallelism, and the environments you choose.
Or skip the browser setup
For a screenshot of a publicly reachable page without installing browser engines or maintaining visual test baselines, ScreenshotNeo is a website screenshot API and MCP server. It returns a screenshot or PDF from one GET request. See the ScreenshotNeo API docs for options and response details.
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);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and billing status. Its MCP server gives AI agents screenshot tools. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. This is useful for capturing pages; Playwright’s browser projects remain the approach in this guide for automated baseline comparisons across engines.
Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.
FAQ
Are Chrome and Chromium the same target in this configuration?
The example config uses Playwright’s Chromium project. If you specifically need branded Google Chrome, verify the browser channel configuration supported by your installed Playwright version.
Should screenshots from Firefox and WebKit match Chromium pixel for pixel?
No. Each engine has its own rendering behavior and should have a separate reference image for the same intended page state.
Can screenshot tests replace functional tests?
No. They catch visual differences in captured states. Keep functional assertions for behavior such as navigation, validation, and interactions.
When should I update a baseline?
Update it after an intentional visual change, once you have inspected the new image and confirmed the diff reflects the expected design.


