Playwright Cross-Browser Testing: Chromium, Firefox & WebKit

Playwright Cross-Browser Testing: Chromium, Firefox & WebKit

Playwright is the most capable cross-browser testing framework available in 2026. It ships with bundled Chromium, Firefox, and WebKit engines, runs the same tests across all three without modification, and executes them in parallel by default. This guide covers everything from initial setup to CI configuration and handling browser-specific behavior.

Key Takeaways

  • Playwright bundles its own Chromium, Firefox, and WebKit — no separate browser installs needed
  • The "projects" config defines your browser matrix; tests run against all projects automatically
  • Playwright runs tests in parallel by default — a 100-test suite runs across 3 browsers in similar time to a 100-test Chrome-only suite
  • WebKit on Linux emulates Safari closely enough to catch the majority of Safari-specific bugs
  • Use browserName fixture to conditionally handle browser-specific behavior without duplicating test code

Why Playwright for Cross-Browser Testing

Before Playwright, cross-browser testing with Selenium was painful. Each browser needed its own driver binary, versions had to be kept in sync manually, and running tests in parallel required significant infrastructure work.

Playwright changed this. Microsoft's framework ships with bundled browser binaries — a specific Chromium build, a Firefox build, and a WebKit build — all pinned to versions that are tested to work together. You run npx playwright install and you have everything you need to test across all three engines on any OS.

The architecture is also fundamentally different from Selenium. Playwright uses browser-native protocols (CDP for Chromium, custom protocols for Firefox and WebKit) rather than the WebDriver protocol, which makes it faster, more reliable, and capable of things Selenium cannot do (network interception, multiple contexts, etc.).

Installation and Setup

Installing Playwright

npm init playwright@latest

This scaffolds the configuration file, installs dependencies, and downloads the browser binaries. The interactive setup lets you choose TypeScript or JavaScript, test directory, and whether to add a GitHub Actions workflow.

To install browsers manually:

npx playwright install          # all browsers
npx playwright install chromium <span class="hljs-comment"># just Chromium
npx playwright install --with-deps  <span class="hljs-comment"># browsers + system dependencies (needed in CI)

Basic Project Structure

After init, your project contains:

playwright.config.ts    # test configuration
tests/                  # test files
tests-examples/         # sample tests
package.json

Configuring the Browser Matrix

The playwright.config.ts file is where you define which browsers to test against using the projects array.

Basic Multi-Browser Config

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',

  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
});

With this config, every test in ./tests runs three times — once per browser. Playwright reports results per-project, making it easy to see which tests fail only in WebKit.

Adding Mobile Browsers

Playwright includes pre-configured device descriptors for common mobile devices:

