Q22 of 42 · Playwright

How do you test against multiple browsers in CI?

PlaywrightMidplaywrightcicross-browsershardingmid

Short answer

Short answer: Define a project per browser in `playwright.config.ts`. The runner spawns each project's tests in parallel by default. In CI, optionally shard each project across machines via `--shard`. Match Playwright's bundled browser version with the runner OS to avoid system-lib drift.

Detail

Cross-browser CI for Playwright is mostly a config exercise.

Step 1: Define projects for each browser:

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

A single npx playwright test runs all three sets in parallel.

Step 2: Filter per CI job if you want each browser on its own runner:

strategy:
  matrix:
    browser: [chromium, firefox, webkit]
steps:
  - run: npx playwright test --project=${{ matrix.browser }}

This uses GitHub Actions' matrix to spawn three parallel runners, each handling one browser. You get faster wall time at the cost of triple the runner usage.

Step 3: Shard each browser for more parallelism:

strategy:
  matrix:
    browser: [chromium, firefox, webkit]
    shard: [1, 2, 3]
steps:
  - run: npx playwright test --project=${{ matrix.browser }} --shard=${{ matrix.shard }}/3

That's 9 parallel jobs (3 browsers × 3 shards). Wall time drops further; runner cost rises.

Practical decisions:

  • Don't run cross-browser on every PR. Run Chromium on PR for fast feedback; run all three on PR-to-main or nightly.
  • Pin Playwright version in CI matching your local. Playwright version determines browser binary versions; mixing produces inconsistent results.
  • Linux base image with system libs. Use mcr.microsoft.com/playwright:v1.x.y-jammy or run npx playwright install-deps to install required system packages.
  • Smoke vs full. Some teams run a small smoke set on every browser, full regression on Chromium only. Cross-browser bugs are usually CSS / Date / focus-style; targeting smoke covers most.

WebKit caveats: WebKit on Linux ≠ Safari on macOS. Some genuinely-Safari behaviour (extensions, ITP, certain permission flows) doesn't reproduce. For high-fidelity Safari, BrowserStack / SauceLabs or a real Mac runner is required.

// EXAMPLE

.github/workflows/playwright.yml

name: playwright
on:
  pull_request:
  push:
    branches: [main]

jobs:
  pr-fast:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    container: mcr.microsoft.com/playwright:v1.45.0-jammy
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright test --project=chromium

  cross-browser:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    container: mcr.microsoft.com/playwright:v1.45.0-jammy
    strategy:
      fail-fast: false
      matrix:
        browser: [chromium, firefox, webkit]
        shard: [1, 2, 3]
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright test --project=${{ matrix.browser }} --shard=${{ matrix.shard }}/3

// WHAT INTERVIEWERS LOOK FOR

Project + matrix + shard combination, the smoke-on-PR / full-on-main pattern, and naming the WebKit-vs-Safari nuance.

// COMMON PITFALL

Running all three browsers × all tests on every PR — slow, expensive, and most cross-browser bugs surface in nightly anyway.