Multiple Browsers, Workers and Sharding
On this page
A suite that takes forty minutes gets run less often, and a suite that only runs in Chromium misses the bugs your Safari and Firefox users hit. Playwright Test handles both with three mechanisms: projects decide which browsers and settings a test runs under, workers run tests in parallel on one machine, and sharding splits a run across machines.
Projects: the same tests, different configurations#
A project is a named set of options. Every test runs once per project unless you filter:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
// Branded browsers installed on the machine (not the bundled builds)
{ name: 'google-chrome', use: { ...devices['Desktop Chrome'], channel: 'chrome' } },
{ name: 'edge', use: { ...devices['Desktop Edge'], channel: 'msedge' } },
// Mobile viewport, touch and user agent emulation
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
{ name: 'mobile-safari', use: { ...devices['iPhone 12'] } },
],
});
The devices presets bundle a browser engine, viewport, user agent, device scale factor and touch support. devices['iPhone 12'] runs WebKit with an iPhone-sized viewport and touch events; it is emulation, not an iPhone. That catches responsive layout and touch-handling bugs cheaply, but it will not catch bugs in the real Safari on iOS.
Run a subset with --project (repeatable, and * works as a wildcard):
npx playwright test --project=chromium --project=webkit
npx playwright test --project="mobile-*"
Projects are not only for browsers. You can have a smoke project with testMatch: /.*smoke\.spec\.ts/, a project per environment with a different baseURL, or the setup project from chapter 4.
Browser-specific behaviour#
Occasionally a test cannot run on one engine, for example because it uses a Chromium-only API. Skip it explicitly so the report says so, rather than letting it fail:
test('downloads a PDF', async ({ page, browserName }) => {
test.skip(browserName === 'webkit', 'PDF viewer differs in WebKit');
// ...
});
Use this sparingly. A skip that nobody revisits is coverage you think you have.
You want one project to run against the Google Chrome installed on the machine rather than the bundled Chromium build. What do you set?
channel selects a branded browser. The Desktop Chrome preset only sets engine, viewport and user agent; it still launches the bundled Chromium.
Workers: parallelism on one machine#
Playwright runs tests in separate worker processes, each with its own browser. By default it uses half your logical CPU cores. Control it in the config or on the command line:
workers: process.env.CI ? 2 : undefined,
npx playwright test --workers=4
npx playwright test --workers=50%
Two config settings decide what can run at the same time:
- Without
fullyParallel, files run in parallel with each other, but the tests inside one file run in order, in the same worker. - With
fullyParallel: true(the generated default), individual tests are distributed across workers, so even a single large file is spread out.
You can override that per file or per describe block:
// Tests in this block run in order, in one worker
test.describe.configure({ mode: 'default' });
There is also mode: 'serial', where tests run in order and a failure skips the remaining tests in the group. It exists for genuinely dependent steps, but the Playwright docs recommend making tests isolated instead, and so do we: a serial chain hides which step really broke and cannot be retried piece by piece.
Parallel-safe test data#
Parallel tests break when they share mutable state on the server: the same user, the same cart, the same record. Browser contexts already isolate cookies and storage, so the remaining work is on your data:
- Create the data a test needs inside that test, with unique names (
`todo-${Date.now()}`), or through an API call via therequestfixture. - Give each worker its own account.
testInfo.parallelIndexis a number from0toworkers - 1, unique among workers running at the same moment, and also available asprocess.env.TEST_PARALLEL_INDEX. A worker-scoped fixture can use it to pickuser-0,user-1and so on. - Never assume test order. If a test only passes after another test, it will eventually fail when the scheduler reorders them.
A test passes when you run the whole file, but fails when you run it alone with -g. What is the most likely cause?
A test that only passes after another test will eventually fail when the scheduler reorders them. Make it create its own data.
Sharding: parallelism across machines#
Once one machine is saturated, split the suite into shards and run each on its own CI job:
npx playwright test --shard=1/4
npx playwright test --shard=2/4
npx playwright test --shard=3/4
npx playwright test --shard=4/4
Each command runs roughly a quarter of the tests. With fullyParallel: true, Playwright splits at the level of individual tests, which balances shards better than splitting by file. Without it, whole files are assigned to shards, so one very large file can make one shard much slower than the others.
Each shard produces its own report, which is not much use on its own. Switch to the blob reporter on CI, then merge the results into one HTML report after all shards finish:
reporter: process.env.CI ? 'blob' : 'html',
npx playwright merge-reports --reporter html ./all-blob-reports
Chapter 7 wires this into a GitHub Actions matrix.
Without fullyParallel, shard 3 of 4 takes three times as long as the others. What explains it?
Without fullyParallel, sharding splits by file, so one big file makes one shard slow. With it on, tests are distributed individually and the shards balance.
How much parallelism is enough?#
On a single machine, more workers is not always faster. Each worker runs a real browser, and once CPU or memory is saturated tests slow down and time out, which looks like flakiness. Start at the default, watch for timeouts, and lower workers on small CI runners. That is why the generated config uses workers: 1 on CI: the Playwright docs recommend it for stability and reproducibility on shared CI runners, and suggest scaling out with sharding rather than piling workers onto one small machine.
Sharding moves that ceiling to the number of machines, and running browsers remotely moves it again, because the browsers no longer compete with the test runner for your CPU. In chapter 8 each worker connects to a browser on a remote grid, so worker count is limited by how many sessions your plan allows rather than by your runner's hardware. Our parallel testing overview and parallel calculator help estimate how many sessions a suite actually needs.
For the official reference, see the parallelism and sharding guides.