Testing Guide August 15, 2026 11 min read

Playwright Cross-Browser Testing: Chromium, Firefox & WebKit Guide

Your app looks perfect in Chrome. Then a customer opens it in Safari and the layout shatters. Cross-browser testing with Playwright eliminates these surprises — one test suite, three browser engines, zero guesswork.

Browser compatibility bugs are among the most expensive defects in web development. They slip past unit tests, survive code reviews, and only surface when real users encounter them in production. A date picker that works flawlessly in Chrome might render incorrectly in Safari. A drag-and-drop interaction that feels smooth in Firefox might be completely broken in Edge.

Playwright solves this problem at its root. Unlike tools that rely on WebDriver or browser-specific drivers, Playwright ships its own patched versions of Chromium, Firefox, and WebKit. Every test you write runs identically across all three engines with zero configuration changes. This guide covers everything you need to implement production-grade cross-browser testing with Playwright in 2026.


Why Cross-Browser Testing Matters

The modern web runs on three rendering engines: Blink (Chromium-based browsers like Chrome, Edge, Opera, and Brave), Gecko (Firefox), and WebKit (Safari on macOS and iOS). Each engine interprets HTML, CSS, and JavaScript with subtle differences that can break your application for specific user segments.

Browser Market Share Reality

As of 2026, Chrome dominates desktop browsing at roughly 65% market share. But that leaves 35% of your users on other browsers. Safari holds approximately 18% globally and dominates mobile browsing on iOS, where every browser — including Chrome for iOS — is forced to use the WebKit engine. Firefox maintains around 6%, and Edge accounts for roughly 5%.

Ignoring cross-browser testing means potentially breaking the experience for one in three users. For e-commerce sites, that translates directly to lost revenue. For SaaS products, it means increased support tickets and churn.

Common Rendering Differences

  • CSS Grid and Flexbox gaps: Safari historically lagged behind on gap support in flex containers. While modern versions have caught up, older WebKit versions still render differently.
  • Date and time inputs: Native date pickers look and behave completely differently across Chrome, Firefox, and Safari. Some browsers don't support certain input types at all.
  • Scroll behavior: Smooth scrolling, scroll snap, and overscroll containment all have subtle differences across engines.
  • Font rendering: Sub-pixel antialiasing, font weight interpretation, and web font loading behave differently across browsers, causing layout shifts.
  • JavaScript APIs: Clipboard API, Web Bluetooth, WebUSB, and newer APIs may be available in Chromium but absent in Firefox or WebKit.

iOS is WebKit-only: Every browser on iOS — Chrome, Firefox, Edge, Brave — uses the WebKit engine underneath. Testing with Playwright's WebKit project is the only way to catch Safari/iOS bugs without a physical Apple device.


Playwright's Built-In Browser Support

Playwright takes a fundamentally different approach to browser automation than Selenium or Cypress. Instead of relying on external browser drivers (like ChromeDriver or GeckoDriver), Playwright ships its own browser binaries that are patched for automation reliability.

The Three Engines

Chromium. Playwright bundles a specific Chromium build that matches the latest stable Chrome release. This covers Chrome, Edge, Opera, Brave, and all Chromium-based browsers. When you test on Playwright's Chromium, you're testing the same engine that powers 70%+ of all web browsing.

Firefox. Playwright maintains a patched Firefox build specifically for automation. Unlike Selenium's GeckoDriver approach, Playwright's Firefox integration uses the same CDP-like protocol as Chromium, ensuring consistent API behavior. Tests that work on Chromium work on Firefox with the same syntax.

WebKit. This is Playwright's killer feature for cross-browser testing. WebKit is the engine behind Safari, and Playwright is the only major testing framework that supports WebKit natively. You can test Safari behavior on Windows and Linux — no macOS machine required.

Installing Browsers

Terminal
# Install all browsers
npx playwright install

# Install specific browsers only
npx playwright install chromium firefox
npx playwright install webkit

# Install browsers with OS dependencies (CI/Docker)
npx playwright install --with-deps

Playwright downloads browsers to a shared cache directory (~/.cache/ms-playwright on Linux/macOS). Each Playwright version pins specific browser versions, so upgrades are deterministic. When you update Playwright, run npx playwright install again to get the matching browser builds.

Disk space tip: Each browser binary is 150–300 MB. If you only need Chromium for local development, install just that one and let CI handle the full cross-browser matrix.


Configuring Multi-Browser Tests

