Safari accounts for 27% of mobile web traffic, and every iOS browser — including Chrome on iPhone — runs on WebKit under the hood, which means skipping WebKit means skipping your iPhone users. Chrome and Edge share the same Chromium engine, so they together cover roughly 78% of desktop traffic with a single browser project. This guide covers how to tier browser coverage by user risk, configure Playwright projects for each engine, and keep cross-browser CI time from tripling.

Why Browsers Still Differ

Modern browsers (Chrome, Firefox, Safari, Edge) all support the same HTML, CSS, and JavaScript standards. But differences remain:

CSS rendering: Flexbox gaps, grid behaviors, font rendering, shadow DOM — browsers implement specs slightly differently. JavaScript APIs: Some newer APIs aren't supported in older Safari versions. Array.at(), structuredClone(), ResizeObserver — all have varying support. Form elements: Date pickers, file inputs, select elements — browsers style and behave differently. PDF rendering: Safari handles PDFs differently than Chrome. WebKit-specific: iOS uses WebKit for all browsers (yes, Chrome on iPhone is WebKit under the hood). Safari on Mac = Safari on iPhone.

Browser Market Share (2025)

| Browser | Desktop | Mobile |

|---------|---------|--------|

| Chrome | ~65% | ~66% |

| Safari | ~12% | ~27% |

| Firefox | ~3% | ~1% |

| Edge | ~13% | ~3% |

Key insight: Safari is critical for mobile (iPhone is WebKit). Edge is Chrome-based (Chromium). Firefox is low market share but important for enterprise and specific regions.

The Pragmatic Strategy

You can't test everything in every browser. Here's a practical hierarchy:

Tier 1: Always test (every sprint)

  • Chrome — largest market share, primary development browser
  • Safari — critical for iOS/macOS users

Tier 2: Test before major releases

  • Firefox — smaller market share but distinct rendering
  • Edge — Chromium-based, low additional risk

Tier 3: Test for specific features

  • Safari iOS — when testing mobile-specific flows
  • Chrome Android — mobile-specific behaviors
  • Specific browser versions — when enterprise clients require them

Playwright Cross-Browser Setup

Playwright supports Chrome, Firefox, and WebKit (Safari's engine) out of the box:

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

export default defineConfig({
  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'] },
    },
  ],
});

Run a specific browser:

npx playwright test --project=webkit

Run all browsers:

npx playwright test

What to Test Cross-Browser (and What Not To)

Test cross-browser:
  • Core user flows (login, checkout, key workflows)
  • Forms — especially date pickers, file uploads, selects
  • CSS layout — especially flex/grid-heavy designs
  • JavaScript-heavy features (complex interactions)
  • Mobile-specific flows (touch, viewport, orientation)
Don't need cross-browser testing:
  • Unit tests — browser-independent
  • API tests — no browser involved
  • Backend logic tests
  • Internal admin tools that only your team uses

Handling Browser-Specific Failures

When a test fails in one browser only:

test('file upload', async ({ page, browserName }) => {
  // Skip in WebKit — file input behavior differs significantly
  test.skip(browserName === 'webkit', 'WebKit handles file inputs differently');
  
  await page.goto('/upload');
  await page.setInputFiles('[data-testid="file-input"]', 'test-file.pdf');
  await expect(page.getByTestId('upload-success')).toBeVisible();
});

When you need browser-specific behavior:

test('date picker works', async ({ page, browserName }) => {
  await page.goto('/book-appointment');
  
  if (browserName === 'webkit') {
    // Safari's date picker requires different interaction
    await page.fill('[data-testid="date-input"]', '2026-06-15');
  } else {
    await page.click('[data-testid="date-input"]');
    await page.click('[data-date="2026-06-15"]');
  }
  
  await expect(page.getByTestId('selected-date')).toContainText('June 15');
});

BrowserStack and Sauce Labs

Playwright's built-in browsers use open-source versions. For testing on actual Chrome (not Chromium), real Safari, and specific browser versions, use cloud testing services.

BrowserStack

// playwright.config.ts for BrowserStack
const capabilities = {
  'browserstack.user': process.env.BROWSERSTACK_USERNAME,
  'browserstack.key': process.env.BROWSERSTACK_ACCESS_KEY,
};

export default defineConfig({
  projects: [
    {
      name: 'chrome@latest-mac',
      use: {
        connectOptions: {
          wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(JSON.stringify({
            ...capabilities,
            browserName: 'chrome',
            browserVersion: 'latest',
            'bstack:options': {
              os: 'OS X',
              osVersion: 'Sonoma',
            }
          }))}`,
        },
      },
    },
  ],
});

When cloud services are worth it

  • You need real Safari (not WebKit) on real macOS
  • Specific Chrome version (enterprise clients locked to Chrome 110)
  • Real iOS device testing (not simulator)
  • IE11 support (rare, but still exists in finance/government)

Responsive Testing

Test at multiple viewport sizes, not just mobile vs desktop:

// playwright.config.ts
projects: [
  {
    name: 'desktop',
    use: { viewport: { width: 1920, height: 1080 } },
  },
  {
    name: 'laptop',
    use: { viewport: { width: 1280, height: 720 } },
  },
  {
    name: 'tablet',
    use: { viewport: { width: 768, height: 1024 } },
  },
  {
    name: 'mobile',
    use: { viewport: { width: 375, height: 667 } },  // iPhone SE
  },
],

Within a test:

test('navigation works on mobile', async ({ page }) => {
  await page.setViewportSize({ width: 375, height: 667 });
  await page.goto('/');
  
  // Mobile: hamburger menu instead of nav bar
  await expect(page.getByTestId('hamburger-menu')).toBeVisible();
  await expect(page.getByTestId('desktop-nav')).not.toBeVisible();
  
  await page.getByTestId('hamburger-menu').click();
  await expect(page.getByTestId('mobile-nav')).toBeVisible();
});

Managing Cross-Browser Failures in CI

Run browsers in parallel to keep CI fast:

# GitHub Actions matrix strategy
strategy:
  matrix:
    browser: [chromium, firefox, webkit]

steps:
  - run: npx playwright test --project=${{ matrix.browser }}

Or shard cross-browser tests:

strategy:
  matrix:
    include:
      - browser: chromium
        shard: 1/2
      - browser: chromium  
        shard: 2/2
      - browser: firefox
        shard: 1/1  # Fewer Firefox tests
      - browser: webkit
        shard: 1/1

Visual Cross-Browser Testing

CSS rendering differences are often subtle. Manual comparison is painful. Use Percy or Playwright's screenshot comparison:

test('homepage renders correctly', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('homepage.png', {
    maxDiffPixelRatio: 0.02,  // 2% pixel difference allowed
  });
});

Screenshots are stored per project — each browser gets its own baseline.

Summary

Practical cross-browser strategy:

1. Run full suite in Chrome on every commit

2. Run Chrome + Safari before every release

3. Run all browsers (including Firefox, Edge) before major releases

4. Test mobile viewports for responsive features

Playwright makes cross-browser easy:
  • Define projects for each browser
  • browserName fixture for browser-specific code
  • test.skip() for known incompatibilities
  • Built-in WebKit = Safari without macOS required

The goal isn't to test everything in every browser — it's to catch the browser-specific bugs that actually affect your users, using the time you have.

→ See also: Cross-Browser Testing with Playwright: Chrome, Firefox, Safari | Mobile Emulation in Playwright: Responsive and Touch Testing | Parallel Execution in Playwright: Workers, Shards, and Sharding for Speed