Why is the Playwright job so slow in CI

A slow Playwright job usually means the suite is running as one unsharded process, waiting on fixed sleeps instead of real readiness signals, and uploading every trace and video whether the run passed or not. Check sharding, sleeps, and artifact capture before assuming the tests themselves are slow.

hybrid · repo + run history
How StarSling works

AI agents find the cause

StarSling agents run this diagnosis against your workflows and run history, identify which cause is yours, and open the fix as a reviewable PR.

Symptoms

When a Playwright job dominates the workflow's wall clock, the browsers, the test count, and the assertions are rarely the bottleneck on their own. What usually happens is that a single job runs the entire suite sequentially in one process, so the job's duration is the sum of every test instead of the slowest shard. Inside that run, page.waitForTimeout calls and container startup sleeps add fixed delays that have nothing to do with how fast the page actually responded. On top of that, trace and video capture set to on records and uploads artifacts for every test, including the ones that passed, and a cold browser-binary cache reinstalls Chromium, Firefox, and WebKit from scratch on every run. The causes below are ordered by how much of the job's time they typically consume: artifact capture and browser installs first because they are the cheapest to confirm and fix, then sharding because it changes the job's structure, then sleeps and healthchecks because they hide inside test and service code rather than the workflow file, and dependency caching last because it affects setup time rather than the test run itself.

  • The Playwright job takes far longer in CI than the same suite takes to run locally.

  • The workflow run summary shows one long e2e job instead of several shorter parallel ones.

  • Every run, including ones where every test passes, uploads a full set of trace and video artifacts.

  • The job log shows long gaps where nothing appears to be happening before a test or a service starts responding.

  • The npx playwright install step takes a noticeable chunk of the job on every run.

How to diagnose it

  1. Open the workflow run in the Actions UI and read the Playwright job's total duration next to the other jobs in the run; if it is the longest by a wide margin, it is the critical path worth diagnosing.

  2. Expand the job's steps and note how long npx playwright install and the dependency install step each take before any test starts.

  3. Read the job's YAML: run grep -n 'shard\|matrix' .github/workflows/*.yml for the Playwright job to see whether it is already split across parallel legs.

  4. Compare the upload-artifact step's size across a few recent runs; if passing runs upload traces and videos as large as failing runs, capture is not conditioned on failure.

  5. Grep the test source for fixed waits: grep -rn 'waitForTimeout' e2e/ tests/ --include='*.spec.ts' and note the count and the millisecond values.

  6. Check whether any service the tests depend on (database, mock API) starts with a sleep step or a healthcheck: block in its container definition.

Likely causes

