CI/CD Integration — Automating the Pipeline
Tests are useless if they aren't run. Learn how to integrate Playwright into GitHub Actions, cache browsers for lightning-fast builds, and generate HTML reports for every commit.
📖 The Story: The 3 AM Deploy
A development team was preparing to launch a new feature. The QA engineer ran the Playwright suite locally on their Mac. All 200 tests passed. The code was merged and deployed to production at 3 AM.
At 8 AM, users started complaining. The checkout page was completely broken on Windows Firefox. The QA engineer ran the tests again on their Mac—they passed. The issue? The QA engineer had a different timezone, causing a date API to return a different value, which broke a hidden UI element only on Windows.
If the tests had been running in CI on every commit, the build would have failed immediately when the developer pushed the code. Instead, the bug slipped through because "it worked on my machine."
"Works on my machine" is a CI problem, not an excuse.
🎯 Why Integrate into CI/CD?
Automated
Catch bugs on every commit, not just when QA remembers to run tests.
Cross-Platform
Run tests on Linux, Mac, and Windows to catch OS-specific bugs.
Block Bad Deploys
Prevent broken code from ever reaching production.
Artifacts
Automatically save HTML reports and traces for every run.
🌍 Real World Example
A developer pushes a PR. GitHub Actions spins up a Linux machine, checks out the code, installs dependencies, installs Playwright browsers, and runs the tests. If a test fails, the PR is blocked from merging. The developer downloads the HTML report artifact, opens the Trace Viewer, finds the bug, and fixes it—all without bothering the QA team.
🧒 Explain Like I'm 10
Imagine you are writing a book. Every time you finish a chapter, you give it to your mom to read. She checks for spelling mistakes. If she finds one, you fix it before writing the next chapter.
CI/CD is like having a robot mom. Every time you save a file, the robot instantly reads it, checks it against a dictionary, and highlights mistakes in red. You don't even have to ask.
🎓 Professional Explanation
Continuous Integration (CI) is the practice of merging all developer working copies to a shared mainline several times a day, with automated tests running on each merge. Playwright is designed for CI. It provides a --with-deps flag to automatically install OS-level browser dependencies (like libgbm) on Linux.
To optimize CI, we use caching. Playwright browsers are ~300MB. Downloading them on every run wastes 30 seconds. By caching the ~/.cache/ms-playwright directory, we cut that time to 1 second.
📊 The CI Pipeline Flow
Standard Playwright GitHub Actions Flow
npx playwright test⚙️ GitHub Actions Setup
Here is the standard, production-ready workflow file for Playwright.
name: Playwright Tests on: push: branches: [ main, master ] pull_request: branches: [ main, master ] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - 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 uses: actions/upload-artifact@v4 if: always # Upload even if tests fail with: name: playwright-report path: playwright-report/ retention-days: 30
The if: always line is critical. If a test fails, the GitHub Action step is marked as "Failed". Without if: always, the upload step would be skipped, and you'd never get your report to debug.
⚡ Caching Browsers (Speed Hack)
Downloading browsers takes 20-30 seconds. Caching them takes 1 second. Add this step before playwright install.
- name: Get Playwright Version id: pw-version run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT - name: Cache Playwright Browsers id: cache-pw uses: actions/cache@v4 with: path: ~/.cache/ms-playwright key: ${{ runner.os }}-pw-${{ steps.pw-version.outputs.version }} - name: Install Playwright Browsers if: steps.cache-pw.outputs.cache-hit != 'true' run: npx playwright install --with-deps
This caches browsers based on the exact Playwright version. If package.json bumps the version, the cache key changes, and it downloads the new browsers. Otherwise, it uses the cache.
🐳 The Docker Strategy
Instead of installing Node and Browsers on a raw Ubuntu runner, you can use Playwright's official Docker image. This guarantees 100% consistency.
jobs: test: runs-on: ubuntu-latest container: image: mcr.microsoft.com/playwright:v1.42.0-jammy options: --user 1001 # Prevents permission issues steps: # ... checkout, npm ci ... # No need for 'playwright install', image has them! - run: npx playwright test
⚠️ Common Mistakes
Mistake 1: Uploading the entire folder without retention limits
If you don't set retention-days, GitHub Actions will keep your 50MB HTML reports forever. You will run out of artifact storage quickly. Set it to 7 or 14 days.
Mistake 2: Running Tests on Webhooks for irrelevant folders
If a developer only changes a README file, why run 1,000 E2E tests? Use paths-ignore to skip the workflow for documentation changes.
on: push: paths-ignore: - 'README.md' - 'docs/**'
✅ Best Practices
- Always use
npm ciinstead ofnpm installto ensure deterministic dependency versions. - Set
timeout-minutes: 60so a hung test doesn't burn your CI minutes for 6 hours. - Use
if: alwayson artifact uploads to ensure you get reports on failure. - Use the Docker image if your CI environment is inconsistent.
🏗️ Senior Engineer Deep Dive
CI Flakiness vs Local Flakiness
CI machines are inherently slower than local MacBooks. They have less RAM, shared CPUs, and no GPU. If a test passes locally but fails in CI, it's usually a timing issue. The CI takes 500ms longer to render an element, and your hardcoded sleep fails. This is why auto-waiting is mandatory, and why you should never rely on waitForTimeout in CI.
Sharding in CI
If your suite takes 20 minutes, you can configure a CI matrix to run 4 shards simultaneously, dropping the time to 5 minutes. (See Chapter 8.2 for the exact matrix YAML).
💼 Interview Questions
Show Answer
.github/workflows. I use actions/checkout, setup Node, run npm ci, run npx playwright install --with-deps, and finally run npx playwright test. I upload the report using actions/upload-artifact with if: always.--with-deps flag when installing Playwright?Show Answer
libnss3 or libgbm) to run. The --with-deps flag tells Playwright to use the OS package manager (like apt on Ubuntu) to install these missing system dependencies automatically.Show Answer
actions/cache@v4. I cache the ~/.cache/ms-playwright directory. To ensure the cache invalidates when Playwright updates, I use the Playwright version (extracted via Node) as the cache key. If the cache hits, I skip the playwright install step.Show Answer
playwright-report and test-results (trace) folders as artifacts using if: always. I download those artifacts from GitHub, open the HTML report locally, and click through to the Trace Viewer to see exactly what happened in CI.🏋️ Practical Exercise
Your First Action
- Create a new GitHub repository.
- Add a simple Playwright test.
- Create the
.github/workflows/playwright.ymlfile using the code from this lesson. - Push to GitHub.
- Go to the "Actions" tab and watch it run.
- Once finished, download the "playwright-report" artifact and open
index.htmllocally.
🚀 Mini Project
🏗️ The Optimized Pipeline
- Take the workflow you built in the practical exercise.
- Add the "Caching Browsers" step to it.
- Push a commit. Watch it run (it should download browsers).
- Push another commit immediately. Watch it run (it should skip the download and use the cache, saving 30 seconds).
📝 Quiz
Test your understanding. Click an option to check your answer.
--install-os--with-deps--linux--system-deps--with-deps automatically installs required OS packages like libnss3.if: always on the artifact upload step?if: always forces the upload so you can debug the failure./usr/local/bin./node_modules~/.cache/ms-playwright/tmp/playwright~/.cache/ms-playwright is the default cache directory for Playwright binaries.npm installnpm run buildnpm cinpm updatenpm ci installs exactly what is in package-lock.json, ensuring deterministic builds.retention-days: 14 do?if: readme-changedpaths-ignore in the workflow triggerpaths-ignore: ['README.md'] tells GitHub Actions not to trigger the workflow for those files.playwright install step downloads the ~300MB binaries every time, slowing down the pipeline.❓ FAQ
Q: Does Playwright work with Jenkins or GitLab CI?
A: Yes, perfectly. The concepts are identical. You just use Jenkins' sh 'npx playwright install --with-deps' or GitLab's script: block. Caching syntax differs slightly.
Q: Should I run tests on every PR or just on main?
A: Run them on every PR. It is much cheaper to block a bad PR than to revert a broken main branch.
📦 Summary
🎯 Key Takeaways
- CI/CD catches bugs automatically on every commit.
- Use
--with-depsto install OS-level browser dependencies. - Always upload artifacts with
if: alwaysto get reports on failure. - Cache the
~/.cache/ms-playwrightdirectory to save 30 seconds per run. - Use the official Docker image for guaranteed environment consistency.
- Use
npm cifor deterministic dependency installation.