The projects array in playwright.config.ts is where cross-browser testing comes together. Each project defines a browser, viewport, and any browser-specific settings. When you run your tests, Playwright executes every test file against every project.

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

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

  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },

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

    /* Mobile browsers */
    {
      name: 'mobile-chrome',
      use: { ...devices['Pixel 7'] },
    },
    {
      name: 'mobile-safari',
      use: { ...devices['iPhone 14'] },
    },
  ],
});

Each devices preset provides a complete configuration including viewport, userAgent, deviceScaleFactor, isMobile, and hasTouch. The ...devices['Desktop Chrome'] spread pulls in all those properties at once.

With this configuration, every test file runs five times — once per project. A suite of 50 tests becomes 250 test executions, covering desktop Chromium, Firefox, WebKit, plus mobile Chrome and mobile Safari.

Project names matter. The name field appears in test reports and CLI filtering. Use clear, lowercase names like chromium, firefox, webkit for easy filtering with --project flags.


Running Tests Across All Browsers

Once your projects are configured, Playwright provides flexible CLI commands to run tests across all browsers or target specific ones.

Terminal
# Run all tests on all configured browsers
npx playwright test

# Run tests on a specific browser only
npx playwright test --project=chromium
npx playwright test --project=firefox
npx playwright test --project=webkit

# Run on multiple specific browsers
npx playwright test --project=chromium --project=firefox

# Run a specific test file on all browsers
npx playwright test login.spec.ts

# Run with headed mode to watch (one browser)
npx playwright test --project=chromium --headed

# Run with UI mode for interactive debugging
npx playwright test --ui

Understanding Test Output

When tests run across multiple projects, Playwright's output clearly labels each result with the project name:

Test Output
Running 150 tests using 4 workers

  OK  [chromium] login.spec.ts:5  should login successfully
  OK  [firefox]  login.spec.ts:5  should login successfully
  OK  [webkit]   login.spec.ts:5  should login successfully
  FAIL [webkit]   datepicker.spec.ts:12  should select date range
  OK  [chromium] datepicker.spec.ts:12  should select date range
  OK  [firefox]  datepicker.spec.ts:12  should select date range

  148 passed, 2 failed

This output immediately tells you which browser has the problem. In this example, the date picker test fails only on WebKit — a classic cross-browser bug that would have gone undetected without multi-browser testing.


Browser-Specific Test Behavior

Not every test makes sense on every browser. Some APIs are Chromium-only. Some behaviors are known WebKit bugs. Playwright provides built-in mechanisms to handle browser-specific test logic cleanly.

Skipping Tests Per Browser

tests/clipboard.spec.ts
import { test, expect } from '@playwright/test';

test('should copy to clipboard using Clipboard API',
  async ({ page, browserName }) => {
    // Clipboard API behavior differs in Firefox
    test.skip(browserName === 'firefox',
      'Firefox requires user gesture for clipboard access');

    await page.goto('/editor');
    await page.click('button#copy');
    const clipboardText = await page.evaluate(
      () => navigator.clipboard.readText()
    );
    expect(clipboardText).toBe('Copied content');
  }
);

Conditional Logic Based on Browser

tests/upload.spec.ts
test('should handle file upload',
  async ({ page, browserName }) => {
    await page.goto('/upload');

    // WebKit handles file inputs differently
    if (browserName === 'webkit') {
      await page.setInputFiles('input[type="file"]',
        './fixtures/test-file.pdf');
    } else {
      const [fileChooser] = await Promise.all([
        page.waitForEvent('filechooser'),
        page.click('button#upload'),
      ]);
      await fileChooser.setFiles('./fixtures/test-file.pdf');
    }

    await expect(page.locator('.upload-success'))
      .toBeVisible();
  }
);

Marking Known Bugs

tests/animation.spec.ts
test('should animate modal entrance',
  async ({ page, browserName }) => {
    // Known WebKit bug: CSS animation timing is off
    test.fixme(browserName === 'webkit',
      'WebKit animation timing issue #1234');

    await page.goto('/dashboard');
    await page.click('button#open-modal');
    await expect(page.locator('.modal'))
      .toHaveCSS('opacity', '1');
  }
);

The test.fixme() annotation marks a test as a known issue. It appears in reports as "fixme" rather than "failed" or "skipped," making it easy to track bugs that need resolution without cluttering your CI pipeline with false failures.


Mobile Browser Emulation

Cross-browser testing isn't just about desktop browsers. Mobile traffic accounts for over 60% of web usage globally, and mobile browsers have their own quirks. Playwright's device registry provides presets for dozens of real devices.

