🔥 0
0
Lesson 7 of 9 25 min +200 XP

Running It for Real: CI & Parallel

A green suite on your laptop protects nobody. It protects the product only when it runs automatically, on every change, before code ships — and when a failure actually stops the bad code. This lesson takes our suite from "works on my machine" to "guards production."

Parallel execution: speed you get for free (if you're isolated)

Run tests one after another and a 50-test suite that averages 6 seconds each takes 5 minutes. Run them across 5 workers and it takes about 1. Playwright parallelizes across files by default:

npx playwright test --workers=5

But parallelism is a lie detector for isolation. The moment tests run at the same time, any hidden shared state surfaces as a flaky failure — two tests logging in as the same user and stomping each other's cart, or one asserting on data another is busy deleting. This is exactly the flakiness from Lesson 4, now triggered on purpose.

So parallel execution isn't a separate skill — it's the payoff for having isolated your data. If Lessons 3 and 4 are done right, turning on workers just makes the suite faster. If they aren't, parallelism is where you find out.

> Sharding takes this further: split the suite across multiple machines in CI (--shard=1/3, --shard=2/3, --shard=3/3 on three runners). Same rule — it only works if every test is independent.

The CI pipeline

Here's a complete, working GitHub Actions workflow that installs the app, runs the suite on every push and pull request, and saves the report. Drop it at .github/workflows/e2e.yml:

name: E2E Tests

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    timeout-minutes: 20
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - 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 chromium

      - name: Run tests
        run: npx playwright test

      - name: Upload report
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

A few things worth pointing out, because each one is a lesson people learn the hard way:

  • npm ci, not npm install. ci installs the exact versions from your lockfile, so CI runs what you tested — not a newer patch release that quietly changed behavior.
  • --with-deps installs the OS libraries the browser needs on a bare Linux runner. Skip it and you get a cryptic "browser failed to launch."
  • if: ${{ !cancelled() }} on the upload. You want the report especially when tests fail. Without this guard, a failed test job skips the upload step and you lose the very artifact you need to debug.
  • retries: process.env.CI ? 2 : 0 (from Lesson 4, in your config) applies here — CI is where bounded retries earn their place, absorbing rare infra hiccups.

Reports and artifacts: debugging a failure you didn't see

When CI goes red, you weren't watching. The report and the trace are how you reconstruct what happened.

The workflow above uploads the HTML report as an artifact on every run. When a test fails, that report contains the screenshot and the trace we configured in Lesson 3 (trace: 'retain-on-failure'). Download it from the run's summary page and open it:

npx playwright show-report        # open the downloaded HTML report
npx playwright show-trace trace.zip   # scrub through the failing test step by step

This is the difference between "a test failed on CI, no idea why" and "I can see the exact DOM and network state at the moment it broke." Artifacts turn a remote, invisible failure into a local, reproducible one.

The gate: when does red block a deploy?

CI running your tests is only half the value. The other half is the pipeline acting on the result. This is a policy decision, and it should be deliberate — not an accident of how someone wired the YAML.

A sane policy for an e-commerce app:

| Suite | Runs on | Blocks?

Smoke (login, catalog, reach checkout)Every push & PRYes — red smoke means the app is broken; never merge or deploy.
Full regressionPRs to main, nightlyYes on PRs. A failing regression is a real defect until proven otherwise.
Known-flaky / quarantinedNightly, reported separatelyNo — but they're tracked and getting fixed, not ignored forever.

The critical rule: a required check must be trustworthy. If you let a flaky suite block merges, developers will demand you make it non-blocking — and then it protects nothing. This is why Lesson 4 comes before this one. You earn the right to block a deploy by first making the suite deterministic. A green check has to mean the app works, or the gate is theater.

> Deploy gating in practice: mark the smoke workflow as a required status check on your main branch (GitHub → branch protection). Now the merge button is physically disabled until smoke is green. That's the whole payoff of everything so far: a bad change literally cannot reach production without a human overriding a red gate on purpose.

Key takeaways

  • Parallelism is free speed — if you're isolated. It's also the lie detector that exposes any shared state you missed.
  • A real pipeline runs on every push/PR, pins versions with npm ci, installs browsers with --with-deps, and always uploads the report (even on failure).
  • Artifacts (report + trace) turn an invisible remote failure into a reproducible local one.
  • Gate deliberately: smoke and regression block merges/deploys; quarantined flaky tests are tracked, not required. A required check only has value if it's trustworthy — which is why you kill flakiness first.

Next lesson

The suite runs, it's fast, and it guards main. In the capstone, we close the loop: we'll find a real, reproducible bug in ShopKart, write a bug report an engineer can act on, and add the regression test that makes sure it never comes back.

Handling Tricky Components