Playwright on GitHub Actions (Free Tier)
Push a small Playwright repo to GitHub, add a workflow file, and watch your tests run on every push — on GitHub's runners, for free, with the HTML report downloadable as a build artifact.
1 Goal
Take the Playwright project you built in Practice 01 (or a fresh copy), push it to a public GitHub repository, and add a .github/workflows/test.yml file that runs npx playwright test on every push and pull request. The workflow should install Node, cache dependencies, install Playwright browsers, run the suite, and upload the HTML report as a downloadable artifact. By the end, a teammate clicking "Actions" on your repo should see green runs for good commits, red runs for bad ones, and be able to download the HTML report with the trace viewer attached — without ever cloning your code. That is the bar for "CI" in 2025.
2 Sign up for GitHub & understand the free tier
- Create a free GitHub account Go to github.com/signup. Pick a username, use any email, and verify. The free plan is permanent; no credit card required.
-
Understand what you get on the free tier
GitHub Actions is metered, but the headline numbers are generous for public repos.
Repo type Actions minutes / month Storage Notes Public Unlimited Unlimited (subject to fair-use) Use this for the exercise — no meter. Private (Free plan) 2,000 / month 500 MB Linux minutes count 1×, Windows 2×, macOS 10×. Bottom line: Make the exercise repo public. Playwright runs on Linux by default, which is the cheapest runner, and the public-repo tier has no minute cap. You can run the workflow a hundred times a day and pay nothing. -
Install Git locally
If
git --versionalready returns a version, skip this step. Otherwise download from git-scm.com/downloads.Install via winget (or use the MSI)winget install --id Git.Git -e git --version
Terminal (Xcode Command Line Tools)xcode-select --install git --version
Debian / Ubuntusudo apt-get update && sudo apt-get install -y git git --version
-
Configure your Git identity once
All platforms
git config --global user.name "Your Name" git config --global user.email "you@example.com"
3 Project setup
You can reuse the project from Practice 01 or scaffold a fresh one. The steps below assume a fresh repo so the exercise stands alone.
-
Create and enter the project folder
All platformsThe
mkdir playwright-ci-lab cd playwright-ci-lab npm init -y npm install --save-dev @playwright/test@latest npx playwright install --with-deps
--with-depsflag installs system libraries on Linux; it's a no-op on Windows and macOS. -
Target this final layout
Tree
playwright-ci-lab/ .github/ workflows/ test.yml tests/ smoke.spec.ts playwright.config.ts package.json package-lock.json .gitignore -
Add a
.gitignore.gitignorenode_modules/ playwright-report/ test-results/ blob-report/ playwright/.cache/
4 Write the tests, config, and workflow
Four files total. Paste each one in full.
a) playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html', { open: 'never' }], ['list']],
use: {
actionTimeout: 10_000,
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
});b) tests/smoke.spec.ts
import { test, expect } from '@playwright/test';
test('example.com loads and has the expected heading', async ({ page }) => {
await page.goto('https://example.com');
await expect(page.getByRole('heading', { name: 'Example Domain' })).toBeVisible();
});
test('playwright.dev homepage renders', async ({ page }) => {
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
});
test('saucedemo standard user can log in', async ({ page }) => {
await page.goto('https://www.saucedemo.com/');
await page.getByPlaceholder('Username').fill('standard_user');
await page.getByPlaceholder('Password').fill('secret_sauce');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL(/inventory\.html/);
});c) package.json — add a test script
Open package.json and replace the scripts block so it reads:
"scripts": {
"test": "playwright test"
}d) .github/workflows/test.yml — the CI file. Create the folders first:
New-Item -ItemType Directory -Force -Path .github/workflows | Out-Null
mkdir -p .github/workflows
Then write .github/workflows/test.yml with this exact content:
name: Playwright Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
name: Run Playwright suite
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- name: Check out the repository
uses: actions/checkout@v4
- name: Set up Node.js 20
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
- name: Run Playwright tests
run: npx playwright test
- name: Upload HTML report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 7
if: always() on the upload step? Without it, the upload only runs if the previous step succeeded. When tests fail, that is exactly when you most want the report. if: always() runs the step regardless, so red builds still publish their artifacts.
@v4, not @main. Pinning to a major version is the balance between "I want security patches" and "I don't want my workflow to break overnight when someone merges a breaking change upstream."
5 Push the repo & verify the run
-
Create a new public repo on GitHub
Go to github.com/new. Name it
playwright-ci-lab. Select Public. Do NOT tick "Initialize with README" — you'll push your existing files. -
Initialise git locally and push
Replace
YOUR-USERNAMEwith your GitHub handle.All platformsOn the first push, GitHub prompts for credentials. Use a personal access token or the browser-based flow that the Git Credential Manager opens for you.git init git add . git commit -m "Initial Playwright CI lab" git branch -M main git remote add origin https://github.com/YOUR-USERNAME/playwright-ci-lab.git git push -u origin main
-
Watch the workflow run
Open
https://github.com/YOUR-USERNAME/playwright-ci-lab/actions. You'll see a run called "Playwright Tests" with a yellow dot. Click into it to watch live logs. The full run on a public Ubuntu runner takes roughly two to three minutes: most of that is the browser download on first run, then about thirty seconds for the tests themselves. -
Expected result
A green tick next to "Run Playwright Tests". If all three tests pass, the last log line in the Run Playwright tests step reads:
Actions log (abridged)Scroll down to the Artifacts section at the bottom of the run summary — you'll see
3 passed (18.4s)
playwright-reportlisted.
6 Download the report & open the trace
-
Click the artifact to download it
The download is a zip. Extract it — you'll get a
playwright-report/folder withindex.htmland adata/subfolder. -
Open the report locally
You can't just double-click
index.html; Playwright reports need to be served because they load JSON viafetch(). Run it through Playwright's own server:All platformsA browser opens atnpx playwright show-report ./playwright-report
http://localhost:9323with the same report you'd see locally — traces, screenshots, videos, everything. -
Force a failure on purpose (optional)
Change one assertion in
smoke.spec.tsto fail (e.g.toHaveTitle(/NotReallyPlaywright/)), commit, push. Watch Actions run red, then download the artifact. The failing test's trace tab should contain the full recording. This is the same triage loop as Practice 01 — only now the failure happened on a server you don't own.
actions/checkout → setup-node with cache → npm ci → playwright install --with-deps → run → upload on always. Yours is missing two things a real team adds: sharding (Challenge 1) and a status badge in the README. That's it.
7 Troubleshooting
The workflow doesn't appear in the Actions tab after pushing
Most likely your file isn't at the exact path .github/workflows/test.yml. GitHub only discovers workflow files under that literal folder structure, and the file extension must be .yml or .yaml. Check in the GitHub web UI that the path renders as you expect. If it's there but "No workflow runs yet", the YAML has a syntax error — open the file in GitHub's web editor; a red banner near the top will show the parse error.
Error: Process completed with exit code 1 and no obvious test failure
Expand the Install Playwright browsers step. The most common cause is running npx playwright install without --with-deps on Linux, which leaves Chromium unable to launch. Always use npx playwright install --with-deps on GitHub-hosted Ubuntu runners.
The artifact uploads but show-report says "No data"
You uploaded the wrong folder. Playwright's HTML report lives in playwright-report/, not test-results/. The path in the workflow must match exactly: path: playwright-report/. If you changed the outputFolder in playwright.config.ts, update the workflow path to match.
Free-tier minutes panic — "Am I about to get billed?"
If the repo is public, minutes are unlimited — the meter doesn't apply. Double-check the repo visibility at Settings → General → Danger Zone. If it's private, you have 2,000 Linux minutes per month on the free plan; billing settings at Settings → Billing and plans will show your current usage. You cannot be charged by accident — GitHub will simply stop running jobs when the free allowance is exhausted unless you have added a payment method and lifted the spending cap.
8 Challenge
Make it look like a real team's CI
Challenge 1 — shard the test run across two jobs. Add a strategy: matrix: block with shardIndex: [1, 2] and shardTotal: [2], then pass --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} to npx playwright test. Each job uploads its own artifact with a name suffixed by the shard number. Sharding is how real projects keep CI under five minutes on a 300-test suite, and it is a standard interview talking point for lead-level roles.
Challenge 2 — add a status badge to the README. Create a README.md at the repo root with the line . Push it. The badge turns green on a passing run, red on failure. It is the single cheapest signal you can give reviewers that your repo actually tests itself — and it costs zero minutes to keep green.