---
title: "Why Is Playwright Slow in CI | StarSling"
description: "Diagnose why your Playwright end-to-end job dominates CI wall clock, with five ordered causes to check and how to confirm each one."
url: https://starsling.dev/github-actions/problems/slow-playwright
canonicalUrl: https://starsling.dev/github-actions/problems/slow-playwright
---

# Why is the Playwright job so slow in CI

[GitHub Actions](https://starsling.dev/github-actions) / [Problems](https://starsling.dev/github-actions/problems) / Why is the Playwright job so slow in CI

- [How StarSling works](https://starsling.dev/)

Diagnosis mode: hybrid. Last updated: 2026-08-31

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.

## Table of contents

- [Symptoms](#symptoms)
- [How to diagnose it](#how-to-diagnose-it)
- [Likely causes](#likely-causes)
- [Hand it to an agent](#verify)
- [Related pages](#related-pages)
- [Related symptoms](#related-symptoms)
- [Sources](#sources)

<a id="symptoms"></a>

## 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.

<a id="how-to-diagnose-it"></a>

## How to diagnose it

[ci-speedup](https://starsling.dev/ci-speedup) carries the detection logic behind the causes below and opens the fix as a reviewable pull request. Install it with `npx skills add starslingdev/skills`, then run `/ci-speedup` in your repository.

To check by hand:

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.

<a id="likely-causes"></a>

## 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](https://starsling.dev/github-actions/optimizations/optimize-playwright) (`ci.hygiene.playwright-artifacts`, detection mode static)

### 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](https://starsling.dev/best-practices/github-actions/shard-tests) (`ci.parallel.test-sharding`, detection mode static)

### 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](https://starsling.dev/best-practices/github-actions/replace-fixed-sleeps-with-polling) (`ci.hygiene.polling-waits`, detection mode static)

### 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](https://starsling.dev/best-practices/github-actions/wait-for-container-healthchecks) (`ci.hygiene.container-healthchecks`, detection mode static)

### 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](https://starsling.dev/best-practices/github-actions/cache-dependencies) (`ci.cache.dependency-cache`, detection mode static)

<a id="verify"></a>

## 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:

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.

<a id="related-pages"></a>

## Related pages

- [Speed up Playwright tests in GitHub Actions](https://starsling.dev/github-actions/optimizations/optimize-playwright)
- [Shard tests across parallel jobs in GitHub Actions](https://starsling.dev/best-practices/github-actions/shard-tests)
- [Replace fixed CI sleeps with bounded readiness polling](https://starsling.dev/best-practices/github-actions/replace-fixed-sleeps-with-polling)
- [Wait for container healthchecks instead of sleeping](https://starsling.dev/best-practices/github-actions/wait-for-container-healthchecks)
- [GitHub Actions cache: dependencies, keys, and cache hits](https://starsling.dev/best-practices/github-actions/cache-dependencies)
- [Why is GitHub Actions slow](https://starsling.dev/github-actions-too-slow)

<a id="related-symptoms"></a>

## Related symptoms

Other symptoms on this site that share a likely cause with this one. If none of the causes above is yours, one of these is usually the page you wanted.

- [Why your test job is slower in CI than it is locally](https://starsling.dev/github-actions/problems/slow-tests)
- [CI fails, then passes on an unchanged rerun](https://starsling.dev/github-actions/problems/flaky-tests)
- [A job hits a timeout, or hangs until GitHub kills it](https://starsling.dev/github-actions/problems/github-actions-timeouts)

<a id="sources"></a>

## Sources

- [Playwright: Continuous Integration](https://playwright.dev/docs/ci)
- [Playwright: Sharding](https://playwright.dev/docs/test-sharding)
- [GitHub Actions: Caching dependencies to speed up workflows](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching)
- [GitHub Actions: Run variations of jobs in a workflow](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/run-job-variations)