playwright.config.ts — Mobile Projects
projects: [
  /* iPhone 14 - Mobile Safari (WebKit) */
  {
    name: 'iphone-14',
    use: {
      ...devices['iPhone 14'],
      // Overrides if needed:
      // locale: 'en-US',
      // geolocation: { longitude: -73.935, latitude: 40.730 },
    },
  },

  /* iPhone 14 Pro Max - larger viewport */
  {
    name: 'iphone-14-pro-max',
    use: { ...devices['iPhone 14 Pro Max'] },
  },

  /* Pixel 7 - Mobile Chrome (Chromium) */
  {
    name: 'pixel-7',
    use: { ...devices['Pixel 7'] },
  },

  /* iPad Pro 11 - Tablet Safari */
  {
    name: 'ipad-pro',
    use: { ...devices['iPad Pro 11'] },
  },

  /* Galaxy S23 - Samsung browser (Chromium) */
  {
    name: 'galaxy-s23',
    use: { ...devices['Galaxy S III'],
      viewport: { width: 360, height: 780 },
    },
  },
],

What Device Presets Configure

Each device preset sets multiple properties simultaneously:

  • viewport: Exact screen dimensions (e.g., 390x844 for iPhone 14)
  • userAgent: The device's actual user agent string, which affects server-side rendering and responsive breakpoints
  • deviceScaleFactor: Retina/HiDPI pixel ratio (2x for most modern phones, 3x for Pro models)
  • isMobile: Enables mobile-specific behaviors like touch scrolling and viewport meta tag handling
  • hasTouch: Enables touch events instead of mouse events, catching hover-dependent UI that breaks on touch devices

List all available devices: Run npx playwright show-devices in your terminal to see every built-in preset with its full configuration.


Parallel Cross-Browser Execution

Running tests across five browser projects sounds expensive, but Playwright's parallel execution makes it fast. The key settings are workers, fullyParallel, and sharding.

Workers and Parallelism

playwright.config.ts
export default defineConfig({
  // Run all tests in all files in parallel
  fullyParallel: true,

  // Number of parallel worker processes
  // Default: half of CPU cores
  workers: process.env.CI ? 4 : undefined,

  // Retry failed tests (catches flaky network issues)
  retries: process.env.CI ? 2 : 0,
});

fullyParallel: true means tests within the same file run in parallel, not just across files. Without this flag, tests in a single file run sequentially (useful when tests in a file depend on order).

workers controls how many parallel processes Playwright spawns. On a CI machine with 4 CPU cores, setting workers: 4 runs 4 tests simultaneously. Locally, undefined lets Playwright auto-detect your CPU count.

Sharding for CI

For large cross-browser suites, sharding splits the entire test run across multiple CI machines:

Terminal
# Machine 1: runs first quarter of all tests
npx playwright test --shard=1/4

# Machine 2: runs second quarter
npx playwright test --shard=2/4

# Machine 3: runs third quarter
npx playwright test --shard=3/4

# Machine 4: runs fourth quarter
npx playwright test --shard=4/4

Sharding distributes tests evenly across machines, including all browser projects. A suite of 200 tests across 3 browsers (600 total executions) sharded across 4 machines means each machine runs ~150 executions. Combined with 4 workers per machine, you get 16-way parallelism.


Cross-Browser Testing in CI/CD

The real power of cross-browser testing emerges in CI/CD. Every pull request should run your test suite against all target browsers before merge. GitHub Actions makes this straightforward with its matrix strategy.

.github/workflows/playwright.yml
name: Playwright Cross-Browser Tests

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

jobs:
  test:
    timeout-minutes: 30
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        project: [chromium, firefox, webkit]
        shard: [1/3, 2/3, 3/3]

    steps:
      - uses: actions/checkout@v4

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

      - run: npm ci

      - run: npx playwright install --with-deps ${{ matrix.project }}

      - name: Run tests
        run: |
          npx playwright test \
            --project=${{ matrix.project }} \
            --shard=${{ matrix.shard }}

      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: report-${{ matrix.project }}-${{ strategy.job-index }}
          path: playwright-report/
          retention-days: 14

How This Works

The matrix strategy creates 9 parallel CI jobs (3 browsers x 3 shards). Each job installs only the browser it needs (--with-deps ${{ matrix.project }}), reducing install time and disk usage. The fail-fast: false setting ensures all browser jobs complete even if one fails, giving you a complete cross-browser picture.

This approach scales linearly. A test suite that takes 15 minutes on a single machine completes in under 2 minutes with this 9-way parallelism. Add more shards to scale further.

