📖 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

1. Developer pushes code to GitHub
⬇️
2. GitHub Actions spins up Ubuntu Runner
⬇️
3. Install Node.js & Restore Cached Browsers
⬇️
4. Run npx playwright test
⬇️
5. Upload HTML Report & Trace Artifacts
⬇️
6. PR is blocked if tests fail, or merged if they pass

⚙️ GitHub Actions Setup

Here is the standard, production-ready workflow file for Playwright.

yaml · .github/workflows/playwright.yml
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.

yaml
- 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.

yaml
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.

yaml
on:
  push:
    paths-ignore:
      - 'README.md'
      - 'docs/**'

✅ Best Practices

  1. Always use npm ci instead of npm install to ensure deterministic dependency versions.
  2. Set timeout-minutes: 60 so a hung test doesn't burn your CI minutes for 6 hours.
  3. Use if: always on artifact uploads to ensure you get reports on failure.
  4. 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

Beginner
How do you run Playwright tests in GitHub Actions?
Show Answer
I create a YAML file in .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.
Intermediate
Why do we use the --with-deps flag when installing Playwright?
Show Answer
Browsers like Chromium require OS-level shared libraries (like 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.
Advanced
How do you cache Playwright browsers in CI to save time?
Show Answer
I use 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.
Scenario
Your CI pipeline fails, but you don't know why. How do you debug it?
Show Answer
I ensure my workflow is configured to upload the 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

🎯 Hands-On Practice Medium

Your First Action

  1. Create a new GitHub repository.
  2. Add a simple Playwright test.
  3. Create the .github/workflows/playwright.yml file using the code from this lesson.
  4. Push to GitHub.
  5. Go to the "Actions" tab and watch it run.
  6. Once finished, download the "playwright-report" artifact and open index.html locally.

🚀 Mini Project

🏗️ The Optimized Pipeline

  1. Take the workflow you built in the practical exercise.
  2. Add the "Caching Browsers" step to it.
  3. Push a commit. Watch it run (it should download browsers).
  4. 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.

1. What flag is required to install OS-level dependencies for browsers on Linux CI?
A) --install-os
B) --with-deps
C) --linux
D) --system-deps
Answer: B — --with-deps automatically installs required OS packages like libnss3.
2. Why must you use if: always on the artifact upload step?
A) To make the step run faster
B) So the report is uploaded even if the test step fails
C) It is required by GitHub Actions syntax
D) To prevent the workflow from failing
Answer: B — By default, if a previous step fails, subsequent steps are skipped. if: always forces the upload so you can debug the failure.
3. What directory does Playwright cache its browsers in on Linux?
A) /usr/local/bin
B) ./node_modules
C) ~/.cache/ms-playwright
D) /tmp/playwright
Answer: C — ~/.cache/ms-playwright is the default cache directory for Playwright binaries.
4. What is the benefit of using the official Playwright Docker image in CI?
A) It runs faster than Ubuntu
B) It comes with browsers and all OS dependencies pre-installed
C) It allows tests to bypass CORS
D) It is required for sharding
Answer: B — The Docker image guarantees the environment is perfectly configured, avoiding missing dependency errors.
5. Which npm command is best for installing dependencies in CI?
A) npm install
B) npm run build
C) npm ci
D) npm update
Answer: C — npm ci installs exactly what is in package-lock.json, ensuring deterministic builds.
6. Why is CI often "slower" than local development for UI tests?
A) GitHub Actions uses throttled internet
B) CI runners often have less RAM and no dedicated GPU
C) Playwright runs in debug mode in CI
D) Docker adds a 10x performance overhead
Answer: B — CI machines are shared, lower-spec instances. This can cause elements to render slower, exposing timing bugs.
7. What does setting retention-days: 14 do?
A) Keeps the workflow running for 14 days
B) Deletes the artifact after 14 days to save storage
C) Cancels the test if it runs longer than 14 days
D) Caches the browsers for 14 days
Answer: B — It automatically deletes the uploaded artifact after the specified number of days to avoid filling up cloud storage.
8. How can you skip running tests when only a README is updated?
A) Use if: readme-changed
B) Use paths-ignore in the workflow trigger
C) Write a script to check git diff
D) You cannot skip them; tests must always run
Answer: B — paths-ignore: ['README.md'] tells GitHub Actions not to trigger the workflow for those files.
9. If your CI suite takes 20 minutes, what is the best way to reduce it to 5 minutes without changing test code?
A) Run the tests on a Mac runner
B) Delete half the tests
C) Use a CI matrix to shard the tests across 4 machines
D) Disable tracing
Answer: C — Sharding distributes the files across multiple runners, cutting total time drastically.
10. What happens if you don't cache browsers in CI?
A) The tests fail immediately
B) Playwright refuses to run
C) The CI wastes 20-30 seconds downloading binaries on every single run
D) It uses the local machine's browsers
Answer: C — Without caching, the 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-deps to install OS-level browser dependencies.
  • Always upload artifacts with if: always to get reports on failure.
  • Cache the ~/.cache/ms-playwright directory to save 30 seconds per run.
  • Use the official Docker image for guaranteed environment consistency.
  • Use npm ci for deterministic dependency installation.