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: 7This 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 }}/4Artifacts 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: 14For 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=visualGitHub 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?
Should I use GitHub-hosted or self-hosted runners?
How do I update baselines when visual changes are intentional?
Should visual test failures block PRs?
How do I handle visual test artifacts in PRs?
Can I run visual tests on pull_request and push events?
Streamline your visual testing CI workflow
Get early access