CI is a robot inspector that builds and tests each proposed change. CD moves an approved change onward.
push → install → test → report → permit or block
Series: Playwright Zero to Expert | Demo app used throughout this series: saucedemo.com
Every test you’ve run so far has been run manually, by you, on your own machine, whenever you happened to remember to. Real teams can’t rely on that — a test suite that only runs when someone remembers to run it will, eventually, fail to catch a real regression simply because nobody happened to run it at the right moment. This part is about making that automatic.
What CI and CD Actually Mean
CI (Continuous Integration)
is the practice of automatically building and testing code every time it’s changed — typically, every time someone pushes to a shared repository (recall Part 5’s git push). The core idea, worth sitting with: instead of a team occasionally, manually testing a large batch of accumulated changes right before a release (finding problems late, and finding many of them at once, tangled together and hard to isolate), CI tests every small change immediately, as it happens — catching a problem within minutes of it being introduced, while it’s still small, isolated, and fresh in the mind of whoever just wrote it.
CD (Continuous Delivery/Deployment)
extends this further — automatically preparing (Delivery) or even directly releasing (Deployment) code to real users once it’s passed all the automated checks, without requiring a separate, manual release process.
Analogy: The Conveyor Belt Quality Control Robot Imagine running a physical toy manufacturing factory:
- Manual Testing (No CI): Workers paint, package, and load toys onto trucks in large batches. On Friday afternoon, right before shipping, the manager pulls a random toy from a box to test if the wheels spin. They find the wheels are glued shut. Now they must open all 10,000 packages shipped this week to find which ones are broken, holding up the shipment (finding errors late in massive blocks).
- Continuous Integration (The QC Robot): You install an automated sensor robot directly over the conveyor belt. The moment a worker finishes attaching wheels to a single toy and pushes it down the line (
git push), the robot picks it up, tests the wheels (runs Playwright tests), and sounds an alarm within 3 seconds if it’s broken. The error is fixed immediately before the toy is even packaged.
📊 Visual Flowchart: The GitHub Actions Execution Pipeline
Here is the step-by-step pipeline executed inside a fresh cloud container on every code change:
graph TD
Trigger["Developer runs: git push / creates PR"] --> SpinVM["1. CI Server launches fresh VM<br>(ubuntu-latest)"]
SpinVM --> Checkout["2. Clone Repository<br>(actions/checkout)"]
Checkout --> Node["3. Setup Node.js runtime<br>(actions/setup-node)"]
Node --> Install["4. Lock dependency versions<br>(npm ci)"]
Install --> Browsers["5. Install browser binaries & dependencies<br>(npx playwright install --with-deps)"]
Browsers --> RunTests["6. Execute Playwright suite<br>(npx playwright test)"]
RunTests --> Decision{"Did tests pass?"}
Decision -->|Yes| Merge["7. Green Build: Allow pull request merge"]
Decision -->|No| Upload["8. Red Build: Block merge & upload reports"]
Upload --> Artifacts["9. Save HTML report, traces & visual diffs"]
Why does this matter specifically for QA, beyond general software engineering hygiene? Because a test suite that only exists on your local machine provides essentially zero actual protection to the team — a teammate could introduce a genuine regression, and nobody would know until it reached production, simply because your suite never actually ran against their change. CI is what turns your test suite from “something I can run” into “something that actually, automatically protects the whole team, on every single change, without anyone needing to remember.”
GitHub Actions — a Working Pipeline
Recall Part 6’s setup prompt, which offered to generate a GitHub Actions workflow automatically. Here’s a real one, explained properly:
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
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
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Walk through this properly, since every line here is something you now genuinely understand from earlier parts:
on: push / pull_request— defines exactly when this workflow runs: on every push tomain, and on every pull request targetingmain. This is precisely how “every change gets tested automatically” is actually implemented — no human needs to remember to trigger anything.runs-on: ubuntu-latest— the CI provider spins up a brand new, clean virtual machine for every single run, meaning your suite runs in a genuinely fresh environment every time, with none of the “works on my machine” contamination Part 4 and Part 5 both touched on.actions/checkout@v4— the CI equivalent ofgit clone, pulling the actual code being tested onto this fresh machine.actions/setup-node@v4— installs Node.js (Part 4) on this machine, since it obviously doesn’t come pre-installed.npm ci— notice this is notnpm install.npm ci(clean install) specifically usespackage-lock.json(Part 4) to install exact, locked versions, and is both faster and stricter thannpm installfor CI purposes — it fails outright ifpackage-lock.jsonandpackage.jsonare inconsistent, rather than silently trying to resolve a slightly different set of versions, which is exactly the “works for me, not for you” scenario Part 4 warned about, now actively prevented in CI specifically.npx playwright install --with-deps— installs Playwright’s browser binaries (Part 6) and their underlying OS-level dependencies, since this fresh CI machine, unlike your own development machine, has never had a browser installed on it before at all.if: always()on the artifact upload step — genuinely important, and easy to miss: without this, the HTML report (Part 23, Part 34) would only be uploaded when the entire job succeeds — but the situation you most need that report for is precisely when tests fail.if: always()ensures the report is uploaded regardless of the outcome, which is exactly when it’s actually needed most.
Jenkins and Docker, Briefly
Jenkins
is an older, still widely used, self-hosted CI/CD tool — conceptually accomplishing the same thing as GitHub Actions (running your pipeline automatically on a trigger), but self-managed on infrastructure your own organization runs and maintains, rather than a fully managed service like GitHub Actions. Many large, established enterprises still run Jenkins, often for reasons of existing infrastructure investment or specific compliance/security requirements, which is worth knowing as context even if GitHub Actions (or a similar managed service like GitLab CI or CircleCI) is more common for newer projects.
Docker
genuinely deserves its own full part — Part 33, immediately following this one — but it’s worth a brief mention here because it connects directly: runs-on: ubuntu-latest in the example above is, in a real sense, already running your tests inside a fresh, isolated environment. Docker takes this same idea further, letting you define and fully control that exact environment yourself (an exact OS version, exact pre-installed dependencies), rather than relying on whatever a CI provider’s generic “latest Ubuntu” image happens to currently provide — genuinely important once environment-specific consistency becomes a real, ongoing concern.
How It Works in a Real Test Run
A CI job checks out one commit, installs the locked dependency tree, installs compatible browsers and operating-system dependencies, starts or reaches the test environment, runs tests, and uploads reports even when tests fail.
Separate installation failure, environment unavailability, test failure, and artifact-upload failure in logs. Pin the Playwright package and container image compatibly, protect secrets, cache carefully, and use sharding only after tests are isolated.
Interview Questions
Q: What is the core problem CI actually solves, compared to a team relying on developers manually running tests before merging?
Ans: Manually run tests depend entirely on individuals remembering to run them, consistently, on every single change — a step that inevitably gets skipped under time pressure or simply forgotten, meaning problems accumulate and are discovered late, in large, tangled batches that are hard to isolate. CI runs the full test suite automatically on every change, catching problems within minutes of being introduced, while they’re small, isolated, and easy to trace back to the specific change that caused them.
Q: Why does the example workflow use npm ci instead of npm install?
Ans: npm ci installs exact, locked versions directly from package-lock.json, and fails outright if the lockfile and package.json are inconsistent, rather than attempting to resolve a potentially different set of compatible versions. This is both faster and stricter than npm install, which matters specifically in CI because it actively prevents the “different versions on different machines” problem Part 4 described, rather than just hoping it doesn’t happen.
Q: Why is npx playwright install --with-deps a necessary step in a CI pipeline, when it’s typically not needed as a separate step on a developer’s own machine?
Ans: A CI job runs on a brand-new, clean virtual machine every single time, with no browsers or their underlying OS-level dependencies pre-installed — unlike a developer’s own machine, which likely already has real browsers installed from everyday use, and may have already run Playwright’s browser install previously. This step ensures the fresh CI environment has everything genuinely necessary to actually launch and run real browsers before the test suite attempts to use them.
Q: Why is if: always() important on the artifact upload step, specifically for a test-reporting workflow?
Ans: Without it, the report and other artifacts would only be uploaded when the overall job succeeds — but the scenario where that report is actually most needed is precisely when tests fail, since that’s when someone needs to investigate what went wrong. if: always() ensures the report is uploaded regardless of whether the tests passed or failed, making it available exactly in the situation where it’s genuinely useful.
Q: What’s the conceptual relationship between a CI pipeline running on ubuntu-latest and Docker, even without using Docker explicitly?
Ans: Running on ubuntu-latest already provides a fresh, isolated environment for every run, which is the same underlying goal Docker pursues. Docker takes this further by letting a team define and fully control the exact contents of that environment themselves — an exact OS version, exact pre-installed dependencies — rather than relying on whatever a CI provider’s generic base image currently happens to include, which becomes genuinely valuable once precise environment consistency is a real requirement.
Q: A test suite passes reliably when run locally by every developer, but consistently fails in CI. What are some CI-specific factors you’d investigate, beyond assuming the tests themselves are simply wrong?
Ans: I’d check whether the CI environment genuinely has the same dependency versions installed (verifying npm ci is actually being used, and that the lockfile is up to date and committed), whether Playwright’s browsers and their OS dependencies were correctly installed on the fresh CI machine, and whether any environment variables or secrets (recall Part 16 and Part 21) that the suite depends on are actually correctly configured in the CI environment specifically, since a fresh CI machine won’t have any of a developer’s local, potentially undocumented environment setup.
Exercises — Part 32
Understand: Explain, in your own words, why testing every small, individual change immediately is generally better than testing a large, accumulated batch of changes right before a release, using a concrete scenario involving three unrelated changes made by three different developers in the same week.
Simple Practice: Set up a GitHub Actions workflow (using the example in this part as a starting point) for a Playwright project you’ve built earlier in this series, push a change, and confirm the workflow runs automatically and reports its result.
Real-World Scenario: Deliberately introduce a failing test into your project, push it, and confirm the GitHub Actions run fails and that the HTML report artifact is still uploaded and downloadable despite the failure — walk through actually downloading and reviewing it from the Actions tab.
Challenge: Research how to configure a GitHub Actions workflow to run your Playwright suite on a schedule (a nightly cron trigger) in addition to on push/pull request, connecting this back to Part 14’s discussion of running a full regression suite nightly versus a fast smoke suite on every commit.
Next: Part 33 — Docker
— images, containers, and running your entire Playwright suite in a fully controlled, reproducible environment.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed