An E2E suite is useful only when it runs automatically on every change. Run by hand and by mood, it protects nothing: a regression will be noticed already in production. CI's job is to make checking inevitable, but E2E in CI has a special requirement beyond the ordinary pipeline. These tests fail from a complex confluence of circumstances - data state, timing, environment - and without saved evidence a failure on CI cannot be taken apart.
The basic pipeline is built predictably. A job on ubuntu checks out the code, sets up Node of the needed version with an npm cache, runs npm ci for a deterministic install by the lockfile. Then the step key for Playwright - npx playwright install --with-deps, which brings in the browsers and their system dependencies. Next the application is built and npx playwright test is run; the runner itself starts the webServer and passes the scenarios in the configured browsers.
The critical difference of an E2E pipeline is saving failure artifacts. The upload-artifact step saves the playwright-report folder after the run, and with a condition that it runs even when tests failed. This is fundamental: artifacts are needed precisely when tests went red, not when everything is green. Without this condition the job ends on a test failure and the report with traces - the only key to analysis - simply is not saved.
It helps to see the basic workflow in full once, to have something to build on. Below is a GitHub Actions job with browser installation, the run and saving the report with retention. You return to this form when setting up E2E in a new project: it already contains both the mandatory install --with-deps and saving artifacts on failure, without which the pipeline either does not start or gives no way to analyze a red run.
The artifact-saving condition deserves separate attention. The report is written with an if that fires if the job was not cancelled - then the artifact is saved both on a test failure and on success, but not on a manual cancellation. Retention is set sensibly - about two weeks: keeping reports with traces longer is expensive in storage, and shorter risks losing evidence of a failure that is not analyzed right away.
A mature pipeline is arranged more complexly than one run of everything at once. A fast smoke set of a few critical paths blocks merge and deploy - it is short and gives quick feedback. A full regression is run in parallel, without delaying the developer. The report and trace are always saved on failure. And a smoke on production after deploy checks that the live system works - but only read-only, without changing real data.
The split by speed and purpose is the key to E2E not slowing delivery. If every push waits for a half-hour full run in all browsers, developers will start bypassing the check or merging around it. A fast smoke on the critical path gives a signal in minutes and holds the main guarantee, while a heavy regression goes its own way and does not stand on the critical path to merge. Feedback speed here is part of reliability.
The typical E2E failures in CI are predictable. Forgetting install --with-deps - and the browser will not start on clean ubuntu, failing the whole job before the first test. Not saving artifacts on failure - and a red run on CI stays un-analyzable. And running the whole heavy suite synchronously on every push, stretching feedback to half an hour and pushing the team to bypass the check. Install browsers with dependencies, save failure evidence and separate smoke and regression.
name: Playwright
on: [push, pull_request]
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npx playwright install --with-deps # browsers and system dependencies
- run: npm run build
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }} # save even when tests failed
with:
name: playwright-report
path: playwright-report/
retention-days: 14