How to Do Cross-Browser Testing With Playwright
Run the same Playwright tests in Chromium, Firefox, and WebKit. Configure a useful browser matrix, install matching binaries, and run it reliably in CI.
Use Playwright Test projects to run the same test suite in Chromium, Firefox, and WebKit. Install the browser binaries that match your Playwright release, define one project per browser in playwright.config.ts, then run npx playwright test. All configured projects run by default; use --project to select specific ones.
This guide configures a practical desktop matrix, shows how to add device emulation and branded browser channels, and explains local and CI workflows, troubleshooting, and coverage limits. Playwright’s browser documentation describes the supported browser builds and their differences.
1. Install Playwright and its browsers
If the project already uses Playwright Test, keep its package version pinned through your package manager lockfile. Otherwise, add the test package using the setup command for your project:
npm init playwright@latest
Install browser binaries for the installed Playwright version:
npx playwright install
On a Linux CI runner that needs operating system libraries, install those dependencies too:
npx playwright install --with-deps
Playwright releases expect particular browser revisions. After upgrading Playwright, rerun the install command so those binaries are available. See the official browser installation guidance for platform-specific details.
2. Configure Chromium, Firefox, and WebKit projects
Create or update playwright.config.ts. Each project is a named configuration applied to the same test files unless you configure different test matching rules. The starter below runs the tests in all three core browser engines:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
reporter: 'html',
use: {
baseURL: 'http://127.0.0.1:3000',
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
webServer: {
command: 'npm run start -- --port 3000',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
});
Adjust testDir, the server command, and baseURL for your app. If the app is already running or the tests do not need a local server, remove the webServer block. Projects are configured in the Playwright Test projects settings; the best practices guide covers reliable test design.
Run the full matrix or select projects
# Run every configured project
npx playwright test
# Run just Firefox
npx playwright test --project=firefox
# Run Chromium and WebKit
npx playwright test --project=chromium --project=webkit
During development, use headed mode to see the browser, or UI mode to inspect and rerun tests interactively:
npx playwright test --project=webkit --headed
npx playwright test --ui
Project names appear in test output and reports, which helps identify whether a failure is isolated to one engine. The command line reference lists further run options.
3. Choose browser and device coverage deliberately
Start with the browser engines your product promises to support. Chromium, Firefox, and WebKit reveal many engine-specific differences. Add projects for branded Chrome or Edge channels only when you need to validate those installed browser channels specifically. Playwright’s Chromium, Firefox, and WebKit builds are not the branded Chrome, Firefox, and Safari applications.
| Coverage need | Configuration choice | What it tells you |
|---|---|---|
| Core engine coverage | Chromium, Firefox, WebKit projects | Whether shared behavior works across the three browser engines. |
| Branded desktop browser | Chromium with a Chrome or Edge channel | Behavior in that installed branded channel; it does not replace engine coverage. |
| Mobile-like layout and input | A device profile or custom viewport/touch settings | Selected emulated device characteristics; this is not a physical device run. |
| Platform-sensitive behavior | Run on the relevant operating system | Differences tied to OS features such as media codec availability. |
Playwright device profiles simulate characteristics such as user agent, viewport, screen dimensions, and touch support. You can also configure locale, timezone, geolocation, permissions, and color scheme. These settings are emulation, not proof of behavior on every real phone or browser. See Playwright emulation.
Add a mobile profile or branded channel
Import the profile and add another project when mobile layout or touch behavior is part of your support requirements:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{
name: 'mobile-chrome',
use: { ...devices['Pixel 7'] },
},
{
name: 'chrome-channel',
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
},
],
});
Use device descriptors available in the installed Playwright package. A device profile is a useful way to exercise responsive layouts and touch settings, but it does not reproduce every property of a physical device. A branded channel project also needs that browser channel installed on the runner; consult the current browser channel documentation.
Set locale, timezone, geolocation, or color scheme
These are browser context settings and can be shared across a project or set for an individual test. For example:
import { test, expect } from '@playwright/test';
test.use({
locale: 'en-GB',
timezoneId: 'Europe/London',
colorScheme: 'dark',
});
test('shows the localized home page', async ({ page }) => {
await page.goto('/');
await expect(page.locator('html')).toHaveAttribute('lang', 'en-GB');
});
For geolocation, set a location and grant permission in the relevant project or test:
use: {
geolocation: { latitude: 51.5072, longitude: -0.1276 },
permissions: ['geolocation'],
}
Only add these settings when they represent a supported user scenario. More projects and context variations increase the number of test executions and the work needed to maintain results.
4. Understand what WebKit coverage means
Playwright’s WebKit build is derived from WebKit and is useful for catching engine differences. It is not the branded Safari application. For behavior sensitive to Apple’s platform implementation, run WebKit on macOS; a Linux WebKit run is not identical to Safari on macOS. Operating system differences can also affect available media codecs. See the official browser notes.
Apply the same distinction to Firefox: Playwright provides a supported Firefox build that is separate from the branded browser. Use a branded channel project if your support commitment specifically names that browser application.
5. Run cross-browser tests in CI
A CI job should install the locked project dependencies, install the browsers and any required system dependencies, then run the suite. A minimal GitHub Actions workflow looks like this:
name: Playwright
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
Use the Node version and dependency install command that match your application. The browser install step should use the Playwright version in the lockfile. For stability on a constrained runner, begin with one worker:
npx playwright test --workers=1
To reduce wall-clock time as the suite grows, shard tests across CI jobs rather than assuming a higher worker count on one runner will always be faster or more reproducible. The Playwright CI guide documents installation, workers, and sharding.
Keep reports useful
Retain project names in reports and traces so a failure can be tied to its browser configuration. A trace on retry is a practical default for CI: it captures diagnostic context for retried failures without requiring every passing run to retain a trace. Open the HTML report with:
npx playwright show-report
When a single project fails, rerun that project locally first. Decide whether the cause is a real browser difference, a test assumption, or an installation/environment issue before changing application code.
6. Make the matrix efficient and reliable
- Prioritize by risk. Cover the engines and platforms your users rely on, then add targeted device or branded-channel projects for meaningful differences.
- Separate fast feedback from broad coverage. Run a small smoke suite on every change if the full matrix is costly, and run the broader suite on the schedule appropriate for the team.
- Keep browser versions aligned. Pin Playwright through the lockfile and install its matching browser revisions in CI.
- Avoid accidental matrix multiplication. Every project runs matching tests. Adding multiple browsers, devices, locales, and color schemes can multiply execution and maintenance costs.
- Use stable test behavior. Prefer Playwright’s locator assertions and auto-waiting over arbitrary sleeps; control test data and external dependencies where possible.
- Scale with shards. Distribute work across CI jobs when additional throughput is needed, and keep each job’s browser setup consistent.
There is no universal browser matrix. Select coverage from the product’s support commitments, user environment, and features with known engine or platform variation.
7. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Executable does not exist or browser cannot launch | The expected browser binary is missing, often after a Playwright upgrade or on a fresh runner. | Run npx playwright install, or npx playwright install --with-deps on Linux when system dependencies are needed. |
| Only one browser project runs | The command includes a --project filter, or only one project is configured. |
Run npx playwright test without a project filter and inspect the config’s projects array. |
| Test passes in Chromium but fails in WebKit or Firefox | The app or test may rely on engine-specific behavior, timing, or an unsupported assumption. | Reproduce with --project=webkit --headed or the failing project, inspect trace and console output, and verify the behavior against that engine. |
| WebKit result does not match Safari on a Mac | The test ran on another OS or assumes Playwright WebKit is the branded Safari app. | Run on macOS for the closest Safari-related coverage and treat the WebKit build as an engine signal, not an identical Safari environment. |
| Media playback differs across runners | Codec or media support can depend on operating system and browser build. | Run on the OS and browser channel relevant to the supported use case; avoid inferring universal media support from one runner. |
| CI is flaky or slower after adding projects | More projects multiply work and can compete for limited runner resources. | Start with one worker for stability, trim unnecessary projects, and shard across jobs when throughput is required. |
| Device project behaves differently from a real phone | Device descriptors emulate selected properties and do not reproduce all hardware, OS, or browser behavior. | Use the emulation for responsive and touch checks, then validate critical device-specific behavior on the target platform where needed. |
| Local server is unavailable in CI | The app server command, readiness URL, port, or environment differs from the config. | Check the webServer.command and url, ensure the server binds to the expected interface and port, or remove the block if the app is provisioned elsewhere. |
Or skip the browser setup
If you need screenshots of pages as part of visual review or documentation, ScreenshotNeo captures a URL through one API request. It complements Playwright’s interactive browser tests; it does not replace cross-browser test coverage.
See the ScreenshotNeo API documentation for request options. Example using cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same request in 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)
And 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}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.
Sign up free for 1,000 screenshots a month, with no card required.
Frequently asked questions
Do all Playwright projects run by default?
Yes. A plain npx playwright test runs all configured projects that match the tests. Use --project to narrow the run.
Does Playwright cross-browser testing require three separate test suites?
No. Projects apply different browser configurations to matching tests, so a shared suite can run across the matrix.
Does a Playwright WebKit run count as a Safari test?
It is WebKit coverage, not the branded Safari application. For the closest Safari-related behavior, use WebKit on macOS and account for platform-specific differences.
Should every pull request run every device profile?
Only if the feedback time and runner capacity make that useful. Keep the required support matrix covered, and add broader device coverage where the product’s risk justifies the extra runs.


