---
title: "Speed up Playwright Tests in GitHub Actions | StarSling"
description: "Choose Playwright trace and video retention, preserve first-retry diagnostics, and install only the browsers your CI suite uses."
url: https://starsling.dev/github-actions/optimizations/optimize-playwright
canonicalUrl: https://starsling.dev/github-actions/optimizations/optimize-playwright
---

# Speed up Playwright tests in GitHub Actions

[GitHub Actions](https://starsling.dev/github-actions) / [Optimizations](https://starsling.dev/github-actions/optimizations) / Speed up Playwright tests in GitHub Actions

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

Rule: ci.hygiene.playwright-artifacts. Detection mode: static. Last updated: 2026-09-08

Choose trace recording, retention, and artifact upload separately. on-first-retry records and keeps the first retry even if it passes. retain-on-failure records every attempt and keeps failed attempts, including failures followed by a successful retry. Upload retained diagnostics on successful retry runs when flake investigation matters.

## Table of contents

- [Do this](#do-this)
- [Avoid this](#avoid-this)
- [How to detect it](#how-to-detect-it)
- [Tradeoffs and safety](#tradeoffs)
- [Verify it worked](#verify)
- [Related pages](#related-pages)
- [Symptoms that lead here](#related-symptoms)
- [Sources](#sources)

<a id="do-this"></a>

## Do this

Recording consumes time while tests run; retaining files consumes disk space; uploading compresses and transfers those files. trace: 'on-first-retry' limits trace recording to the first retry and requires retries to be enabled. trace: 'retain-on-failure' records every attempt, then discards successful-attempt traces. Video has its own configuration. A failure()-only upload skips a run that recovers on retry, even when a trace exists locally. The example uploads retained files on non-cancelled runs so that recovered failures remain available for investigation.

_.github/workflows/e2e.yml_

```yaml
jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test
      # Configure retry-aware trace recording in playwright.config.ts.
      # Upload retained diagnostics even when a retry passes.
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: playwright-report
          path: playwright-report/
          if-no-files-found: ignore
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: test-results
          path: test-results/
          if-no-files-found: ignore
```

<a id="avoid-this"></a>

## Avoid this

_.github/workflows/e2e.yml_

```yaml
jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
      - uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: test-results/
```

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

## How to detect it

[ci-speedup](https://starsling.dev/skills/ci-speedup) carries this rule's detection logic 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. Search Playwright config for unconditional capture: `grep -n 'trace:\|video:\|screenshot:' playwright.config.*` - a value of `'on'` (rather than `'on-first-retry'`, `'retain-on-failure'`, or `'only-on-failure'`) means every run records, pass or fail.
2. Inspect the upload-artifact conditions alongside the recording mode. A failure()-only gate skips recovered retries. Use !cancelled() when retained retry evidence should upload on both successful and failed runs; decide separately whether cancelled-run artifacts are needed.
3. Search for the browser install step: `grep -n 'playwright install' .github/workflows/*.yml` - a bare `npx playwright install --with-deps` with no browser argument installs Chromium, Firefox, and WebKit even when the suite only targets one.

<a id="tradeoffs"></a>

## Tradeoffs and safety

- on-first-retry only records once a test has already failed once and is being retried, so it needs `retries` configured above zero; with retries at zero, on-first-retry never fires and a failing test produces no trace at all.
- `on-first-retry` records the first retry attempt regardless of whether that retry passes or fails, so it preserves useful evidence for a flaky test. `retain-on-failure` records every attempt, discards successful attempts, and retains failed attempts; choose it when failed-attempt evidence matters more than recording less.
- The example uses !cancelled() to upload retained files after successful or failed test runs, including recovered retries. It can also upload reports from clean passing runs. Use failure() only when losing recovered-retry artifacts is acceptable; use always() if uploads should also run after cancellation.
- Restricting the install to one browser (chromium) breaks a suite that runs cross-browser projects in playwright.config.* - check the config's projects list before narrowing the install command.

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

## Verify it worked

Hand this prompt to your coding agent (Claude Code, Cursor, and the like) to run this audit and open the fix as a reviewable PR:

```
Audit this repository's Playwright configuration and CI workflow for unconditional
artifact capture and fix what you find.

1. Read playwright.config.* and check the trace, video, and screenshot options. If any is
   set to 'on', propose changing trace to 'on-first-retry' (or 'retain-on-failure' if the
   repo does not configure retries) and video to 'retain-on-failure'.
2. Review upload conditions together with recording and retention. When recovered-retry
   diagnostics must leave the runner, use an expression containing !cancelled() for the
   report and test-results uploads and ignore missing files. Explain that this can upload
   reports on clean passing runs too. Use failure() only if that loss of flaky-run evidence
   is acceptable; decide cancelled-run retention separately.
3. Check the playwright install step. If playwright.config.* defines projects for only one
   browser, propose narrowing npx playwright install --with-deps to name that browser
   explicitly; if multiple browser projects are configured, leave the install as is.
4. Read the Playwright trace viewer, test configuration, and CI docs linked on this page
   before editing.
5. Show the full diff and open a pull request; do not apply changes blindly. In the PR
   body, state how to verify a clean pass, a fail-then-pass retry, and a final failure.
   Confirm retained diagnostics upload under the chosen policy and measure recording
   overhead separately from upload time.
```

Confirm the change landed:

1. Run the suite with all tests passing and confirm the upload steps complete harmlessly (or ignore missing files) when the selected recording mode produces no traces.
2. With retries enabled for on-first-retry, force a test and its retries to fail; confirm the selected trace/video modes retain the expected attempts and the upload steps attach the files.
3. Force one test to fail once and pass on retry; confirm the retry trace remains in test-results and the upload step still attaches it even though the job finishes green.
4. Compare recording time, retained artifact size, and upload duration separately on a clean pass, a recovered retry, and a final failure. Confirm the chosen diagnostic coverage before judging the savings.

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

## Related pages

- [Shard tests across parallel jobs in GitHub Actions](https://starsling.dev/best-practices/github-actions/shard-tests)
- [Make vitest run mode explicit in GitHub Actions](https://starsling.dev/github-actions/optimizations/optimize-vitest)
- [Use Docker layer caching in GitHub Actions](https://starsling.dev/github-actions/optimizations/use-docker-layer-caching)

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

## Symptoms that lead here

Diagnosis pages whose likely causes point at this recipe. Start there if you know the symptom but not yet which fix it needs.

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

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

## Sources

- [Playwright: trace recording and retention modes](https://playwright.dev/docs/api/class-testoptions#test-options-trace)
- [GitHub Actions: status-check expressions](https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#status-check-functions)
- [Playwright: Trace viewer](https://playwright.dev/docs/trace-viewer-intro)
- [Playwright: Test configuration (video, screenshot)](https://playwright.dev/docs/test-configuration)
- [Playwright: Continuous Integration](https://playwright.dev/docs/ci-intro)
- [Playwright: Browsers](https://playwright.dev/docs/browsers)