Ordered by how often each one turns out to be the answer. Confirm a cause with its check before you change anything.

  1. Traces and videos captured on every run, not just failures

    Playwright's trace and video recorders write to disk while tests run, and the upload-artifact step then has to compress and transfer whatever they produced. When trace or video in the Playwright config is set to on instead of on-first-retry or retain-on-failure, that recording and upload cost is paid on every run, including the runs that pass. A bare npx playwright install --with-deps with no browser argument also installs Chromium, Firefox, and WebKit even when the suite only targets one, adding to the same step.

    Confirm it

    Run grep -n 'trace:\|video:\|screenshot:' playwright.config.* and check whether the values are 'on' (records every run) versus 'on-first-retry' or 'retain-on-failure'. Then run grep -n 'playwright install' .github/workflows/*.yml and check whether a specific --with-deps chromium (or similar) is passed.

    Fix: Playwright artifact capture
  2. The suite runs as one job instead of parallel shards

    Playwright supports splitting a suite across machines with --shard=<index>/<total>, but a job that runs the full suite in a single process is bounded by the sum of every test's duration rather than the slowest shard. This is the largest single lever on a Playwright job's wall clock because the job cannot finish faster than its own test count allows, no matter how fast individual pages respond.

    Confirm it

    Run grep -n 'shard' .github/workflows/*.yml playwright.config.* and check whether a matrix with shard or --shard=${{ matrix.shard }}/${{ strategy.job-total }} is present. If it is absent, the job is running the entire suite as one unit.

    Fix: Test sharding
  3. Fixed sleeps inside the test source, not the workflow

    page.waitForTimeout(), cy.wait(<ms>), and raw setTimeout/sleep calls embedded in test files add fixed delays regardless of how quickly the page actually became ready. Because these live in test source rather than workflow YAML, they do not show up when scanning .github/workflows/ alone, and they accumulate across every spec file that uses them.

    Confirm it

    Run grep -rn 'page\.waitForTimeout(\|waitForTimeout([0-9]' e2e/ playwright/ tests/ --include='*.spec.ts' --include='*.test.ts' and sum the delays found; a suite with dozens of these calls can lose minutes per run to sleeps alone.

    Fix: Polling waits, not fixed sleeps
  4. The job sleeps for a fixed time before a dependent container is ready

    A workflow step using sleep N to wait for a database or backend container to accept connections has to guess a duration long enough for the slowest case, so it wastes time on every run where the container starts faster than the guess. This is separate from sleeps in test source: it appears directly in the workflow file or docker-compose.yml, ahead of the test job actually starting.

    Confirm it

    Run grep -rn 'sleep [0-9]' .github/workflows/ docker-compose*.yml and check whether the container the Playwright job depends on has a healthcheck: block; if it only has a sleep before the test step, this is the cause.

    Fix: Container healthchecks
  5. No caching for npm/pnpm dependencies or the Playwright browser binaries

    The Playwright job installs project dependencies and then downloads browser binaries before any test runs, and neither install is fast without a cache. Without actions/cache (or setup-node's built-in cache) targeting the package manager's store, and without a cache targeting the Playwright browser binary directory, both steps repeat their full download and install cost on every single run.

    Confirm it

    Run grep -n 'actions/cache\|setup-node.*cache' .github/workflows/*.yml in the job that runs Playwright, and separately check whether the cache key or path covers the Playwright browser binaries (typically under the OS cache home) rather than only node_modules.

    Fix: Dependency caching

Hand it to an agent

Hand this prompt to your coding agent (Claude Code, Cursor, and the like) to run this diagnosis against your repository and report which cause it found.

Prompt for your coding agent
Diagnose why the Playwright end-to-end job dominates this repository's CI wall clock. Do not change anything yet. First, read the workflow file(s) under `.github/workflows/` that run Playwright and note the job's total duration relative to other jobs. Then check, in this order: (1) whether `playwright.config.*` sets `trace` or `video` to `'on'` instead of `'on-first-retry'`/`'retain-on-failure'`, and whether `upload-artifact` runs unconditionally; (2) whether the job uses `--shard`/a matrix to split tests across parallel legs, or runs the full suite in one process; (3) whether test files under `e2e/`, `tests/`, or `playwright/` contain `page.waitForTimeout()` or similar fixed sleeps, and how much total time they add; (4) whether any dependent container is started with a `sleep` instead of a `healthcheck:`; (5) whether dependency installs and the Playwright browser binary download are cached with `actions/cache` or `setup-node`'s cache option. Report which of these five causes you found evidence for, in order of estimated time contribution, before proposing or making any change.

Go further

Find the cause, fix them all, or keep them fixed.

You have the prompt that works out which cause applies. Here is how much further you can take it, each step doing more for you than the last.

  1. Find the cause

    Copy the diagnosis prompt above

    Hand it to your coding agent to confirm which cause applies in your repo. It reports back what it found and changes nothing.

  2. Fix them all, once

    Install the ci-speedup skill

    One prompt audits your whole repo against all 73 ci-speedup patterns (these 6 plus 67 more) and hands your agent every fix at once. Open source, MIT, runs locally.

  3. Keep it fixed, forever

    Install the StarSling GitHub App

    Connect GitHub and the fixes stay applied as your CI evolves, with agents that keep inspecting your workflows and opening optimization PRs you review.

Sources

1Playwright: Continuous Integration (opens in new tab)

2Playwright: Sharding (opens in new tab)

3GitHub Actions: Caching dependencies to speed up workflows (opens in new tab)

4GitHub Actions: Run variations of jobs in a workflow (opens in new tab)

Last updated 2026-08-31