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
maintriggers 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
- Go to your repo Settings → Pages
- Set Source to "GitHub Actions" (not a branch)
- 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@v4The 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: 7Then 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 beforeThe 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.