Cross-Browser Automation: Proving the Same Playwright Architecture Holds on Firefox and WebKit
- Anuradha Agarwal
- 4 days ago
- 4 min read
Joining partway through? This is Post 7 of Phase 3: Enterprise Scalability, part of my Playwright Automation Testing Udemy course.
Post 1, Scale Test Automation lays out what this series is about.
Post 2, Getting Your Playwright Framework Running gets the Playwright framework running on your own machine.
Post 3, Running in Parallel for the First Time is where a single shared login collides with itself the moment you turn workers up from one to four.
Post 4, Designing and Building Concurrency-Safe Authentication built three classes, IdentityProvider, AuthenticationManager, WorkerResolver, each with one job, safe up to the size of your account pool.
Post 5, Worker Scope, the Fixture, and Closing Out Concurrency wired those three classes in, identity isolation closed and proven.
Post 6, Verifying Test Data Isolation checked whether that was enough on its own. A confirmed gap turned up in checkout, and shared resources got checked and ruled out for now.
Four workers, four identities, both isolation categories accounted for, on one browser, Chromium.
Running 29 tests using 4 workers
Auth state saved for user1 to .../auth/storage/user1.json
Auth state saved for user2 to .../auth/storage/user2.json
Auth state saved for user3 to .../auth/storage/user3.json
Auth state saved for user4 to .../auth/storage/user4.json
Parallel 3 -> user4
Parallel 2 -> user3
Parallel 0 -> user1
Parallel 1 -> user2
15 skipped
14 passed (1.3m)
Why cross-browser matters

Everything built so far- identity isolation, test data isolation- has been proven on Chromium alone. However, Chromium isn't the whole story of who actually uses qa-cart.com.
Real traffic splits across browser engines, and each engine renders, times, and handles JavaScript differently enough that a test suite passing cleanly on one says nothing certain about the others. WebKit in particular has a reputation worth respecting; Safari's engine handles focus, animation timing, and certain form behaviours differently enough from Chromium that automation written and only ever run against one engine can quietly bake in assumptions that don't hold anywhere else.
That's why this matters: a framework that's only ever been proven on Chromium hasn't been proven cross-browser reliable at all; it's been proven Chromium-reliable, which is a narrower and less useful claim.
What actually changes
Modify this file: playwright.config.ts. Two new projects, alongside the existing Chromium one, both depending on setup the same way:
projects: [
{
name: 'setup',
testDir: './helpers',
testMatch: 'auth.setup.ts',
use: {
browserName: 'chromium',
headless: true,
},
},
{
name: 'chromium',
testDir: './tests',
use: {
browserName: 'chromium',
headless: true,
baseURL: process.env.BASE_URL
},
dependencies: ['setup'],
},
{
name: 'firefox',
testDir: './tests',
use: {
browserName: 'firefox',
headless: true,
baseURL: process.env.BASE_URL
},
dependencies: ['setup'],
},
{
name: 'webkit',
testDir: './tests',
use: {
browserName: 'webkit',
headless: true,
baseURL: process.env.BASE_URL
},
dependencies: ['setup'],
},
],
Worth being precise about how workers actually get used once all three projects run together, since it's not what it looks like at first glance. workers: 4 does not mean four workers per browser, twelve total. It means four workers, total, shared across all three projects at once. Playwright pools every test from every project, 14 × 3 = 42 executions, into one combined queue, and hands the next available task to the next available worker, whichever project it happens to belong to. The scheduler doesn't group by project, it just keeps workers busy.

That has a genuine consequence for identity assignment worth stating directly. parallelIndex, and the workerStorageState resolved from it, are properties of the worker slot, not the browser. A single slot, say parallelIndex 0, running as user1 for its whole lifetime, might process a Chromium test, then later pick up a Firefox test, then a WebKit test, still as user1 the entire time, reusing the same cached storage state file across all three. The identity doesn't reset when the browser does, and it doesn't need to, that's exactly why setup staying Chromium-only works at all, the storage state format itself is browser-agnostic, so user1.json authenticates correctly no matter which engine ends up loading it later in that slot's run.
Setup itself stays Chromium-only, deliberately. Authenticating four identities three times over, once per browser, would triple the login traffic against qa-cart.com for zero benefit; the storage state Chromium produces is a standard format every engine can load. One authentication pass, reused by all three browser projects.
Why IdentityProvider and the fixture chain don't need to know
WorkerResolver.resolve() takes a parallelIndex and returns an identity and a storage path; nothing about a browser engine enters that calculation. workerStorageState resolves once per worker and hands back a file path. loggedInPage takes that path and calls browser.newContext({ storageState: ... }), where browser is whatever Playwright's own project configuration handed it, Chromium, Firefox, or WebKit, decided entirely by which project is currently running, not by anything in the fixture chain. The identity architecture sits one layer above the browser question entirely, which is exactly why adding two new projects doesn't touch auth/, fixtures/, or a single test file.

One question worth answering plainly: does cross-browser need more workers, or more identities, to run at all? No. Fewer workers is always safe; one worker and one identity works fine. It just runs slower: every Chromium test in order, then every Firefox test, then every WebKit test, one identity the whole time, since with one worker there's no concurrency, and nothing to collide. One worker needs one identity. Four workers need four. Cross-browser doesn't change that constraint; it only changes how many tasks get queued behind it.
One warning worth stating plainly: playwright.config.ts needs workers fixed to match the identity pool, not left to auto-detect from CPU cores:
workers: process.env.CI ? 1 : 4,Leave it auto-detecting, and a run can may silently exceed the pool
Let's Run The Final Suit Now:
npx playwright test --project=chromium --project=firefox --project=webkit
What's next
Three browsers, one machine, confirmed clean. That machine is still the ceiling, though, and it's worth being honest about a bigger gap than that: everything in this post ran locally, and local was never the actual target, CI is. The next post takes this exact setup, three browsers, four workers, the identity pool, and runs it on GitHub Actions itself, to see what a 2-core hosted runner does with a configuration built on a machine with far more room to work with. Whatever that run shows is what earns the case for sharding.
This is Post 7 of the Phase 3: Enterprise Scalability series. Post 1 · Post 2 · Post 3 · Post 4 · Post 5 · Post 6 · Repo link · Course link



