typescript-ci-workflow-playwright
Gap (RR-GAP-020): the repo had zero CI wiring for Playwright E2E. Fix: a single ubuntu-latest workflow on push + PR→main running the full gate pipeline, with Playwright artifacts uploaded on failure. Since this workspace shipped empty (local-only repo, no remote), I also scaffolded a runnable repo so every step in the workflow is a real, executable gate and the workflow is a complete deliverable.
Deliverable — .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
verify:
name: Validate, unit-test, and E2E
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Validate configs
run: npm run validate:configs
- name: Typecheck
run: npm run typecheck
- name: Unit tests
run: npm test
- name: Install Playwright chromium (with system deps)
run: npx playwright install --with-deps chromium
- name: Playwright E2E
run: npx playwright test
env:
CI: "true"
- name: Upload Playwright artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-artifacts
path: |
playwright-report/
test-results/
if-no-files-found: warn
retention-days: 7
Supporting files so the gates are real (all committed on main): package.json (scripts validate:configs / typecheck / test), tsconfig.json (strict), playwright.config.ts (chromium project, CI retries/workers), configs/app.json + scripts/validate-configs.mjs, src/todo.ts + test/todo.test.mjs (unit), e2e/todo.spec.ts (E2E), package-lock.json (required by npm ci), plus scripts/verify-workflow.py (the actionlint fallback).
**Workflow validation (two independent methods, per spec):**
- `actionlint .github/workflows/ci.yml` → **PASS** (actionlint is present in this env, so I used it in addition to the fallback).
- PyYAML parse + structural assertion (`scripts/verify-workflow.py`) → **PASS**: exactly 1 job, `ubuntu-latest`; triggers on `push` + `pull_request` targeting `main`; ordered steps `npm ci` → `validate:configs` → `typecheck` → `npm test` → `npx playwright install --with-deps chromium` → `npx playwright test`; exactly one `actions/upload-artifact@v4` gated on `if: failure()` including `playwright-report`.
- Note found while building the fallback: PyYAML is YAML 1.1 and coerces the `on:` key to boolean `True` — the assertion script normalizes `wf.get("on") or wf.get(True)`. This is exactly why the spec prefers actionlint.
**Local gate run (clean `npm ci`, mirroring workflow order):** `npm ci` OK → `validate:configs` OK (1 config valid) → `typecheck` OK → unit tests **3/3 pass** → Playwright E2E **5/5 pass** on chromium.
**Edge cases tested:**
- **Failure path:** injected a deliberately failing spec → `npx playwright test` exits **1** (the condition that arms the `failure()` artifact step); removed after verification.
- **`--with-deps` root requirement:** this sandbox sets the "no new privileges" flag (no sudo), so `playwright install --with-deps` cannot run `apt-get` here; fell back to plain `playwright install chromium` (browser downloaded to cache, E2E green). On GitHub `ubuntu-latest` the runner has sudo, so `--with-deps` works as written.
- **Reproducibility:** ran the entire pipeline twice from `rm -rf node_modules`; both green. Repo is `git init`-ed on `main` with a single clean commit — workflow is ready to fire the moment a remote attaches.
**Baseline note:** the problem statement records the upstream pre-commit baseline as **713 unit + 22/22 E2E**. This environment shipped no repo source, so those exact suites are not re-runnable here; the numbers above (3 unit + 5 E2E) are the executed verification in this sandbox on the reconstructed scaffold, with the pipeline structure identical to the spec.{"model": "deepseek-v4-flash", "problem_class": "typescript-ci-workflow-playwright", "result": "passed", "tests": 8}