projects: [
  // Desktop
  { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
  { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  { name: 'firefox', use: { ...devices['Desktop Firefox'] } },

  // Mobile
  { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
  { name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
  { name: 'tablet', use: { ...devices['iPad Pro 11'] } },
],

Mobile device descriptors set the viewport, user agent, touch support, and device pixel ratio automatically.

Scoping Tests to Specific Browsers

Some tests are only relevant for specific browsers. Use test.skip with a condition:

test('Safari-specific date picker behavior', async ({ page, browserName }) => {
  test.skip(browserName !== 'webkit', 'Safari-specific test');
  // ...
});

Or configure a project to only run certain test files:

{
  name: 'webkit-only',
  use: { ...devices['Desktop Safari'] },
  testMatch: '**/safari-*.spec.ts',
},

Parallel Execution

How Playwright Parallelizes Tests

Playwright's parallelism operates at two levels:

  1. Worker-level parallelism: Multiple test files run concurrently in separate worker processes
  2. Test-level parallelism: Within a file, tests can run in parallel if you opt in

By default, each test file runs sequentially within itself but files run in parallel across workers. The fullyParallel: true config makes every test run in its own worker regardless of file.

For cross-browser testing, each project runs independently. With 3 projects and 4 workers, Playwright distributes test execution efficiently — Chromium tests don't wait for WebKit tests to finish.

Optimal Worker Configuration

export default defineConfig({
  fullyParallel: true,
  // In CI: limit workers to avoid resource contention
  workers: process.env.CI ? 2 : '50%',
});

On a local machine, using 50% of available CPUs as workers is a reasonable default. In CI, limiting to 2–4 workers prevents out-of-memory issues in containers.

Sharding for Large Test Suites

For very large test suites, Playwright supports sharding — splitting the test suite across multiple CI jobs:

# GitHub Actions matrix strategy
strategy:
  matrix:
    shardIndex: [1, 2, 3, 4]
    shardTotal: [4]
steps:
  - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}

Each shard runs one quarter of the tests. Combined with multi-browser projects, this can reduce a 1-hour test suite to 15 minutes.

Handling Browser-Specific Behavior

The browserName Fixture

The browserName fixture is available in every test and reports which browser the test is running against:

test('file upload', async ({ page, browserName }) => {
  await page.goto('/upload');

  if (browserName === 'webkit') {
    // Safari handles file inputs differently
    await page.setInputFiles('input[type=file]', 'test-file.txt');
  } else {
    await page.locator('input[type=file]').setInputFiles('test-file.txt');
  }
});

Conditional Expectations

Some visual differences between browsers are intentional and acceptable. Skip visual assertions for known differences:

test('button styling', async ({ page, browserName }) => {
  await page.goto('/buttons');
  const button = page.locator('.primary-button');

  // Only do pixel-perfect visual comparison in Chromium
  if (browserName === 'chromium') {
    await expect(button).toHaveScreenshot('primary-button.png');
  } else {
    // For other browsers, just verify functionality
    await expect(button).toBeVisible();
    await button.click();
    await expect(page.locator('.confirmation')).toBeVisible();
  }
});

Common WebKit Gotchas

WebKit (Safari) has several behaviors that differ from Chromium and Firefox:

Input events: WebKit fires input and change events differently. Some form interactions that work in Chrome need explicit page.fill() instead of typing simulation.

Date/time inputs: Safari's date picker is completely different from Chrome's. Tests that interact with native date inputs need WebKit-specific handling.

Clipboard API: WebKit restricts clipboard access more aggressively. Tests involving copy/paste often need workarounds.

Scroll behavior: scrollTo and scrollIntoView behave differently. Always test scroll-dependent features in WebKit.

CSS features: Newer CSS (e.g., aspect-ratio, some gap behaviors, newer color functions) may not work in the bundled WebKit version.

CI/CD Setup

GitHub Actions

name: Playwright Tests
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  playwright:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Run Playwright tests
        run: npx playwright test

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

The --with-deps flag installs system dependencies (libgbm, libasound, etc.) needed by the browser engines on Ubuntu. Without it, WebKit and Firefox may fail to launch.

Caching Browser Binaries

Browser installs take 1–3 minutes. Cache them to speed up CI:

- name: Cache Playwright browsers
  uses: actions/cache@v4
  with:
    path: ~/.cache/ms-playwright
    key: playwright-${{ hashFiles('package-lock.json') }}

- name: Install Playwright browsers
  run: npx playwright install --with-deps

The cache key includes the lockfile hash — when Playwright version changes (and thus browser versions change), the cache is invalidated automatically.

Viewing Test Results

Playwright generates an HTML report showing results per test, per browser, with screenshots and traces for failures. Upload it as a CI artifact and download it locally to diagnose failures:

npx playwright show-report path/to/downloaded/playwright-report

For failed tests, the Playwright Trace Viewer shows a full timeline of the test execution with DOM snapshots, network requests, and console logs — invaluable for debugging cross-browser failures.

Playwright vs Other Cross-Browser Tools

Feature Playwright Selenium Cypress
Chromium support
Firefox support
WebKit support Limited
Bundled browsers
Parallel by default Manual Manual
Network interception
Multiple tabs/windows Limited
Mobile emulation Limited Limited

Playwright's WebKit support is unique — no other framework ships with a bundled WebKit engine for non-macOS systems. This is the single biggest reason to choose Playwright for cross-browser testing.

HelpMeTest and Playwright

Writing Playwright tests still requires familiarity with the API and maintenance over time as selectors change and flows evolve. HelpMeTest takes a different approach — describe your test scenarios in plain English and HelpMeTest handles the Playwright implementation underneath, running your tests across Chromium, Firefox, and WebKit automatically.

For teams that want the reliability of Playwright's cross-browser coverage without the maintenance overhead of a Playwright test suite, HelpMeTest bridges the gap.

Conclusion

Playwright is the best tool available for cross-browser testing in 2026. Its bundled browser approach eliminates the infrastructure headaches of traditional cross-browser testing, parallel execution by default makes multi-browser runs practical, and its WebKit support is unmatched in the ecosystem.

Set up the projects configuration with Chromium, Firefox, and WebKit, wire it into your CI pipeline with the GitHub Actions workflow above, and you have comprehensive cross-browser coverage on every pull request. The investment in setup is a few hours; the payoff is catching Safari and Firefox regressions before your users do.

Read more