Skip to content
ExponaBLOG
PLAYWRIGHT & BROWSER AUTOMATION · DAY 022 / 100

Playwright in GitHub Actions: what developers should know

Run tests on every pull request.

Expona Admin6 min read

Introduction

Running Playwright tests locally is one thing; running them reliably in CI/CD is where most teams hit friction. Playwright in GitHub Actions: what developers should know covers the practical setup that keeps test suites stable, fast, and debuggable across every pull request without surprises.

We've seen teams waste weeks troubleshooting flaky tests in CI only to discover they hadn't installed browser dependencies correctly, or they were pulling stale artifacts. This article walks through the real patterns that work in production—from browser installation to artifact handling to using Docker images that eliminate environment drift.

Why CI matters for Playwright

Running tests only on your machine is a false sense of security. Your OS, Node version, browser cache, and system libraries are all different from CI. When a test passes locally but fails in GitHub Actions, you lose developer time diagnosing environment issues instead of fixing actual bugs.

The goal of a solid CI setup is reproducibility: every pull request gets the exact same test environment, the same browser versions, and the same dependencies. This removes the "works on my machine" excuse and catches real regressions early.

Step 1: Install browsers with dependencies

The first—and most common—mistake is assuming Playwright's browser binaries are enough. They're not. Playwright bundles Chromium, Firefox, and WebKit, but those binaries need system libraries to run. On Ubuntu (common in CI), that means fonts, graphics libraries, and audio dependencies.

The command is simple but essential:

typescript
npx playwright install --with-deps

The --with-deps flag installs not just the browsers but also system packages required to run them. Without it, you'll see cryptic errors like "error while loading shared libraries" when Playwright tries to spawn a browser.

In your GitHub Actions workflow, add this early:

typescript
- name: Install Playwright browsers
  run: npx playwright install --with-deps

This step should run after checking out code and installing Node dependencies (via npm ci), but before any test execution. It's fast—usually under 30 seconds on a fresh runner—and it prevents 80% of CI browser issues we've seen.

Step 2: Start your application and run tests

Most Playwright suites need your application running. Whether it's a Next.js dev server, a Docker container, or a deployed staging environment, your tests need a target.

For a typical setup, you'll run the app in the background, wait for it to be ready, then kick off Playwright:

typescript
- name: Start application
  run: npm run dev &
  
- name: Wait for app to be ready
  run: npx wait-on http://localhost:3000 --timeout 30000
  
- name: Run Playwright tests
  run: npx playwright test

Breaking this down:

  • Start app in background: The & lets the process run without blocking subsequent steps.
  • Wait for readiness: Tools like wait-on poll your app's health endpoint until it responds. This prevents race conditions where tests start before the app is actually listening.
  • Run tests: Once the app is ready, Playwright can connect and run your suite.

If you're using Docker Compose or have a more complex setup, start those services earlier in the workflow and point Playwright at them.

Step 3: Upload reports and artifacts

Test failures in CI are useless without evidence. Playwright generates three types of artifacts worth preserving:

  • HTML report: Interactive report showing test results, timestamps, and pass/fail details.
  • Traces: Detailed execution traces (with video, screenshots, network logs) for failed tests.
  • Screenshots: Captured at specific test steps or on failure.

GitHub Actions' artifact system keeps these for 90 days by default. Use the upload-artifact action to capture them:

typescript
- name: Upload Playwright report
  uses: actions/upload-artifact@v4
  if: always()
  with:
    name: playwright-report
    path: playwright-report
    retention-days: 30

Key points:

  • if: always() ensures artifacts upload even if tests fail. Without this, you lose traces when you need them most.
  • path: playwright-report points to Playwright's default report output directory.
  • retention-days saves storage; 30 days is usually enough for debugging.

After the workflow completes, the "Artifacts" section in the GitHub Actions UI lets you download the report. You can then open it locally in a browser and click through failed tests to see exactly what went wrong—network requests, DOM state, video scrubs, everything.

Step 4: Use the Playwright Docker image

GitHub Actions runners come with various configurations. To eliminate environment drift, use the official Playwright Docker image: mcr.microsoft.com/playwright. This image bakes in all system dependencies, Node, and Playwright, so your tests run in a guaranteed environment.

Configure your workflow to use it:

typescript
jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: mcr.microsoft.com/playwright:v1.45.0-jammy
      options: --shm-size=1gb
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: npm ci
      - name: Run Playwright tests
        run: npx playwright test

When you specify a container, GitHub Actions runs all steps inside that image. Benefits:

  • Consistency: Every developer and CI run uses the exact same OS, libraries, and versions.
  • Reproducibility: Issues tied to system configuration vanish.
  • Pinned versions: You control which Playwright Docker tag you use; no surprises from auto-updates.

The --shm-size=1gb flag increases shared memory, which Chromium needs for stable rendering. Without it, you may see "Discarding image" warnings or hangs.

Note: if you're inside a container, you no longer need npx playwright install --with-deps—browsers are already there. Just run your tests.

A complete workflow example

Here's a realistic GitHub Actions workflow combining all these patterns:

typescript
name: Playwright Tests
on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: mcr.microsoft.com/playwright:v1.45.0-jammy
      options: --shm-size=1gb
    steps:
      - uses: actions/checkout@v4
      
      - name: Install dependencies
        run: npm ci
      
      - name: Start application
        run: npm run dev &
      
      - name: Wait for app
        run: npx wait-on http://localhost:3000 --timeout 30000
      
      - name: Run Playwright tests
        run: npx playwright test
      
      - name: Upload report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report
          retention-days: 30

This workflow runs on every PR and push to main. It uses the Playwright Docker image, installs your app's dependencies, starts the dev server, waits for it to respond, runs all tests, and uploads the report regardless of pass/fail.

Common mistakes

  • Forgetting --with-deps: npx playwright install without the flag installs browsers but not system libraries. Tests fail with cryptic shared library errors. Always use --with-deps on bare Ubuntu runners.
  • Not using if: always() on artifact upload: If tests fail, the step is skipped by default. You lose the report when you need it most. Always include this condition.
  • No wait logic before tests: Starting an app and immediately running tests causes race conditions. The app may not be fully initialized. Use wait-on or similar health checks.
  • Ignoring Docker image SHM limits: Chromium renders to shared memory. Low SHM causes hangs and timeouts. Set --shm-size=1gb in the container options.
  • Running tests on every file change: Consider using job conditions to skip tests for documentation-only changes. This saves CI minutes and keeps feedback fast.

Optimization tips

Once your basic setup works, consider these refinements:

  • Cache Node modules: Use actions/setup-node@v4 with cache: npm to skip reinstalling dependencies each run.
  • Parallel execution: Playwright runs tests in parallel by default. Set workers in your playwright.config.ts to match CI runner capacity.
  • Conditional test runs: Use GitHub Actions path filters to skip tests if only docs changed.
  • Report merging: If you split tests across multiple jobs, merge reports with playwright merge-reports.

Key takeaways

  • Always use npx playwright install --with-deps on standard CI runners to install both browser binaries and required system libraries.
  • Start your application, wait for it to be ready, then run tests—race conditions are subtle but common.
  • Upload Playwright artifacts with if: always() to capture reports and traces even when tests fail.
  • Use the official Playwright Docker image (mcr.microsoft.com/playwright) to eliminate environment drift and guarantee consistent test execution.
  • Invest time in CI setup early; flaky or opaque CI wastes developer time and undermines test value.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles