Playwright CI/CD Integration with GitHub Actions: The Complete Guide
- Get link
- X
- Other Apps
In modern web development, end-to-end (E2E) testing is non-negotiable. But writing tests is only half the battle. The real magic happens when those tests run automatically on every code change, providing instant feedback and preventing regressions from ever reaching production.
Integrating Playwright with GitHub Actions creates a powerful, automated quality gate right inside your development workflow. This guide will walk you from a basic setup to a production-grade pipeline, complete with parallel testing, artifact management, and advanced debugging techniques.
Why Integrate Playwright with GitHub Actions?
Before diving into the code, let's understand why this combination is so compelling:
Automatic Test Execution: Tests run on every
pushorpull_request, ensuring new changes don't break existing functionality.Consistent Environment: GitHub-hosted runners provide clean, isolated VMs, eliminating the "it works on my machine" problem.
Native Integration: GitHub Actions lives inside your repository, making setup seamless with no external CI tools required.
Cross-Browser Coverage: Leverage Playwright's multi-browser capabilities with GitHub Actions' matrix strategy to test Chromium, Firefox, and WebKit in parallel.
Rich Artifacts: Automatically store test reports, screenshots, videos, and traces for debugging failed runs.
"When my team first shifted our Playwright suite into GitHub Actions, we expected smooth automation… and instead ran into flaky tests and slow pipelines. The problem wasn't Playwright or GitHub Actions – it was how we connected the two."
Let's learn how to connect them correctly.
Prerequisites: What You Need to Start
Ensure your project is ready for CI/CD integration:
A GitHub Repository: Your Playwright project must be pushed to a GitHub repo.
A Working Playwright Setup: Playwright should be installed locally, and tests should pass with
npx playwright test.Node.js Configuration: Your project must have a valid
package.jsonfile.Basic YAML Knowledge: GitHub Actions workflows are defined in YAML.
Step 1: Preparing Playwright for CI
Before configuring the pipeline, optimize your Playwright configuration for a CI environment. Update your playwright.config.ts:
// playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ // Fail the build on CI if you accidentally left test.only in the source code forbidOnly: !!process.env.CI, // Retry on CI only - flaky tests happen, let's give them a second chance retries: process.env.CI ? 2 : 0, // Optimal parallel workers for CI - reduces strain on runners workers: process.env.CI ? '50%' : undefined, // Reporter configuration - HTML for local, JSON for CI aggregation reporter: process.env.CI ? 'blob' : 'html', use: { // Essential artifacts for debugging CI failures trace: 'on-first-retry', // Trace only when retrying a failed test screenshot: 'only-on-failure', // Screenshot on failure video: 'retain-on-failure', // Video on failure // Base URL for tests - override via environment variable in CI baseURL: process.env.BASE_URL || 'http://localhost:3000', }, });
Key configurations:
forbidOnly: Prevents accidental commits of focused tests (test.only)retries: 2 retries in CI combat flakiness without slowing down local runsArtifacts on failure: Critical for debugging why tests passed locally but failed in CI
Step 2: Creating Your First GitHub Actions Workflow
Now, let's create the workflow file that will orchestrate your tests.
Create the Workflow File
In your repository root, create: .github/workflows/playwright.yml
Basic Workflow Configuration
Start with a simple but complete workflow:
name: Playwright Tests on: push: branches: [main, develop] pull_request: branches: [main] workflow_dispatch: # Allow manual triggering jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' # Automatically caches npm dependencies - name: Install dependencies run: npm ci # 'ci' for faster, deterministic installs - name: Install Playwright browsers run: npx playwright install --with-deps chromium # --with-deps installs system dependencies automatically - name: Run Playwright tests run: npx playwright test - name: Upload test report if: always() # Run even if tests fail uses: actions/upload-artifact@v4 with: name: playwright-report path: playwright-report/ retention-days: 30
What this does:
Uses
ubuntu-latestrunner for a consistent Linux environmentCaches
node_modulesviaactions/setup-node@v4for faster subsequent runs
Step 3: Advanced Configuration - Parallel & Cross-Browser Testing
The real power of Playwright shines when testing across multiple browsers. Let's leverage GitHub Actions' matrix strategy.
name: Playwright Tests (Full Suite) on: push: branches: [main] pull_request: branches: [main] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest strategy: fail-fast: false # Don't cancel all jobs if one fails matrix: browser: [chromium, firefox, webkit] shardIndex: [1, 2, 3, 4] # Shard tests across 4 parallel jobs per browser shardTotal: [4] steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Cache Playwright browsers id: playwright-cache uses: actions/cache@v4 with: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ matrix.browser }}-${{ hashFiles('package-lock.json') }} restore-keys: | ${{ runner.os }}-playwright-${{ matrix.browser }}- ${{ runner.os }}-playwright- - name: Install dependencies run: npm ci - name: Install Playwright (${{ matrix.browser }}) if: steps.playwright-cache.outputs.cache-hit != 'true' run: npx playwright install ${{ matrix.browser }} --with-deps - name: Run tests (${{ matrix.browser }} - Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) run: npx playwright test --project=${{ matrix.browser }} --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - name: Upload blob report if: always() uses: actions/upload-artifact@v4 with: name: blob-report-${{ matrix.browser }}-${{ matrix.shardIndex }} path: blob-report/ retention-days: 7
Merging Reports from Parallel Jobs
Add a final job to merge all blob reports into a single HTML report:
merge-reports: if: always() needs: [test] # Wait for all test jobs to complete runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Download all blob reports uses: actions/download-artifact@v4 with: path: all-blob-reports pattern: blob-report-* merge-multiple: true - name: Merge into HTML report run: npx playwright merge-reports --reporter=html ./all-blob-reports - name: Upload merged HTML report uses: actions/upload-artifact@v4 with: name: merged-html-report path: playwright-report retention-days: 30
Why this approach?
Parallel execution: Tests run 4 shards × 3 browsers = 12 parallel jobs, drastically reducing total execution time
Browser caching: Prevents re-downloading browsers on every run, saving 2-3 minutes per job
Sharding: Distributes test files evenly across parallel runners
Report merging: Combines results from all shards into one comprehensive HTML report
Step 4: Managing Environment Variables and Secrets
Never hardcode API keys or credentials. Use GitHub Secrets.
Storing Secrets
Go to your GitHub repository → Settings → Secrets and variables → Actions.
Click "New repository secret" and add your variables (e.g.,
API_KEY,TEST_USER_PASSWORD).
Using Secrets in Your Workflow
- name: Run Playwright tests env: API_KEY: ${{ secrets.API_KEY }} TEST_USER: ${{ secrets.TEST_USER }} TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }} BASE_URL: ${{ vars.BASE_URL }} # Use vars for non-sensitive config run: npx playwright test
Accessing Environment Variables in Tests
// In your test file test('should authenticate with API key', async ({ page }) => { const apiKey = process.env.API_KEY; expect(apiKey).toBeDefined(); // Use the API key in your test await page.goto(`https://api.example.com/data?key=${apiKey}`); });
Step 5: Debugging CI Failures Like a Pro
CI failures are inevitable. The key is having the right data to diagnose them.
Rich Artifacts Configuration
Update your Playwright config to capture maximum debug information:
// playwright.config.ts export default defineConfig({ use: { trace: 'retain-on-failure', // Full trace on failure screenshot: 'only-on-failure', // Visual evidence video: 'retain-on-failure', // Video replay }, });
Uploading All Debug Artifacts
Enhance your workflow to upload everything:
- name: Upload test results if: always() uses: actions/upload-artifact@v4 with: name: test-results-${{ matrix.browser }}-${{ matrix.shardIndex }} path: | test-results/ playwright-report/ **/__screenshots__/ retention-days: 14
Enabling Verbose Logging
For hard-to-debug issues, enable Playwright's internal logging:
- name: Run tests with debug logging env: DEBUG: pw:api # Log all Playwright API calls run: npx playwright test
This outputs detailed logs showing exactly what Playwright is doing, including locator resolution and network requests.
Viewing Traces in CI
After a failed run, download the test-results artifact. Then locally run:
npx playwright show-trace path/to/trace.zip
This opens the Trace Viewer, allowing you to step through the test, inspect network requests, and see DOM snapshots at each action.
Step 6: Optimizing Performance and Cost
GitHub Actions has usage limits. Here's how to maximize efficiency:
1. Dependency Caching
- name: Get date for browser cache key id: date run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - name: Cache Playwright browsers uses: actions/cache@v4 with: path: ~/.cache/ms-playwright key: playwright-browsers-${{ steps.date.outputs.date }}
Browser binaries are large (~500MB). Caching them saves significant time.
2. Selective Test Execution
Run only tests relevant to the changes:
- name: Run affected tests run: npx playwright test --only-changed
Playwright can detect which tests are affected by changed files, running a subset for faster feedback.
3. Conditional Workflow Triggers
on: pull_request: paths-ignore: - '**.md' # Skip tests for documentation changes - 'docs/**' - '.github/**'
Don't waste CI minutes on changes that can't break tests.
Step 7: Integrating with Deployment
The ultimate CI/CD pipeline tests after deployment to ensure the live site works.
Example: Testing a Staging Deployment
name: Deploy and Test on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest outputs: staging_url: ${{ steps.deploy.outputs.url }} steps: - uses: actions/checkout@v4 - name: Deploy to Vercel (or your hosting provider) id: deploy run: | # Your deployment script here echo "url=https://staging-myapp.vercel.app" >> $GITHUB_OUTPUT test: needs: deploy runs-on: ubuntu-latest env: BASE_URL: ${{ needs.deploy.outputs.staging_url }} steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Run Playwright tests against staging run: npx playwright test - name: Upload test report if: always() uses: actions/upload-artifact@v4 with: name: playwright-report-staging path: playwright-report/
This pattern ensures:
Deployment happens first
Tests run against the newly deployed environment
Failures prevent promoting to production
Step 8: Visualizing Test Results
GitHub Actions Summary
Playwright can write a markdown summary directly into the GitHub Actions run:
- name: Run Playwright tests run: npx playwright test --reporter=html,github
The github reporter creates a summary in the Actions tab with pass/fail counts.
Deploying Reports to GitHub Pages
For permanent test history, deploy reports to GitHub Pages:
- name: Deploy report to GitHub Pages if: always() uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./playwright-report destination_dir: reports/${{ github.run_id }}
Common Pitfalls and Solutions
"Tests pass locally but fail in CI"
Solution: Reproduce CI conditions locally:
# Simulate CI environment CI=true npx playwright test --workers=1 # Use the same Node version as CI nvm use 20 # Clear caches rm -rf node_modules package-lock.json npm install
"Browser crashes with no error"
Solution: Install system dependencies explicitly:
- name: Install Playwright with all dependencies run: npx playwright install --with-deps chromium firefox webkit
"Tests timeout in CI"
Solution: Increase timeout and enable tracing:
- name: Run tests with longer timeout run: npx playwright test --timeout=60000
Conclusion: Building a Robust Quality Pipeline
Integrating Playwright with GitHub Actions transforms your testing strategy from manual, sporadic checks to an automated, continuous validation system. The initial setup time pays dividends by catching bugs before they reach users and freeing developers from manual regression testing.
Key takeaways:
Start simple with a basic workflow, then layer in complexity
Always capture artifacts – traces, screenshots, and videos are your debugging lifeline
Optimize with caching to keep pipelines fast and cost-effective
Integrate with deployment to test in production-like environments
The combination of Playwright's powerful testing capabilities and GitHub Actions' seamless CI/CD integration provides a world-class developer experience. Your future self – and your users – will thank you for the confidence and stability this pipeline delivers.
Recommended Next Reads
- Get link
- X
- Other Apps