Cross-Browser Sharded Runs
Run the same Playwright suite across Chromium, Firefox, and WebKit in parallel — then read the HTML report.
1 Goal
By the end of this exercise you will have a Playwright project with three browser projects (Chromium, Firefox, WebKit) configured in playwright.config.ts, a small test suite of four UI checks against playwright.dev and example.com, and the ability to run the suite in two parallel shards. You will open the HTML report and identify which browser produced which trace.
2 Install the tool
Playwright requires Node.js 18 or later. Both are free and open source.
- Install Node.js 20 LTS. Pick your operating system.
Download the LTS installer from nodejs.org (choose the Windows Installer
.msi). Run it and accept the defaults. Close and reopen any terminal after install.Either download the
.pkgfrom nodejs.org, or use Homebrew:brew install node@20
Use nvm so you can switch Node versions per project:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc nvm install 20 nvm use 20
- Verify Node and npm. Open a fresh terminal and run:
node --version npm --version
You should see
v20.x.x(or higher) and10.x.x. If either command says "not found", the installer did not update your PATH — close and reopen your terminal or log out and back in. - Create the project folder and install Playwright. Pick any folder for bootcamp exercises; we will call ours
bootcamp-playwright.mkdir bootcamp-playwright cd bootcamp-playwright npm init playwright@latest -- --quiet --browser=chromium --browser=firefox --browser=webkit --install-deps
mkdir bootcamp-playwright cd bootcamp-playwright npm init playwright@latest -- --quiet --browser=chromium --browser=firefox --browser=webkit --install-deps
Accept TypeScript when prompted. The installer downloads browser binaries (~350 MB) — this takes a few minutes on first run.
- Verify Playwright. From inside
bootcamp-playwright:npx playwright --version
Expect something like
Version 1.49.0. If you get "command not found", ensure you are inside the project folder — Playwright is a local dependency, not a global one.
3 Project setup
Delete the generator's example tests so you start clean. Your folder should look like this:
bootcamp-playwright/
package.json
playwright.config.ts
tests/
smoke.spec.ts
.gitignoreRemove tests-examples/ and the default tests/example.spec.ts file if they exist:
Remove-Item -Recurse -Force tests-examples -ErrorAction SilentlyContinue Remove-Item tests\example.spec.ts -ErrorAction SilentlyContinue
rm -rf tests-examples rm -f tests/example.spec.ts
Now replace playwright.config.ts with the three-project configuration below.
4 Write the script
File: playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: [['html', { open: 'never' }], ['list']],
use: {
baseURL: 'https://playwright.dev',
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'] },
},
],
});File: tests/smoke.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Playwright docs smoke', () => {
test('homepage has Playwright in title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/Playwright/);
});
test('get-started link navigates to docs', async ({ page }) => {
await page.goto('/');
await page.getByRole('link', { name: 'Get started' }).first().click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
});
test.describe('example.com smoke', () => {
test('example.com loads with expected heading', async ({ page }) => {
await page.goto('https://example.com');
await expect(page.getByRole('heading', { name: 'Example Domain' })).toBeVisible();
});
test('example.com has more-information link', async ({ page }) => {
await page.goto('https://example.com');
const link = page.getByRole('link', { name: 'More information...' });
await expect(link).toBeVisible();
await expect(link).toHaveAttribute('href', /iana\.org/);
});
});File: package.json — add these scripts to the existing "scripts" object:
{
"scripts": {
"test": "playwright test",
"test:shard1": "playwright test --shard=1/2",
"test:shard2": "playwright test --shard=2/2",
"test:chromium": "playwright test --project=chromium",
"report": "playwright show-report"
}
}--shard=1/2 means: Playwright splits all test files across N shards. Shard 1/2 runs the first half; shard 2/2 runs the second. In CI you run each shard on a different machine, then merge reports. We simulate two machines by opening two terminals on one box.5 Run & verify
First, sanity-check all three browsers on one machine with no sharding:
npx playwright test
Expected output (tail):
Running 12 tests using 4 workers 12 passed (18.3s) To open last HTML report run: npx playwright show-report
Twelve tests = 4 tests × 3 browser projects. Open the report:
npm run report
A browser window opens at http://localhost:9323. Each row shows the test name and browser project. Click a row to see the full trace.
Now shard the run. Open two terminals in the same folder.
Terminal 1:
npx playwright test --shard=1/2
Terminal 2 (start immediately):
npx playwright test --shard=2/2
Each terminal runs six tests (half of twelve). Wall-clock time roughly halves compared with the single-terminal run.
playwright-report/ folder by default, so the second shard overwrites the first's HTML report when run locally. In CI you pass PLAYWRIGHT_HTML_REPORT=reports/shard-1 (etc.) per shard and use npx playwright merge-reports to combine them. That is covered in the challenge below.6 Troubleshooting
browserType.launch: Executable doesn't exist at …
You ran tests before the browser binaries finished downloading, or the download was interrupted. Re-run:
npx playwright install
On Linux you may also need system dependencies:
npx playwright install-deps
Error: Timed out 30000ms waiting for expect(locator).toBeVisible()
The page structure changed or the network is slow. Three things to check:
- Run headed to see what the browser sees:
npx playwright test --headed --project=chromium - Open the trace for the failing test from the HTML report — Playwright records a DOM snapshot at every action.
- If the link text moved (Playwright docs do update), change the
getByRole('link', { name: 'Get started' })selector to match what you see on the page.
Error: Sharding requires a value like --shard=1/3, got "1"
You forgot the denominator. The flag must be --shard=N/M where N is the current shard (1-indexed) and M is the total number of shards. Example: --shard=2/4 is the second of four shards.
WebKit tests pass locally but fail in GitHub Actions on Ubuntu
Ubuntu runners need the WebKit system libraries. Add this to your workflow's install step:
- run: npx playwright install --with-deps webkit
Or install all three browsers with their dependencies:
- run: npx playwright install --with-deps
7 Challenge
Extend the setup
- Merge sharded reports. Run both shards with
PLAYWRIGHT_BLOB_REPORT_OUTPUT_DIR=blob-report-1(and-2) and a--reporter=blobflag, then runnpx playwright merge-reports --reporter=html blob-report-1 blob-report-2to produce a single combined HTML report. Confirm the merged report shows all twelve tests across three browsers. - Add a fourth project — mobile Safari. Add a project entry using
devices['iPhone 14']. Re-run and verify the test count jumps from 12 to 16.