QA How-To
GitHub Actions for Playwright: Step by Step (2026)
Set up GitHub Actions for Playwright with runnable YAML, browser installs, reports, traces, secure secrets, sharding, debugging, and interview answers.
23 min read | 3,032 words
TL;DR
A reliable Playwright workflow checks out code, sets up Node, runs `npm ci`, installs Playwright browsers with dependencies, executes `npx playwright test`, and uploads diagnostic artifacts. Add least-privilege permissions, concurrency cancellation, protected secrets, and measured sharding as the suite matures.
Key Takeaways
- Keep GitHub Actions as orchestration around a Playwright command that also works locally.
- Install locked npm dependencies, required browsers, and Linux dependencies on a clean runner.
- Use minimal token permissions and never expose protected secrets to untrusted pull request code.
- Upload HTML reports, traces, screenshots, and JUnit results after failures.
- Use Playwright shards and blob-report merging only after test data is isolated.
- Cancel obsolete runs, set timeouts, and measure every phase of feedback time.
- Treat retries as instability evidence, not a way to erase the first failure.
GitHub Actions for Playwright gives a repository-native way to run browser tests on every pull request, preserve traces and reports, and block unsafe merges. The reliable setup is a short workflow that installs locked Node dependencies, installs the required Playwright browsers and Linux packages, runs the same command developers use locally, and uploads evidence even when tests fail.
This step-by-step guide builds that setup, then improves it with permissions, concurrency control, sharding, report merging, secrets, and failure triage. The examples follow the current Playwright CI guidance and current major versions shown in the official workflow examples.
TL;DR
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: lts/*
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
That is the essential pipeline. Make the workflow production-ready by granting only contents: read, canceling superseded branch runs, setting a timeout, retaining traces and reports, and keeping credentials out of pull requests from untrusted forks.
| Need | Recommended mechanism |
|---|---|
| Repeatable package install | npm ci with committed lockfile |
| Browser and Linux dependencies | npx playwright install --with-deps |
| Stable CI execution | Explicit CI behavior in Playwright config |
| Failure evidence | HTML report, trace, screenshot, and selective video |
| Faster large suite | Playwright shards across matrix jobs |
| One report from shards | Blob reporter plus merge-reports |
| Safe pull request gate | Minimal permissions and protected secrets |
1. GitHub Actions for Playwright: How the Pieces Fit
A GitHub Actions workflow is a YAML file under .github/workflows. An event creates a workflow run, a job receives a runner, and ordered steps prepare and execute the test suite. For Playwright, the runner needs the source code, Node.js, npm dependencies, supported browser binaries, and operating-system libraries.
Playwright Test then handles discovery, workers, projects, retries, assertions, traces, screenshots, videos, and reports. GitHub Actions should orchestrate that command, not duplicate the test logic. If npx playwright test works from a clean local checkout, the CI workflow has a stable foundation.
The default hosted Linux runner is a sensible starting point for web testing. It is disposable, commonly used in official examples, and avoids state leaking between jobs. Windows or macOS runners are justified when the application has platform-specific behavior. Testing all operating systems on every commit multiplies time and cost, so connect each axis to a supported risk.
GitHub Actions reports job status to the pull request. Branch rules can require that status before merge. Artifacts hold the richer evidence that does not fit in a log. The Playwright reporting guide covers reporter choices in more depth.
Keep ownership clear. Application developers own product readiness and testability, SDETs own suite design and diagnostic quality, and platform owners govern runner security and reusable workflows. A workflow is successful when a developer can act on a failure without guessing which layer broke.
2. Prepare Playwright Configuration for CI
Install Playwright Test in the project and generate the browser configuration if the suite does not already exist:
npm install --save-dev @playwright/test
npx playwright install
npx playwright test
Commit package.json and the lockfile. CI should use npm ci, which installs the locked dependency graph rather than modifying it. Do not install @playwright/test@latest inside each workflow run because that disconnects the executed version from reviewed source.
A practical playwright.config.ts can adapt behavior when the standard CI environment variable is present:
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI
? [
["dot"],
["html", { open: "never" }],
["junit", { outputFile: "test-results/junit.xml" }]
]
: [["list"], ["html", { open: "never" }]],
use: {
baseURL: process.env.BASE_URL ?? "http://127.0.0.1:4173",
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure"
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] }
}
],
webServer: {
command: "npm run preview -- --host 127.0.0.1",
url: "http://127.0.0.1:4173",
reuseExistingServer: !process.env.CI,
timeout: 120_000
}
});
One worker in CI favors reproducibility on a modest hosted runner, which aligns with Playwright guidance. A larger runner may support more local workers, but cross-job sharding is easier to reason about for a large suite. Adjust video use if artifact size becomes excessive. Traces on the first retry preserve rich evidence without tracing every passing test.
If your workflow tests a deployed environment, remove webServer and provide BASE_URL securely. Do not keep both a local web server and a remote base URL without an explicit switch because the suite may test the wrong target.
3. Build the First Runnable Workflow
Create .github/workflows/playwright.yml:
name: Playwright tests
on:
pull_request:
branches: [main]
push:
branches: [main]
permissions:
contents: read
concurrency:
group: playwright-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Browser tests
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v5
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: lts/*
cache: npm
- name: Install locked dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Build application
run: npm run build
- name: Run Playwright tests
run: npx playwright test
env:
CI: "true"
- name: Upload Playwright report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v5
with:
name: playwright-report-${{ github.run_attempt }}
path: |
playwright-report/
test-results/
if-no-files-found: warn
retention-days: 14
The workflow runs for pull requests targeting main and pushes to main. Requiring the same check in branch protection closes the enforcement loop. The permissions block limits the job token to reading repository contents. The concurrency group cancels an older run for the same reference after a newer commit arrives.
The job timeout catches a hung server, browser, or test. Artifact upload uses !cancelled() so it runs after test failures but does not waste effort after explicit cancellation. Including the run attempt in the artifact name prevents naming conflicts when a run is retried.
This file assumes the repository has the scripts and paths used by the Playwright config. A workflow cannot compensate for a missing build or preview command. Run the complete sequence in a clean environment before making it a required check.
4. GitHub Actions for Playwright Browser Installation
npx playwright install --with-deps installs browser binaries and the operating-system packages required on Linux. It is the most direct setup for a GitHub-hosted Ubuntu runner. If the Playwright config declares only Chromium, install only Chromium to reduce download and disk use:
npx playwright install --with-deps chromium
Do not install one browser while configuring three projects. Playwright will fail when a missing executable is requested, which is preferable to silently skipping coverage. Keep the install command aligned with the project list.
Caching npm dependencies and caching browser binaries are different choices. actions/setup-node can cache package-manager download data while npm ci still reconstructs node_modules from the lockfile. Playwright's official CI guidance does not generally recommend caching browser binaries because restoring a large cache can take about as much work as downloading them, and Linux dependencies still require installation. Measure before adding a browser cache.
A Playwright Docker image is another valid model, especially on compatible container-capable runners. The package version in the project and the image version must match so browser executables and client expectations stay aligned. Hosted Actions workflows that use --with-deps are often simpler for a first setup.
Pinning matters. The lockfile pins the npm package. The workflow references known major versions of official actions from current documentation. Organizations with stronger supply-chain policy may pin actions to full commit SHAs and update them through reviewed automation. Whatever the policy, do not copy an unreviewed third-party action simply to save one shell command.
5. Start the Application and Verify Readiness
A browser test needs a reachable application. Playwright's webServer configuration can start a local build or development server and wait for a URL before testing. This keeps startup knowledge in Playwright configuration and works locally as well as in CI.
For a Vite application, the workflow may need a build step before the preview server:
- name: Build application
run: npm run build
- name: Run Playwright tests
run: npx playwright test
env:
CI: "true"
The config's webServer.command starts preview during test execution, and Playwright waits for the declared URL up to the configured timeout. That readiness check is better than sleep 30, which can be both longer than necessary and too short during a slow run.
For an external environment, expose the host through an environment-level variable:
- name: Run Playwright tests
run: npx playwright test
env:
CI: "true"
BASE_URL: ${{ vars.PLAYWRIGHT_BASE_URL }}
Use GitHub environment secrets only for credentials, not for ordinary nonsecret configuration. Scope environment access and approvals to the job that needs it. Never print a secret or place it in an HTML report, trace, screenshot, video, query string, or test fixture committed to source.
Health is more than an HTTP 200 from a generic root. Confirm the exact dependency needed by the suite, such as API readiness, database migrations, and seeded test data. If setup creates users or orders, make it idempotent and isolate each run by a unique identifier.
6. Publish Reports, Traces, and JUnit Results
The HTML report is a navigable bundle with test status, steps, errors, attachments, and project information. A trace can include action timing, DOM snapshots, console messages, and network activity. Together they make CI-only failures much easier to reproduce.
Use trace: "on-first-retry" instead of tracing every test unless you are performing a short diagnostic run. Constant tracing increases storage and runtime. Screenshots on failure are cheap context. Failure-only video is useful for motion or timing problems, but it can still produce large artifacts.
JUnit XML supports machine integration and test summaries. The reporter configuration above writes test-results/junit.xml. GitHub does not automatically turn arbitrary JUnit XML into a native test dashboard, but the file is portable to a trusted reporting action, external system, or later processing job. Review any additional action as executable code.
Artifact retention should match triage behavior. A short pull request report may need only one or two weeks, while regulated release evidence may require a governed external store. Longer retention is not automatically better. It increases storage and can preserve sensitive UI data.
Do not upload the entire workspace. Target known result folders and use if-no-files-found: warn or error according to the contract. A missing report after a failed test is a diagnostic defect worth fixing. The Playwright trace viewer guide explains how to inspect trace files safely.
When exposing a report, remember that HTML and attachments contain application data. Apply repository and artifact access controls, redact tokens, and use synthetic accounts.
7. Add Sharding Without Losing One Coherent Report
Playwright can divide tests into shards with --shard=current/total. GitHub Actions matrix jobs can execute those shards on separate runners. Use this after measuring a meaningful suite, not as the first response to one slow test.
Configure the blob reporter in CI because blob reports can be merged:
reporter: process.env.CI ? "blob" : "html"
A compact sharded job looks like this:
jobs:
playwright:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: lts/*
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- if: ${{ !cancelled() }}
uses: actions/upload-artifact@v5
with:
name: blob-report-${{ matrix.shardIndex }}
path: blob-report/
retention-days: 1
Set fail-fast: false so one failing shard does not cancel evidence from the others. Every shard must use the same Playwright version, configuration, and blob-report metadata. Give each worker isolated accounts and mutable data.
A later job can download all shard artifacts, merge them with npx playwright merge-reports --reporter html ./all-blob-reports, and upload one HTML report. Set needs to the matrix job and use if: !cancelled() so failed shards still lead to a merged diagnostic report.
Sharding improves wall-clock time only when runner startup, application capacity, test data, and report work do not dominate. Measure total feedback time and cost. Four shards can be slower than one runner for a small suite.
8. Design Events, Permissions, Secrets, and Environments Safely
A pull request from the same repository and a pull request from a fork have different trust. Do not expose privileged credentials to code an untrusted contributor can modify. Avoid switching to a more privileged event merely to make secrets available. Separate untrusted validation from trusted environment testing.
Grant explicit minimal permissions at workflow or job scope. Most Playwright tests only need contents: read. A result publisher might need an additional permission, but grant it only to that job. If a workflow posts comments, deploys previews, or obtains cloud identity, its threat model expands.
GitHub environments can group nonsecret variables, secrets, reviewers, and protection behavior. Use them for a controlled staging job. Bind the environment only after fast, credential-free checks succeed. Environment names and URLs should be stable, while per-run users and data should be unique.
Third-party actions can read workspace files and available credentials. Prefer official actions for checkout, runtime setup, and artifacts. Review source, permissions, releases, and maintenance before adding anything else. An action version is a software dependency, not decorative YAML.
Fork safety also affects artifacts. A malicious test could upload repository or runner data. Keep the token weak, avoid secrets, use hosted disposable runners for untrusted jobs, and restrict artifact scope. Self-hosted runners require stronger isolation because a job may reach internal networks or leave state for later work.
Document why each permission and secret exists. Delete obsolete credentials. Rotate secrets and test revocation behavior before an incident.
9. Debug CI Failures Systematically
Start with classification. A product assertion failure, selector defect, environment outage, dependency-install failure, and runner problem should not all be called "flaky Playwright." Identify the first failed step and compare it with the same commit locally.
For a browser launch failure, rerun a focused diagnostic with DEBUG=pw:browser. For a test failure, download the HTML report and trace. Inspect the action timeline, locator resolution, DOM snapshot, console, and network. The Playwright timeout troubleshooting guide helps separate slow readiness from weak synchronization.
Use the run metadata: commit SHA, attempt number, matrix values, runner operating system, Node version, Playwright version, base URL, and application build. Do not rerun repeatedly until green. A retry can collect evidence, but the first failure remains part of reliability data.
Common CI-only causes include:
- Missing Linux browser dependencies.
- A server bound to
localhostor another host different from the readiness URL. - Test data shared between parallel workers.
- Environment variables present locally but absent in Actions.
- Case-sensitive paths that worked on a developer's file system.
- Locale, timezone, viewport, or clock assumptions.
- Browser package and Docker image version mismatch.
- Artifact upload that masks the earlier test exit code.
Reproduce using the same locked install and command. If the issue depends on a hosted image change, record runner details and minimize the reproduction. Quarantine only with an owner, reason, linked defect, and expiry.
10. Optimize Feedback Time and Keep the Workflow Healthy
Measure queue time, runner setup, dependency installation, browser installation, application build, test execution, and artifact upload separately. Optimizing only npx playwright test can miss the largest delay.
Run lint, type checks, and small API tests before an expensive browser job when dependencies allow it. Use path filters carefully in monorepos, but retain a manual or scheduled route that detects incorrect filters. Cancel obsolete branch runs through concurrency. Install only tested browsers.
Use Playwright projects for meaningful browser or device configurations. Avoid creating a Cartesian product of browser, locale, role, operating system, Node version, and feature flag on every pull request. Put a representative gate on pull requests, then schedule broader compatibility coverage.
Review workflow dependencies on a regular cadence. Update official actions and Playwright through controlled pull requests. Test artifact names and branch rules when a job is renamed because required checks can otherwise point at stale statuses. Monitor skipped, cancelled, and timed-out runs, not only test failures.
Set a service expectation for the suite itself: maximum normal duration, retry rate, quarantine count, artifact availability, and ownership. A fast workflow that frequently returns ambiguous red results is not healthy. A slow suite that always diagnoses itself may still be too expensive for pull requests. Improve both speed and signal.
Finally, keep the workflow readable. Reusable workflows or composite actions can reduce repetition across many repositories, but keep inputs and permissions explicit. The test command should remain runnable without GitHub Actions.
Interview Questions and Answers
Q: What are the minimum steps to run Playwright in GitHub Actions?
Check out the repository, set up Node.js, run npm ci, install browsers and Linux dependencies with npx playwright install --with-deps, then run npx playwright test. Upload the HTML report and test results after failure. I also add minimal permissions and a job timeout before making it required.
Q: Why use npm ci instead of npm install in CI?
npm ci installs from the committed lockfile and fails when package metadata is inconsistent. It gives a clean, repeatable dependency graph and does not rewrite the lockfile. That makes the CI result correspond to reviewed source.
Q: How do you handle flaky Playwright tests in Actions?
I retain the first failure, capture a trace on the first retry, and classify the cause before changing timeouts. A bounded retry is diagnostic, not proof of stability. Repeated instability gets an owner and either a quick fix or time-limited quarantine.
Q: How do you parallelize a large Playwright suite?
I use Playwright sharding across GitHub matrix jobs, give each shard isolated data, and set fail-fast: false. Shards produce blob reports, which a dependent job merges into one HTML report. I measure whether the additional runners improve total feedback time.
Q: How do you protect secrets in a pull request workflow?
I do not expose secrets to untrusted fork code and keep the default token at minimal permissions. Trusted environment tests run in a separate protected job or workflow. I also inspect artifacts and logs because tests can leak data there even when the secret is masked.
Q: What should be uploaded after a failed test?
I upload the Playwright HTML report, traces, failure screenshots, selective video, and JUnit XML when needed. I avoid the entire workspace and choose a retention period based on triage needs. The artifacts must not contain production credentials or personal data.
Q: How do you debug a test that fails only in GitHub Actions?
I reproduce the locked command, compare Node, Playwright, browser, operating system, configuration, and target build, then inspect the trace and logs. I verify application readiness and test data isolation. I classify runner or environment faults separately from product and test defects.
Common Mistakes
- Installing Playwright packages dynamically instead of using the committed lockfile.
- Running
npx playwright testbefore installing browser binaries and Linux dependencies. - Installing every browser when the configuration uses only one.
- Caching browser binaries without measuring restore time and dependency behavior.
- Using fixed sleeps instead of Playwright's
webServerreadiness support. - Omitting
contents: readand accepting broader default token permissions. - Exposing staging credentials to untrusted pull request code.
- Uploading reports only on success, which removes evidence when it is most useful.
- Tracing and recording video for every passing test without a retention reason.
- Enabling matrix parallelism before isolating test accounts and data.
- Leaving
fail-fastenabled for shards and losing other failure evidence. - Retrying until green and reporting the final attempt as a clean pass.
- Combining too many browsers, platforms, roles, and flags in every pull request.
- Renaming a required job without updating branch protection.
- Hiding test commands inside YAML so engineers cannot reproduce them locally.
Conclusion
GitHub Actions for Playwright works best as thin, secure orchestration around a reproducible Playwright project. Use a lockfile, install the exact browser coverage you declare, start or target the application explicitly, run the standard Playwright command, and preserve useful artifacts after failure.
Begin with the single-job workflow and make it a required check only after it is stable. Add protected environment access and sharding when the risk and suite size justify them. Keep permissions narrow, data isolated, and the merged report actionable, then treat the workflow itself as production test infrastructure.
Interview Questions and Answers
Describe a production-ready GitHub Actions workflow for Playwright.
It uses a clean runner, locked dependency installation, supported browser setup, explicit application readiness, the normal Playwright command, and diagnostic artifact upload. I add minimal token permissions, a timeout, obsolete-run cancellation, and branch protection. Secrets are limited to trusted jobs.
Why keep Playwright configuration outside workflow YAML?
The same reporters, projects, retries, web server, and trace behavior should be reproducible locally. The workflow owns triggers, runners, permissions, and artifact transport. Keeping test behavior in Playwright configuration reduces platform coupling.
How would you reduce Playwright CI duration?
I measure queue, install, browser, build, test, and upload time separately. Then I install only required browsers, cancel obsolete runs, improve application startup, remove test coupling, and shard a sufficiently large suite. I verify the target can handle parallel workers.
How do you make sharded results understandable?
Every shard uses the same commit and configuration and produces a blob report. A dependent job downloads and merges those blobs into one HTML report. I also record shard identity and retain all failures by disabling matrix fail-fast.
What is your strategy for CI-only failures?
I compare runtime, operating system, Playwright version, browser, environment variables, target build, locale, and timezone. Then I inspect the trace and earliest error. I reproduce the locked command and classify product, test, environment, or runner ownership.
How do you secure a Playwright workflow?
I grant minimal `GITHUB_TOKEN` permissions, avoid secrets in untrusted pull requests, review every action dependency, and prefer disposable hosted runners for untrusted work. Protected environment access is isolated to the job that needs it. Reports and traces are treated as sensitive artifacts.
What does a retry mean in Playwright CI?
A passing retry means the test or system was unstable during the first attempt. It can keep a pipeline moving according to policy, but it must remain visible in reporting. Recurring retries need ownership, diagnosis, and a deadline.
Frequently Asked Questions
How do I run Playwright tests in GitHub Actions?
Use a workflow job that checks out the repository, configures Node.js, runs `npm ci`, installs Playwright browsers with `npx playwright install --with-deps`, and runs `npx playwright test`. Upload the HTML report and test-results directory when the run is not cancelled.
Why does Playwright need `--with-deps` in GitHub Actions?
The flag installs the browser binaries and operating-system packages required on Linux. Without the Linux dependencies, a browser can fail to launch even when the Playwright npm package installed successfully.
Should I cache Playwright browsers in GitHub Actions?
Not by default. Playwright guidance notes that restoring browser caches may take similar time to downloading them, while Linux dependencies still need installation. Measure the complete job before introducing and maintaining a browser cache.
How can I see Playwright failures after a GitHub Actions run?
Configure the HTML reporter and failure evidence such as traces and screenshots, then upload their directories as artifacts with a failure-tolerant condition. Download the report, open it locally, and inspect the linked trace.
How do I shard Playwright tests in GitHub Actions?
Create a matrix of shard indexes and run `npx playwright test --shard=index/total` in each job. Use the blob reporter, upload every blob-report directory, and merge them in a dependent job to produce one HTML report.
Can GitHub Actions use Playwright secrets on fork pull requests?
Protected secrets should not be exposed to untrusted fork code. Keep the fork workflow credential-free and use a separate trusted, protected job or workflow for staging tests that require secrets.
Should Playwright use multiple workers in GitHub Actions?
Start with one worker for stability on a modest hosted runner, as current Playwright CI guidance recommends. For a large suite, compare a larger runner with cross-job sharding and choose from measured feedback time, reliability, and cost.