Cost optimization: Install only the browser you need per job. npx playwright install --with-deps chromium downloads ~250 MB instead of ~750 MB for all three browsers, cutting CI setup time by 60%.


Common Cross-Browser Issues and Fixes

After running thousands of cross-browser tests, certain patterns emerge. Here are the most common browser-specific issues and how to handle them in Playwright.

WebKit Date Handling

WebKit's Date constructor is stricter than Chromium's. Date strings without explicit timezone offsets may be interpreted differently:

Do
new Date('2026-08-15T10:00:00Z')

Use ISO 8601 with explicit UTC timezone

Don't
new Date('2026-08-15 10:00:00')

Ambiguous format — parsed as UTC in Chrome, local time in Safari

Firefox Focus Behavior

Firefox handles focus events differently than Chromium. Elements may not receive focus until they're scrolled into view, and document.activeElement can return body instead of the expected element:

Fix: Explicit focus in Firefox
// Instead of clicking and assuming focus:
await page.locator('input#email').click();

// Explicitly focus first, then interact:
await page.locator('input#email').focus();
await page.locator('input#email').fill('user@example.com');
await expect(page.locator('input#email')).toBeFocused();

Chromium-Specific APIs

Some APIs only exist in Chromium. If your application uses these, you need to either skip tests on other browsers or provide fallback assertions:

  • Web Bluetooth / Web USB: Skip on Firefox and WebKit entirely
  • Clipboard API (async): Firefox requires user activation; test with page.evaluate workarounds
  • CSS Container Queries: Fully supported in Chromium and Firefox, partial in older WebKit
  • View Transitions API: Chromium-only in 2026; skip on Firefox and WebKit

Scroll and Animation Differences

Smooth scrolling, CSS scroll-snap, and animations can behave differently across browsers. For assertions on scroll position or animated elements, add explicit waits:

Robust scroll assertion
// Wait for scroll to complete (works cross-browser)
await page.locator('#section-pricing')
  .scrollIntoViewIfNeeded();
await page.waitForTimeout(500); // allow smooth scroll to settle
await expect(page.locator('#section-pricing'))
  .toBeInViewport();

Cross-Browser Testing with Claude AI

Writing cross-browser tests manually means anticipating differences you haven't encountered yet. Claude AI changes this equation. By integrating Claude AI with Playwright through the MCP Server, you can generate browser-aware test suites that automatically account for known cross-browser quirks.

How AI Accelerates Cross-Browser Testing

Claude AI understands the rendering differences between Chromium, Firefox, and WebKit. When you describe a feature to test, Claude generates tests with built-in browser-specific handling — adding test.skip() annotations for unsupported APIs, using cross-browser-safe selectors, and including conditional logic where browsers diverge.

Instead of discovering a WebKit date parsing bug after it reaches production, Claude proactively generates tests that cover known WebKit quirks. Instead of manually writing separate test paths for Firefox's focus behavior, Claude builds the conditional logic into the generated tests from the start.

What You'll Learn in the Course

Course Coverage for Cross-Browser Testing

  • Configure multi-browser projects from scratch
  • Generate browser-aware tests with Claude AI
  • Mobile device emulation for iOS and Android
  • Parallel execution and CI/CD sharding
  • Debug cross-browser failures with Trace Viewer
  • Handle browser-specific APIs and quirks
  • MCP Server integration for AI-powered QA
  • Visual regression across all browsers

Cloud Cross-Browser Testing: BrowserStack & LambdaTest

Running cross-browser tests locally works for development, but CI environments need real browsers on real operating systems. Cloud platforms give you access to hundreds of browser/OS combinations without managing infrastructure.

BrowserStack with Playwright

BrowserStack Automate supports Playwright natively. Connect by setting the remote WebSocket endpoint in your config:

playwright.config.ts — BrowserStack
import { defineConfig } from '@playwright/test';

const BS_CAPABILITIES = {
  browser: 'chrome',
  browser_version: 'latest',
  os: 'Windows',
  os_version: '11',
  'browserstack.username': process.env.BS_USERNAME,
  'browserstack.accessKey': process.env.BS_ACCESS_KEY,
};

