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=webkitRun all browsers:
npx playwright testWhat 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)
- 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/1Visual 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
browserNamefixture for browser-specific codetest.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