QA How-To
How to Set up cross browser testing (2026)
Learn how to set up cross browser testing with analytics-driven matrices, Playwright projects, CI tiers, cloud devices, and maintainable QA workflows for 2026.
19 min read | 2,409 words
TL;DR
To set up cross browser testing, pick browsers from analytics, implement Playwright multi-project engines, tag critical tests, run Chromium on every PR, and schedule deeper Firefox/WebKit/real-device coverage without exploding runtime.
Key Takeaways
- Build the browser matrix from analytics and risk, not from maximum device catalogs.
- Use Playwright projects for Chromium, Firefox, and WebKit engine coverage.
- Tag critical journeys and run only those across browsers on a schedule.
- Keep PR feedback on Chromium; expand engines and real devices in nightly tiers.
- Stabilize flakes on one browser before multiplying browsers.
- Separate responsive viewport tests from real device hardware tests.
- Assign owners for matrix policy, minutes, and triage SLOs.
Cross browser testing verifies that critical user journeys behave correctly across the browsers, engines, and devices your customers actually use. This guide explains how to set up cross browser testing in 2026: choose a matrix from analytics, structure Playwright and Selenium projects, decide local versus cloud, wire CI tiers, control cost and flakes, and keep the program maintainable as browser share shifts.
"Test on every browser" is not a strategy. A good setup is a small, justified matrix with fast feedback for developers and deeper coverage before release. If you only remember one idea: coverage without prioritization is just expensive noise.
TL;DR
| Layer | What runs | Where | When |
|---|---|---|---|
| Unit/component | pure logic, UI components | local CI | every PR |
| UI smoke | top journeys, Chromium | local/Docker | every PR |
| Compatibility | same smokes + critical paths | Firefox/WebKit/cloud | nightly |
| Release matrix | agreed browsers/devices | cloud farm | pre-release |
How to set up cross browser testing starts with analytics and risk, not with buying the largest device cloud plan.
1. How to set up cross browser testing: goals and non-goals
Goals:
- catch engine-specific functional breaks (Safari CSS, Firefox event quirks)
- verify critical revenue and auth paths on real customer browsers
- produce evidence (screenshots, videos, logs) developers trust
- finish PR feedback in minutes, not hours
Non-goals:
- pixel-perfect identity across engines (that is visual testing with intent)
- full regression of every test on every browser
- replacing unit tests with more UI browsers
- chasing 100% of obscure browser versions
Write goals in the repo so product stakeholders cannot silently expand scope. Cross browser testing is a product risk program with an engineering implementation.
2. Build the matrix from data, not folklore
Collect:
- web analytics browser and OS share for the last 90 days
- support tickets tagged by browser
- revenue by device class if available
- markets where older browsers remain common
Example decision table:
| Browser / engine | Share (illustrative) | Include? | Tier |
|---|---|---|---|
| Chromium desktop | high | yes | PR |
| Mobile Chrome Android | high | yes | nightly |
| Safari iOS | high for consumer | yes | nightly |
| Safari macOS | medium | yes | nightly |
| Firefox desktop | low-medium | maybe | weekly |
| Legacy Edge HTML | near zero | no | none |
| IE11 | zero for modern apps | no | none |
Replace illustrative share with your numbers. Revisit quarterly. How to set up cross browser testing without analytics is guessing; guessing becomes permanent spend.
3. Choose execution backends
| Backend | Strengths | Weaknesses |
|---|---|---|
| Playwright local (Chromium/Firefox/WebKit) | fast, free engines, great tooling | not full commercial browser builds or real iOS hardware |
| Docker browsers | reproducible CI | ops care, still not real devices |
| Cloud (BrowserStack, LambdaTest) | real devices, Safari/iOS depth | cost, latency |
| Self-hosted grid | control, residency | heavy ops |
Recommended default for many SaaS teams in 2026:
- Playwright projects for Chromium + Firefox + WebKit in CI images for engine coverage
- cloud real devices for iOS Safari and key Android models on schedule
- keep Selenium only if legacy suites require it
See how to run tests on BrowserStack and how to run tests on LambdaTest for vendor wiring details.
4. Playwright multi-project setup (engines)
Playwright's project model is the cleanest open-source path to cross-engine UI tests.
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
timeout: 60_000,
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
reporter: [["list"], ["html", { open: "never" }]],
use: {
baseURL: process.env.APP_URL || "http://127.0.0.1:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
{
name: "mobile-chrome",
use: { ...devices["Pixel 7"] },
},
{
name: "mobile-safari",
use: { ...devices["iPhone 14"] },
},
],
});
Run one project on PR:
npx playwright test --project=chromium
Run engines nightly:
npx playwright test --project=chromium --project=firefox --project=webkit
Device descriptors emulate viewports and user agents; they are not real iPhones. Use cloud devices when hardware matters.
5. Tag tests by importance
Not every test belongs on every browser.
import { test, expect } from "@playwright/test";
test.describe("auth", { tag: "@critical" }, () => {
test("login with valid user", async ({ page }) => {
await page.goto("/login");
await page.getByLabel("Email").fill("user@example.com");
await page.getByLabel("Password").fill(process.env.E2E_PASSWORD!);
await page.getByRole("button", { name: "Sign in" }).click();
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
});
});
test("help center search", { tag: "@extended" }, async ({ page }) => {
await page.goto("/help");
await page.getByPlaceholder("Search").fill("billing");
await page.getByRole("button", { name: "Search" }).click();
await expect(page.getByTestId("search-results")).toBeVisible();
});
CI mapping:
- PR:
@criticalon chromium - nightly:
@criticalon all engine projects - weekly:
@extendedon chromium + one mobile project
This is the operational heart of how to set up cross browser testing that teams actually keep green.
6. Selenium multi-browser factory (legacy-friendly)
public class DriverFactory {
public static WebDriver create(String browser) {
return switch (browser.toLowerCase()) {
case "chrome" -> new ChromeDriver(new ChromeOptions());
case "firefox" -> new FirefoxDriver(new FirefoxOptions());
case "edge" -> new EdgeDriver(new EdgeOptions());
default -> throw new IllegalArgumentException("Unknown browser: " + browser);
};
}
}
BROWSER=firefox mvn test -Dtest=SmokeIT
For remote:
ChromeOptions options = new ChromeOptions();
options.setCapability("browserVersion", "latest");
// merge cloud vendor options here when needed
WebDriver driver = new RemoteWebDriver(new URL(hubUrl), options);
Prefer one abstraction for local and remote so tests never branch on vendor APIs.
7. Stabilize tests before multiplying browsers
Multiplying browsers multiplies flakes. Before enabling Firefox and WebKit in CI:
- replace hard sleeps with condition waits
- prefer role/label/test id locators
- isolate test data
- stub flaky third parties on PR suites
- fix strict mode and visibility issues on Chromium first
A suite that is 95% reliable on one browser and then expanded to five becomes a pager nightmare. Stabilize, then expand. Related guidance: how to reduce flaky tests in a CI pipeline.
8. How to set up cross browser testing in CI tiers
PR workflow (fast):
name: pr-ui
on: [pull_request]
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run start:test &
- run: npx wait-on http://127.0.0.1:3000
- run: npx playwright test --project=chromium --grep @critical
- if: always()
uses: actions/upload-artifact@v4
with:
name: pr-playwright
path: playwright-report
Nightly workflow (engines):
name: nightly-cross-browser
on:
schedule:
- cron: "0 4 * * *"
workflow_dispatch:
jobs:
engines:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run start:test &
- run: npx wait-on http://127.0.0.1:3000
- run: npx playwright test --project=chromium --project=firefox --project=webkit --grep @critical
- if: always()
uses: actions/upload-artifact@v4
with:
name: nightly-engines
path: |
playwright-report
test-results
Add a third workflow for cloud real devices if needed. Do not merge all tiers into one job.
9. Visual differences vs functional differences
Cross browser testing is primarily functional. Engines will render fonts, subpixels, and form controls differently. Policy:
- assert user-observable behavior and accessibility roles
- avoid asserting exact CSS pixel values unless you own a visual regression system
- when layout is business critical, add dedicated visual tests with thresholds (covered later in our visual regression CI guide)
Example of a good cross-browser assertion:
await expect(page.getByRole("alert")).toHaveText("Payment declined");
Example of a fragile one:
await expect(page.locator(".total")).toHaveCSS("margin-top", "12px");
10. Mobile web and responsive coverage
Cover viewports that match design breakpoints:
test.use({ viewport: { width: 390, height: 844 } });
test("mobile nav opens", async ({ page }) => {
await page.goto("/");
await page.getByRole("button", { name: "Menu" }).click();
await expect(page.getByRole("navigation")).toBeVisible();
});
Separate:
- responsive layout tests (viewport emulation)
- mobile browser engine tests (WebKit/Chromium mobile)
- real device tests (cloud hardware)
Each answers a different question. Collapsing them into one "mobile test" confuses failures.
11. Feature detection and progressive enhancement
Some differences are intentional. Document known engine gaps:
- a CSS feature with fallback
- a Web API gated behind capability checks
- polyfills for older mobile webviews
Tests should follow product intent. If Safari intentionally uses a fallback flow, assert the fallback, not the Chromium-only path. Maintain a short "known differences" doc next to the matrix.
12. Ownership, dashboards, and SLOs
Assign:
- matrix owner (usually QA platform)
- per-area test owners for failures
- browser budget owner for cloud minutes
SLOs examples:
- PR chromium
@criticalgreen rate above an agreed threshold - nightly engine suite completes within N minutes
- open cross-browser defects triaged within one business day
Dashboard panels:
- pass rate by project (chromium/firefox/webkit)
- duration by project
- top failing tests by browser
- cloud minute burn
Without ownership, matrices rot.
13. Cost and runtime control techniques
- Run only impacted packages in monorepos.
- Shard long nightly jobs.
- Cache browser downloads and npm.
- Prefer API setup over UI setup.
- Drop browsers with near-zero share.
- Keep videos on failure only.
- Stop on first failure for PR; full continue-on-error matrix for nightly diagnostics when useful.
Runtime is a product feature of the test system. Treat it like latency budgets.
14. Rollout plan (three weeks)
Week 1: chromium PR smoke with tags; fix flakes; publish report artifacts.
Week 2: add firefox and webkit on nightly for @critical only; triage engine issues daily.
Week 3: add one real-device cloud smoke if iOS/Android revenue warrants it; document matrix policy; set calendars for quarterly review.
Do not enable five cloud devices on day one.
15. Comparison table: strategies
| Strategy | PR feedback | Compatibility depth | Cost | Ops load |
|---|---|---|---|---|
| Chromium only | Excellent | Weak | Low | Low |
| Playwright 3 engines | Good | Good for engines | Low | Low |
| Engines + cloud devices | Good | Excellent | Medium-high | Medium |
| Full cloud everything | Poor | Excellent | High | Medium |
| Self-hosted everything | Variable | Variable | High ops | High |
Most product teams land on row three.
21. Environment parity and test data for multi-browser runs
Cross browser failures are often environment failures wearing a browser costume. Align:
- the same APP_URL and API stubs across projects
- identical seed scripts before each CI job
- clock and locale defaults (
TZ=UTC) - authentication state setup that does not depend on a browser-specific password manager quirk
// tests/auth.setup.ts
import { test as setup, expect } from "@playwright/test";
setup("authenticate", async ({ page }) => {
await page.goto("/login");
await page.getByLabel("Email").fill(process.env.E2E_USER!);
await page.getByLabel("Password").fill(process.env.E2E_PASSWORD!);
await page.getByRole("button", { name: "Sign in" }).click();
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
await page.context().storageState({ path: "playwright/.auth/user.json" });
});
Wire storageState into projects carefully: regenerate auth state per browser if cookies or storage differ, or use API login tokens that are browser-agnostic. Document the choice so nightly engine jobs do not flake on expired storage alone.
22. Reporting that makes multi-browser failures readable
A raw list of 40 failures across three engines is unusable. Aggregate by:
- test identity
- project/browser
- error fingerprint
Surface a small markdown summary in CI:
npx playwright test --project=chromium --project=firefox --reporter=json
node scripts/summarize-by-browser.mjs > browser-summary.md
The summary should answer: is this chromium-only, all engines, or data setup? That single distinction routes the ticket to frontend, QA, or platform without a meeting.
Also keep HTML reports per job and name artifacts nightly-firefox rather than overwriting report. Historical comparison needs unique artifact names per matrix cell when you use cloud devices.
23. Security considerations for cross browser CI
Multi-browser pipelines multiply secret exposure surface:
- more jobs reading
E2E_PASSWORD - cloud vendors receiving session traffic
- screenshots and videos of authenticated pages
Controls:
- least-privilege CI secrets scoped to environments
- synthetic users with no production access
- artifact retention limits on authenticated runs
- no real PII in seed data
- separate cloud keys per product when possible
Cross browser testing is still security-sensitive QA infrastructure. Treat browser farms as third-party processors when legal review requires it.
24. How to set up cross browser testing: acceptance checklist
You have successfully completed how to set up cross browser testing when:
- Analytics-backed matrix is written in the repo.
- Chromium critical smoke gates PRs reliably.
- Nightly multi-engine runs exist with owners.
- Real devices are intentional, not accidental.
- Flake rate is measured per browser project.
- Reports answer "which engines failed" in under a minute.
- Quarterly review calendar invite exists.
If item 1 or 2 is missing, stop expanding browsers and fix foundations first. Breadth without a stable core is how programs lose credibility.
Interview Questions and Answers
Q: How do you set up cross browser testing for a new web app?
I start with analytics to pick browsers, implement Playwright projects for major engines, tag critical journeys, run chromium on every PR, and schedule multi-engine runs nightly. Real devices join later if mobile share or incidents justify them.
Q: Why not run all tests on all browsers?
Runtime and flake cost explode. Most tests do not exercise engine-specific risk. Critical path coverage on top browsers gives better risk reduction per minute.
Q: Playwright WebKit vs real Safari?
WebKit in Playwright approximates Safari's engine family for many issues but is not a substitute for real iOS Safari hardware when dealing with touch, OS browser chrome, or vendor-specific bugs. Use both at different tiers.
Q: How do you prevent cross-browser flakes?
Stabilize waits and data on one browser first, avoid pixel CSS assertions, stub third parties on PR, and measure flake rate per project after expansion.
Q: How do you pick the matrix?
Analytics share, support volume, revenue paths, and historical escaped defects. Review quarterly. Remove browsers that no longer matter.
Q: Where should cross browser tests run in CI?
Fast chromium smoke on PR; broader engines on nightly; real devices on nightly or pre-release. Keep secrets and reports consistent across jobs.
Q: How is this different from visual regression?
Cross browser testing focuses on behavior across engines and devices. Visual regression focuses on pixel or layout deltas against baselines, often on one or few browsers. They complement each other.
Common Mistakes
- Buying a cloud plan before tagging critical tests.
- Running the entire suite on every browser every PR.
- Treating WebKit emulation as full iOS coverage.
- Asserting exact CSS that differs harmlessly by engine.
- Expanding browsers while Chromium suite is still flaky.
- No owner for matrix or minute budget.
- Skipping artifacts so Safari-only bugs are undebuggable.
- Forgetting responsive breakpoints while chasing browser names.
- Hardcoding cloud keys in configs.
- Never revisiting the matrix after the initial setup.
Conclusion
How to set up cross browser testing is a prioritization exercise implemented with projects, tags, and CI tiers. Use analytics to choose browsers, Playwright engines for affordable depth, cloud devices for hardware truth, and strict suite tagging so PR feedback stays fast.
Next step: add a chromium @critical PR job if you lack one, then schedule firefox and webkit nightly for the same tags. When functional coverage is stable, add visual checks for design-critical pages via how to set up visual regression in CI. For CI plumbing patterns, see add CI to a test framework.
16. Monorepo and multi-app portfolios
Large orgs rarely have one web app. Apply the same matrix principles per app, not once globally:
- consumer marketing site might care deeply about mobile Safari
- internal admin tools might be chromium-only by policy
- embedded webviews may need a specific OS browser matrix
Create a central template workflow, but let each app declare browsers.yaml:
# apps/storefront/browsers.yaml
pr:
- chromium
nightly:
- chromium
- firefox
- webkit
- mobile-chrome
cloud_nightly:
- ios-safari-latest
critical_tags:
- "@critical"
A generator script turns that file into CI jobs. Central QA reviews exceptions. This prevents every team inventing a different definition of cross browser testing.
17. Accessibility intersects cross browser testing
Some a11y behaviors differ by engine and assistive technology. While full screen reader coverage is a specialized practice, you can:
- run axe-core on chromium every PR
- spot-check keyboard paths on WebKit weekly
- avoid engine-specific ARIA anti-patterns
import AxeBuilder from "@axe-core/playwright";
import { test, expect } from "@playwright/test";
test("home has no serious a11y violations", async ({ page }) => {
await page.goto("/");
const results = await new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa"]).analyze();
expect(results.violations).toEqual([]);
});
Do not pretend axe replaces manual AT testing. Do use it as a cheap cross-cutting signal alongside browser engines.
18. Debugging Safari-only and Firefox-only failures
A practical loop:
- Confirm failure is deterministic on that project.
- Capture trace and screenshot.
- Check console errors unique to that engine.
- Binary search recent CSS/JS changes.
- Reproduce with vendor-specific devtools when needed.
- Decide: product fix, progressive enhancement, or test intent wrong.
Common Safari issues historically cluster around date inputs, flex/grid edge cases, third-party script blockers, and storage quirks. Firefox often surfaces stricter standards behavior. Use failures as product quality signals, not as reasons to drop the browser from the matrix silently.
19. Contract with product and design
Engineering alone cannot sustain cross browser scope. Agree in writing:
- supported browser list for customers
- unsupported browsers get a best-effort banner if required
- design review includes at least one non-Chromium screenshot for major UI
- intentional differences are documented
When design ships a Chromium-only CSS feature without fallback, tests will fail for good reasons. The fix might be product, not the test. How to set up cross browser testing includes this social contract, not only YAML.
20. Metrics that prove the program works
Track for two quarters:
- escaped production defects tagged by browser (should trend down for covered browsers)
- nightly wall clock and flake rate by project
- percentage of
@criticalpaths covered on the agreed matrix - mean time to triage multi-browser failures
- cloud spend per critical path
If escaped Safari bugs remain high while WebKit nightly is green, your tests are pointing at the wrong journeys. Adjust coverage, not just tooling.
Interview Questions and Answers
How would you set up cross browser testing for a SaaS dashboard?
I would pull browser share from analytics, define Playwright projects for major engines, tag authentication and billing journeys as critical, run Chromium on each PR, and schedule Firefox and WebKit nightly. If mobile traffic is material, I would add a small real-device cloud smoke. I would document ownership and revisit the matrix quarterly.
How do you keep multi-browser CI from becoming slow?
Limit PR to one browser, tag tests, shard nightly jobs, cache dependencies, prefer API setup, and drop low-value browsers. Runtime budgets are enforced like any other SLO.
What is your approach to Safari-only bugs?
Reproduce on WebKit and real Safari when needed, capture traces, determine whether product or test intent is wrong, and fix with progressive enhancement if the feature is unsupported. I do not delete Safari from the matrix to silence the signal.
How do cloud grids fit the strategy?
Cloud grids provide real devices and commercial browser breadth. I use them for scheduled compatibility, not as the only PR gate, because latency and cost hurt developer feedback.
How do you structure Selenium for multiple browsers?
A driver factory selects Chrome, Firefox, or Edge from an environment variable, with the same tests and page objects. Remote capabilities are added in one place for cloud runs.
Which metrics show cross browser testing is effective?
Fewer production defects on covered browsers, stable nightly pass rates, controlled runtime and spend, and fast triage with good artifacts. Coverage counts alone are not success.
How do you handle intentional browser differences?
Document them with product and design, assert the intended fallback behavior in tests, and avoid pixel-perfect CSS expectations that fight engine reality.
Frequently Asked Questions
How do I set up cross browser testing with Playwright?
Define projects for chromium, firefox, and webkit in playwright.config.ts, tag critical tests, run chromium on PRs, and schedule multi-project runs nightly with traces and screenshots on failure.
Which browsers should I include?
Start from your analytics share, support tickets, and revenue paths. Most teams cover Chromium plus Safari/WebKit and one mobile browser, then add Firefox if share or defects justify it.
Is Playwright WebKit enough for Safari?
It catches many engine issues cheaply but does not replace real iOS Safari hardware for all bugs. Use WebKit in CI and add cloud real devices for high-risk mobile flows.
Should every test run on every browser?
No. Multiply browsers only for critical user journeys. Extended coverage can stay on one browser to control time and cost.
How often should the matrix change?
Review at least quarterly against analytics and escaped defects. Remove near-zero browsers and promote browsers that show real production risk.
What belongs in PR versus nightly CI?
PR: fast Chromium critical smoke. Nightly: multi-engine critical paths and optional real devices. Release: agreed full matrix with human-readable reports.
How is cross browser testing different from visual testing?
Cross browser testing validates behavior across engines and devices. Visual testing compares UI appearance to baselines. Use both when layout risk and functional risk both matter.