Publishing Playwright HTML Reports to GitHub Pages

Publishing Playwright HTML Reports to GitHub Pages

Playwright generates a rich interactive HTML report — but it only exists locally after a test run. Pushing it to GitHub Pages gives your team a permanent, browsable URL for every test run without spinning up any infrastructure. Here's the complete setup.

What You Get

After following this guide:

  • Every push to main triggers tests and publishes the report
  • A permanent URL like https://your-org.github.io/your-repo/ shows the latest results
  • Historical reports are preserved in versioned subdirectories
  • Failed tests show screenshots, traces, and error messages in the browser

Step 1: Configure Playwright's HTML Reporter

In playwright.config.ts:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ['html', {
      outputFolder: 'playwright-report',
      open: 'never',  // don't auto-open in CI
    }],
    ['line'],  // console output
  ],
  use: {
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'retain-on-failure',
  },
  outputDir: 'test-results/',  // attachments (screenshots, traces, videos)
});

The HTML reporter generates a self-contained playwright-report/ directory with an index.html that embeds all attachment data.

Step 2: Enable GitHub Pages

  1. Go to your repo Settings → Pages
  2. Set Source to "GitHub Actions" (not a branch)
  3. Leave the folder as / (root)

The "GitHub Actions" source means Pages will be deployed by your workflow, not by pushing to a gh-pages branch.

Step 3: The Workflow

Create .github/workflows/playwright.yml:

name: Playwright Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  test:
    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 Playwright tests
        run: npx playwright test
        continue-on-error: true  # publish report even on test failure

      - name: Upload report as artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: playwright-report/

  deploy:
    needs: test
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

The continue-on-error: true on the test step is critical — without it, a test failure would skip the report upload step and you'd lose visibility into what failed.

Step 4: Handling Pull Requests

The workflow above publishes from main pushes. For PRs, Pages deployment requires the pages: write permission, which is restricted for fork PRs for security reasons. A common pattern: run tests on PRs, upload the report as a downloadable artifact (not Pages), and only deploy to Pages on merge.

- name: Upload PR report as artifact
  if: github.event_name == 'pull_request'
  uses: actions/upload-artifact@v4
  with:
    name: playwright-report-pr-${{ github.event.number }}
    path: playwright-report/
    retention-days: 7

Then add a comment to the PR with a link to the artifact:

- name: Comment PR with report link
  if: github.event_name == 'pull_request'
  uses: actions/github-script@v7
  with:
    script: |
      const run_url = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
      github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: `## Playwright Test Report\nDownload the report from [this workflow run](${run_url}) → Artifacts → playwright-report`
      });

Step 5: Keeping Historical Reports

By default, each deploy overwrites the previous one. To keep history, organize reports by run ID:

- name: Prepare report with run metadata
  run: |
    mkdir -p public/reports/${{ github.run_id }}
    cp -r playwright-report/* public/reports/${{ github.run_id }}/

    # Update the index to point to latest
    echo "<meta http-equiv='refresh' content='0; url=reports/${{ github.run_id }}/'>" > public/index.html

    # Build a simple history page
    echo "<html><body><h1>Test Reports</h1><ul>" > public/history.html
    for dir in public/reports/*/; do
      run_id=$(basename $dir)
      echo "<li><a href='reports/$run_id/'>Run $run_id</a></li>" >> public/history.html
    done
    echo "</ul></body></html>" >> public/history.html

- name: Upload combined site
  uses: actions/upload-pages-artifact@v3
  with:
    path: public/

Note: this approach only keeps reports within a single workflow run's artifact. For persistent cross-run history, you'd need to commit the reports to a branch or use an external store.

Step 6: Parallel Test Sharding

For large suites using Playwright sharding, merge reports before publishing:

jobs:
  test:
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - name: Run tests (shard ${{ matrix.shard }}/4)
        run: npx playwright test --shard=${{ matrix.shard }}/4

      - name: Upload shard results
        uses: actions/upload-artifact@v4
        with:
          name: blob-report-${{ matrix.shard }}
          path: blob-report/
          retention-days: 1

  merge-reports:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Download shard results
        uses: actions/download-artifact@v4
        with:
          pattern: blob-report-*
          merge-multiple: true
          path: all-blob-reports/

      - name: Merge reports
        run: npx playwright merge-reports --reporter html ./all-blob-reports

      - name: Upload merged report
        uses: actions/upload-pages-artifact@v3
        with:
          path: playwright-report/

  deploy:
    needs: merge-reports
    # ... same deploy job as before

The blob reporter (--reporter blob) outputs binary files optimized for merging. merge-reports then generates the final HTML from them.

Viewing the Report

The report URL is:

https://<org>.github.io/<repo>/

From the deployment job output: look for the page_url output from actions/deploy-pages@v4. GitHub also links it from the Actions run summary.

The Playwright HTML report supports:

  • Filtering by status (failed, flaky, skipped, passed)
  • Searching by test name
  • Clicking into any test for screenshots, traces, and console logs
  • Opening traces in Playwright Trace Viewer via a built-in link

Troubleshooting

Report deploys but shows blank page: The HTML report uses relative paths. Ensure you're uploading the contents of playwright-report/, not the folder itself.

Permission denied on deploy: Your repo needs Settings → Pages → Source set to "GitHub Actions". Branch-based source won't work with upload-pages-artifact.

Tests pass locally but fail in CI: Add --workers=1 temporarily to eliminate parallelism as a variable, then re-enable once the root cause is found.

Large reports hit Pages size limit: GitHub Pages has a 1GB site limit and 100MB file limit. For very large traces, use trace: 'on-first-retry' instead of retain-on-failure.

Summary

The workflow is: configure Playwright's HTML reporter, enable GitHub Pages with Actions as the source, run tests with continue-on-error: true, and upload playwright-report/ with upload-pages-artifact. Your team gets a live, browsable URL after every CI run — no servers, no third-party services.

Read more