How it works
The explanation, with examples you can run.
Order and speed
Run checks from cheapest to most expensive and stop at the first failure: type check and lint, then unit tests, then integration tests with containers, then end-to-end tests against a preview build. Cache dependencies, and with Playwright install only the browsers you use (npx playwright install --with-deps chromium).
Playwright runs test files in parallel workers on one machine (--workers, 50% of logical cores by default). For more speed, shard across machines. Each job runs one slice with --shard=1/4 and so on. Use the blob reporter, then merge the slices into one report:
# .github/workflows/e2e.yml (the test job)jobs: e2e: runs-on: ubuntu-latest strategy: fail-fast: false matrix: shard: [1/4, 2/4, 3/4, 4/4] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 22, cache: npm } - run: npm ci - run: npx playwright install --with-deps chromium - run: npx playwright test --shard=${{ matrix.shard }} --reporter=blob - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: blob-report-${{ strategy.job-index }} path: blob-report# A later job downloads all blob-report-* artifacts into one folder and runs:# npx playwright merge-reports --reporter html ./all-blob-reportsSet fullyParallel: true so the shards are balanced test by test rather than file by file.
What retries hide
Retries are tempting: set retries: 2 and the build goes green. But a retry can't tell a flaky test from a flaky product. Here a checkout fails one call in three, and every CI run still ends green:
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
If you use retries, make them visible. Playwright already reports such tests as flaky. Keep the trace from the first retry (trace: "on-first-retry"), chart the flaky rate over time, and consider --fail-on-flaky-tests on the main branch. Never retry unit tests: they have no excuse to be non-deterministic.
Quarantine, with limits
When a flaky test can't be fixed today, move it out of the gating suite (a tag, a separate project, or test.fixme with a ticket) so everyone else can trust the build again. A quarantined test protects nothing, so give quarantine limits: an owner, a ticket, a maximum number of tests and a maximum time (Fowler suggests something like a week). When a limit is reached, fixing the quarantined tests comes before new work.
Make failures easy to act on
Upload reports, traces, screenshots and container logs as artifacts for every failed job. Show the failing test's name and first error in the pull request. Re-run only the failures with --last-failed while you debug, and on large suites use --only-changed for a quick first pass before the full run.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.