export default defineConfig({
  use: {
    connectOptions: {
      wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(JSON.stringify(BS_CAPABILITIES))}`,
    },
  },
});

LambdaTest with Playwright

playwright.config.ts — LambdaTest
const LT_CAPABILITIES = {
  browserName: 'Chrome',
  browserVersion: 'latest',
  'LT:Options': {
    platform: 'Windows 10',
    username: process.env.LT_USERNAME,
    accessKey: process.env.LT_ACCESS_KEY,
    project: 'Playwright Cross-Browser Suite',
    build: 'CI Build',
  },
};

export default defineConfig({
  use: {
    connectOptions: {
      wsEndpoint: `wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(JSON.stringify(LT_CAPABILITIES))}`,
    },
  },
});

Video Recording Per Browser

Capture video per browser to debug cross-browser failures without reproducing them locally:

playwright.config.ts — video per browser
export default defineConfig({
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'], video: 'retain-on-failure' },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'], video: 'retain-on-failure' },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'], video: 'retain-on-failure' },
    },
  ],
});

retain-on-failure only saves videos for failing tests, keeping storage costs low. Videos are available in the test-results/ directory and are embedded in the HTML report.

Frequently Asked Questions

Does Playwright support all major browsers?

Yes. Playwright natively supports Chromium (Chrome, Edge, Opera, Brave), Firefox, and WebKit (Safari). These three engines cover over 95% of global browser usage. Playwright ships its own browser binaries, so you don't need to install browsers separately or manage driver versions like you would with Selenium.

How do I run Playwright tests on multiple browsers at once?

Define multiple projects in your playwright.config.ts file, each targeting a different browser. When you run npx playwright test, all projects execute by default. You can filter with --project=firefox to run on a single browser, or combine flags like --project=chromium --project=webkit to run on specific browsers.

Can I test mobile browsers with Playwright?

Yes. Playwright includes a built-in device registry with presets for iPhone, iPad, Pixel, Galaxy, and dozens more. Each preset configures viewport, userAgent, deviceScaleFactor, and touch support. This lets you test mobile Safari (WebKit) and mobile Chrome (Chromium) alongside desktop browsers in the same test run.

How do I handle tests that only work on specific browsers?

Use the built-in browserName fixture with test.skip() or test.fixme(). For example, test.skip(browserName === 'webkit', 'WebKit does not support this API') skips a test only on Safari. Use test.fixme() for known bugs you plan to fix, so they appear separately in reports.

What is the fastest way to run cross-browser tests in CI/CD?

Use GitHub Actions matrix strategy to run each browser as a separate parallel job, combined with Playwright's sharding (--shard=1/4) to split tests within each browser across multiple machines. This can reduce a 30-minute cross-browser suite to under 5 minutes. Install only the needed browser per job with npx playwright install --with-deps chromium to further cut setup time.

Does Playwright work with BrowserStack?

Yes. BrowserStack Automate natively supports Playwright via a WebSocket CDP endpoint. Configure your playwright.config.ts to use connectOptions.wsEndpoint pointing to BrowserStack's CDP URL with your credentials and browser capabilities encoded as query parameters. This gives you access to real browsers on real Windows, macOS, and Android/iOS devices.

How do I record videos per browser in Playwright?

Set video: 'retain-on-failure' in each project's use config. Videos are saved per test to test-results/ and embedded in the HTML report. Use 'on' to always record, 'retain-on-failure' to only keep failing test videos (recommended for CI), or 'off' to disable. Video recording adds roughly 10–15% overhead to test execution time.

Why do WebKit tests behave differently from Safari?

Playwright's WebKit engine is built from the same open-source WebKit codebase as Safari but ships without Safari's proprietary extensions (like some Apple-specific APIs and DRM). For most web app testing, WebKit coverage equals Safari coverage. Exceptions include Apple Pay, some media APIs, and iCloud-specific features. For full Safari coverage on real devices, use BrowserStack or LambdaTest with Safari on macOS.

How do I run a GitHub Actions matrix for cross-browser testing?

Define a matrix strategy with browser: [chromium, firefox, webkit] and pass --project=${{ matrix.browser }} to the test command. Each browser runs as a separate parallel job. Install only the needed browser per job with npx playwright install --with-deps ${{ matrix.browser }} to avoid downloading all three binaries on every job.


Asim Noaman - Playwright and Claude AI course instructor

Asim Noaman

Senior QA Automation Engineer & AI Testing Specialist

With years of hands-on experience building test automation frameworks for production applications, Asim specializes in combining traditional QA methodologies with cutting-edge AI tools. He has helped teams adopt Playwright and AI-driven testing workflows to ship faster with fewer bugs.

Udemy Instructor Published course author
Playwright + AI Expert Specialized in AI-powered QA
Production Experience Enterprise-grade frameworks
Connect on LinkedIn