CI/CD

GitHub Actions visual testing with Playwright

Set up visual regression testing in GitHub Actions that's fast, reliable, and integrates smoothly into your PR workflow. From basic setup to test sharding and artifact handling.

Minimal workflow structure

Here's a minimal GitHub Actions workflow for Playwright visual tests:

name: Visual Tests

on:
  pull_request:
    branches: [main]

jobs:
  visual-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

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

      - name: Run visual tests
        run: npx playwright test --project=visual

      - name: Upload diff artifacts
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: visual-test-diffs
          path: test-results/
          retention-days: 7

This runs on PRs to main, installs only the browsers you need, and uploads diff artifacts on failure for easy debugging.

Deterministic environment

GitHub Actions runners are consistent but not identical to your local machine. Control these factors:

Font consistency

GitHub runners have limited fonts installed. Install the fonts your app uses or use a Docker container with controlled fonts.

Browser versions

Pin Playwright versions to ensure browser versions stay consistent. Floating versions cause intermittent baseline mismatches.

Display configuration

GitHub runners are headless. Playwright handles this automatically, but ensure your app doesn't depend on display-specific behaviour.

Resource constraints

GitHub runners have limited memory and CPU. Large test suites may need parallelisation or a larger runner.

For maximum consistency, use a Docker container. See Docker setup for deterministic rendering for container configuration.

Speed optimisations

Visual tests are slower than unit tests. These techniques keep feedback fast:

Cache Playwright browsers

Browser downloads are slow. Cache ~/.cache/ms-playwright between runs to save minutes per workflow.

Test sharding

Split tests across multiple runners. Playwright's built-in sharding divides work automatically.

Selective test runs

Run full visual suite on PRs to main only. Feature branches can run a smoke subset.

Fail fast

Stop on first failure during development. Run all tests in CI for complete coverage.

Here's how to cache Playwright browsers:

- name: Cache Playwright browsers
  uses: actions/cache@v4
  with:
    path: ~/.cache/ms-playwright
    key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

And test sharding across multiple runners:

jobs:
  visual-tests:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      # ... setup steps ...
      - name: Run visual tests
        run: npx playwright test --shard=${{ matrix.shard }}/4

Artifacts and PR workflow

When visual tests fail, reviewers need to see the diffs. Upload screenshots as artifacts and make them accessible from the PR.

- name: Upload diff artifacts
  if: failure()
  uses: actions/upload-artifact@v4
  with:
    name: visual-diffs-${{ github.run_id }}
    path: |
      test-results/**/*.png
      test-results/**/*-diff.png
    retention-days: 14

For a better experience, use a GitHub Action or bot that comments on PRs with links to the artifacts or embedded diff images.

Block vs report-only

You have two options for how visual test failures affect PRs:

  • Blocking: Visual test failures prevent merging. High confidence, but requires stable tests and an escape hatch for urgent fixes.
  • Report-only: Failures are reported but don't block. Lower friction, but visual regressions can slip through if reports are ignored.

Start with report-only while building confidence. Move to blocking once your tests are stable and the team trusts them. Add a PR label like skip-visual-tests for emergency bypasses.

# Report-only: use continue-on-error
- name: Run visual tests
  continue-on-error: true
  run: npx playwright test --project=visual

GitHub Actions visual testing checklist

  • Pin Playwright version in package.json for consistent browsers
  • Cache Playwright browser binaries between runs
  • Install required fonts in the runner or use Docker
  • Configure consistent viewport and device settings
  • Upload screenshot diffs as artifacts on failure
  • Consider test sharding for large suites
  • Set up baseline update workflow (branch or label trigger)
  • Decide block vs report policy and configure accordingly
  • Add PR comments linking to visual diff artifacts

Related guides

Frequently Asked Questions

Why do visual tests pass locally but fail in GitHub Actions?
Usually font rendering differences. Linux runners render fonts differently than macOS or Windows. Either install matching fonts in the runner, use a Docker container with pinned fonts, or generate baselines from CI and treat CI as the source of truth.
Should I use GitHub-hosted or self-hosted runners?
GitHub-hosted runners are simpler and work for most teams. Use self-hosted if you need specific fonts, GPU rendering, or consistent hardware. Self-hosted requires more maintenance but gives you complete control over the environment.
How do I update baselines when visual changes are intentional?
Run tests locally with the update flag (npx playwright test --update-snapshots), or configure your CI to update baselines on a specific branch or with a PR label. Then commit the new baseline images. Always review baseline changes before merging.
Should visual test failures block PRs?
Start with reporting-only to build confidence. Once your tests are stable and the team trusts them, switch to blocking. Always provide an escape hatch for urgent fixes—either a skip label or manual override.
How do I handle visual test artifacts in PRs?
Upload diff images as artifacts using actions/upload-artifact. Comment on the PR with a link to view them. Some visual testing services provide automatic PR comments with visual diffs. Make it easy for reviewers to see what changed.
Can I run visual tests on pull_request and push events?
Yes, but be thoughtful. Running on every push creates noise. Running only on pull_request delays feedback. A common pattern: run quick smoke tests on push, full visual suite on pull_request to main.

Streamline your visual testing CI workflow

Get early access