Playwright Tests Slow? How to Speed Them Up
If your Playwright tests are slow, the fix is usually not Playwright. It is workers set too low, a login repeated in every test, browsers downloaded on every run, or one long serial job. This guide shows how to speed up Playwright tests with real config, including test sharding for Playwright and the Python equivalent. It belongs to the slow e2e tests guide.
Why Playwright test execution is slow
Measure first. Run with the list and JSON reporters, then sort by duration:
npx playwright test --reporter=list,json
Split your CI job time into install, browser install, server start and test run. If the test run is under half the total, fix setup before tests.
Run files and tests in parallel
Playwright runs test files in parallel by default, but tests inside one file run in order. Set fullyParallel to run every test in parallel, and set workers explicitly in CI:
// playwright.config.ts
import { defineConfig } from "@playwright/test";
export default defineConfig({
fullyParallel: true,
workers: process.env.CI ? 4 : undefined,
retries: process.env.CI ? 1 : 0,
use: { trace: "on-first-retry" },
reporter: process.env.CI ? [["blob"], ["list"]] : "list",
});
By default Playwright uses about half the logical CPU cores. On a 2-vCPU runner that means one worker, which is why a small runner makes Playwright feel slow. Do not set workers far above core count: browsers are heavy, and oversubscribed CPUs cause timeouts that look like flakiness.
Log in once with storageState
Logging in through the UI before every test is the most common waste. Log in once in a setup project and reuse the saved state:
projects: [
{ name: "setup", testMatch: /auth\.setup\.ts/ },
{
name: "chromium",
use: { storageState: "playwright/.auth/user.json" },
dependencies: ["setup"],
},
],
Seed test data through your API or database instead of through the UI. If your tests need unique users per worker, create them in a worker-scoped fixture.
Stop waiting for time
Remove page.waitForTimeout(). Use web-first assertions that auto-wait, such as await expect(page.getByRole("button", { name: "Save" })).toBeEnabled(). Wait for a specific response with page.waitForResponse when the UI depends on a request. Block what you do not need, such as analytics and fonts, with page.route, to cut page load time.
Start the app once
Use the webServer option so Playwright starts the app once for the whole run, and in CI run a production build rather than a dev server, which compiles on demand:
webServer: {
command: "npm run start",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
Test sharding with Playwright
Sharding splits the suite across jobs. Playwright has it built in with --shard=x/y. Use the blob reporter so reports can be merged afterwards:
jobs:
e2e:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
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: npx playwright test --shard=${{ matrix.shard }}/4
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: blob-report-${{ matrix.shard }}
path: blob-report
merge:
needs: e2e
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: npm }
- run: npm ci
- uses: actions/download-artifact@v4
with: { path: all-blob-reports, pattern: blob-report-*, merge-multiple: true }
- run: npx playwright merge-reports --reporter html ./all-blob-reports
Shard count: use the test sharding planner for wall-time and cost, and see the sharding guide for balancing. With fullyParallel: true, Playwright balances at test level, which gives more even shards than file-level splitting.
Install only the browser you need
npx playwright install --with-deps chromium downloads one browser instead of three. Playwright's own docs note that caching browser downloads often saves little, because restoring a large cache takes about as long as downloading; measure before adding it. Alternatively run in the official Playwright container image so browsers and system dependencies are already present.
Speed up Playwright with Python
The Python package uses pytest. Run in parallel with pytest-xdist, and reuse authenticated state with a session-scoped fixture:
pip install pytest-playwright pytest-xdist
pytest -n 4 --browser chromium
Each xdist worker is a separate process with its own browser, so data must be isolated per worker. Shard Python tests across jobs with the approaches in pytest slow tests. Reuse login by saving context.storage_state(path="state.json") once and passing storage_state to browser.new_context.
Give the browsers more CPU
When workers queue behind two cores, hardware is the next lever. See faster GitHub Actions runners. If you try compiler.dev, its comparison mode runs your own jobs, which matters because browser-test speedups depend on your suite. It was faster than GitHub's 2-core runner on 15 of 19 benchmarked stacks, by 1.1 to 3 times.
Related
Flaky e2e tests explains why retries and slow suites go together; Cypress tests slow is the sibling guide if you use both tools; the slow CI hub covers the rest of the pipeline.
FAQ
How do I speed up Playwright tests?
Use fullyParallel, set workers to match cores, log in once with storageState, remove fixed waits, start the app once, and shard across jobs with --shard.
How many workers should Playwright use in CI?
About one per core for lighter apps, fewer for heavy ones. Measure total time and failure rate at 1, 2 and 4 workers.
How does Playwright sharding work?
--shard=2/4 runs the second of four slices of the suite. Run each slice in its own job and merge blob reports afterwards.
Why are my Playwright tests slower in CI?
CI machines have fewer cores, cold caches and a freshly installed browser. Compare worker counts and setup time to your local run.
Made by compiler.dev. Free tools · Pricing