| name | e2e-setup |
| description | One-time bootstrap that stands up a Playwright e2e suite in a repo that has none, so the e2e-retrofit workflow can then run against it. Copies the bundled reference harness into the project, installs Playwright + browser binaries, registers the Playwright MCP, wires the harness helpers to the real app (auth, API base, login selectors) via MCP recon, writes a single smoke test to prove the wiring (feature specs are e2e-retrofit's job — setup does NOT write them), scaffolds a coverage matrix (recon-seeded or blank — the user's gated choice), and smoke-verifies. Run ONCE per repo. Use when the user says 'set up e2e', 'set up playwright', 'bootstrap e2e tests', 'add playwright to this repo', 'there's no e2e here yet', or before a first 'retrofit e2e tests' run on a greenfield repo. After this completes, day-to-day work uses the e2e-retrofit skill. |
E2E Setup — one-time Playwright bootstrap
Stands up a working Playwright e2e suite in a repo that has none. Run once per repo. Once it's done, every future run uses the e2e-retrofit skill (the many-times workflow) — this skill is the run-once seed.
Scope — what setup does and does NOT produce. Setup wires the harness to
the app and writes exactly one spec: smoke.spec.ts, just enough to prove
the wiring works end to end. It does not write feature/acceptance tests,
and it does not mark any coverage as done — that is e2e-retrofit's job,
one section per batch. Setup that emits a pile of green feature specs is
over-reaching: it has no requirements to test against, so those specs would
only assert "the app does what it currently does" (characterization), and
marking them "covered" is self-grading. Keep setup to wiring + smoke.
The reference scaffold is bundled here under harness/. See harness/README.md for its layout and the infrastructure (keep, adapt) vs example (replace with your domain) labelling of every file. The selector/assertion rules this bootstrap follows when wiring helpers live in the sibling skill's ../e2e-retrofit/patterns.md.
This skill drives two Playwrights, set up separately:
| What | Where it lives | Used by |
|---|
| Test runner | @playwright/test — runs the specs | the project's e2e dir | retrofit Phases 3–6 |
| Playwright MCP | browser-automation server Claude drives | registered in Claude Code, not the repo | recon (setup + retrofit Phase 2) |
Step 0 — Detect: is setup already done?
Look for an e2e dir with a playwright.config.ts (e.g. e2e/, tests/e2e/).
- Found → setup has already run. Stop. Tell the user to use the
e2e-retrofit skill; do not re-instantiate.
- Not found → greenfield. Continue.
Step 1 — Clarify (one round, then proceed)
HARD GATE — the target environment is a blocking prerequisite. Do not
proceed to Step 2 (or any scaffolding, install, or wiring) until the user has
given you a concrete app to point at and you have confirmed it is reachable.
The entire point of this skill is to wire the harness to a real running
app; without one there is nothing to wire, smoke-verify, or seed a matrix
from. If the repo has no app of its own (e.g. it's a library, a CLI, or this
skills repo itself), that does not license you to invent, scaffold, or
assume one — stop and ask which app/URL to target. Never guess the base
URL, never build a stand-in app unless the user explicitly asks for one.
Question 1 below is the gate; questions 2–5 can be inferred or deferred, but
question 1 cannot.
The gate is cleared by exactly one thing: the user naming the target in the
current conversation. Nothing else counts. In particular:
- Ambient artifacts are not the user. A
.playwright-mcp/ recon dump, an
existing COVERAGE.md draft, a half-written .env, prior-session logs, or a
URL in the git history tell you what was looked at once — not what the user
wants wired now. Inferring the target from these is the assumption the
gate forbids. If they make you confident, that is exactly when to ask the user
to confirm, not when to skip asking. Treating them as the answer is a gate
bypass even if they turn out to be right.
- Confidence does not collapse the gate. "I'm sure I know the target" is not
a license to proceed — a hard gate holds regardless of how certain you feel.
If you were uncertain you would ask anyway; being certain changes nothing.
- A reachability check is already proceeding. Do not
curl, browser_navigate,
or otherwise probe a candidate URL to "confirm" it before the user has named it.
Probing a guessed URL is acting on the guess. Reachability is verified after
the user gives the target (Step 1, Q1), never as a way to validate a hunch first.
If you catch yourself reasoning "the user clearly reconned X" or "the draft
points at X, so I'll just verify it" — stop. That is the bypass. Ask.
Ask only what you can't infer from the repo:
-
Target environment (REQUIRED — gate) — tests need a running app to point at. Either:
- Local: the app + API base URLs (e.g.
http://localhost:3000) and the $DEV_UP command that starts services (npm run dev, docker compose up, make dev, …); or
- Running staging/remote: the base URL of an already-running shared env to target instead.
At least one must be reachable. If only a remote env is available, there's no $DEV_UP — point .env at it. Confirm the base URL actually responds before continuing (a quick reachability check, or have the user start $DEV_UP). If you can't get a reachable target, do not scaffold — report that setup is blocked on a target environment.
-
Auth model — dev auto-login (navigating to / lands you in), or a real login form? Where do test-user credentials live? Any HTTP basic-auth on remote/QA? (No auth at all is fine — wire the auth helper to a bare navigate.)
-
e2e directory — default e2e/ at repo root. Confirm or override.
-
Targets — local only, remote/staging only, or both?
-
Tooling (optional) — issue tracker + code host CLIs present (for retrofit later)? Note but don't block; both are optional.
Step 2 — Instantiate the harness
Precondition: Step 1's gate is cleared — you have a concrete, reachable target environment. If not, go back; do not scaffold against an unknown app.
Path placeholders used below: <this-skill> = this skill's folder, .claude/skills/e2e-setup (under whichever .claude/skills/ dir it's installed in — resolve it before copying). <repo> = the target repo root (git rev-parse --show-toplevel).
Copy the bundled scaffold into the project's e2e dir:
cp -R <this-skill>/harness <repo>/e2e
This is a template — every file gets adapted or replaced (Steps 5–6). It is not run from the skill folder.
Step 3 — Install the test runner
Prerequisite: Node ≥ 18 and npm on PATH (node -v). If the repo isn't a JS project, Node still has to be installed for the e2e suite to run.
cd <repo>/e2e
npm install
npx playwright install chromium
Step 4 — Register the Playwright MCP
The recon in Step 5 (and later in retrofit Phase 2) drives a browser via the Playwright MCP — the mcp__playwright__browser_* tools. It's a Claude Code MCP server, registered once globally, not shipped in the repo.
claude mcp add playwright -s user -- npx @playwright/mcp@latest
Equivalent ~/.claude.json entry under top-level mcpServers:
"playwright": { "type": "stdio", "command": "npx", "args": ["@playwright/mcp@latest"], "env": {} }
Project-scoped alternative (shareable via a committed .mcp.json at the repo root — not inside e2e/, where Claude Code won't read it):
{ "mcpServers": { "playwright": { "type": "stdio", "command": "npx", "args": ["@playwright/mcp@latest"] } } }
Then:
- Verify —
/mcp lists playwright connected and mcp__playwright__browser_* tools are available.
- Gitignore the MCP output —
.playwright-mcp/ (snapshots/console/network dumps; can contain real app data — keep it out of the repo) and */.playwright-cli/. The bundled harness/.gitignore covers the e2e dir, but the MCP writes to the directory Claude Code runs from — usually the repo root — so add .playwright-mcp/ to the repo-root .gitignore too (not conditional).
Step 5 — Wire the harness to the real app (the actual work)
The harness ships generic widget/collection examples. Adapt the infrastructure edges to the real app — use MCP recon to discover the truth rather than guessing. Authenticate the recon browser first: it's a fresh session that does NOT inherit storageState, so browser_navigate to the base URL (dev auto-login) or drive the login form once.
Discover and wire:
helpers/urls.ts — real BASE_URL / API_BASE_URL and the id-from-route shape.
helpers/auth.ts — the real login flow + selectors (or confirm dev auto-login). Update formLogin/devAutoLogin (replace the placeholder waitForURL('**/**') with a real post-login assertion).
helpers/api.ts — the real API login/token endpoint in loginApi, and replace the widget/collection seed helpers + response predicates with one real entity wired just enough to prove the client works (login → create → get → delete), exercised by the smoke test below, not by a feature spec. This proves the seeding infrastructure is sound; building feature coverage on top of it is e2e-retrofit's job. (No API? Delete api.ts and drop its .env vars.)
tests/smoke.spec.ts — wire the reachability check (the example pings /api/health) to a real endpoint for your API, and the page-loads test to a real page. Keep smoke dead simple — it is a reachability gate, not a feature test. Three cheap assertions are the target: the app loads, one real page renders (assert a stable heading/landmark), and the API responds. Prefer a direct page.goto('/some-page') over clicking through nav — and if any interaction turns flaky (consent/cookie overlays, ad scripts, async timing on a third-party or heavy app), do not fight it: drop to the simplest assertion that proves the page renders. Resist adding waits, retries, multi-step flows, or overlay-dismissal gymnastics here; that complexity belongs in real e2e-retrofit specs, not the smoke gate. This is the success gate in Step 7.
helpers/db.ts.example — only if a test needs to read state the API doesn't expose: rename to db.ts and npm i -D pg @types/pg (or your driver). Otherwise leave it as .example (it isn't compiled) and drop the DB_* vars from .env.
README.md — the copied harness README.md documents the demo widget/collection domain. Repoint it to the real app (target URL, auth + API model, layout) or delete it — don't ship a README describing entities the project no longer contains. (Step 6 treats README.md as the home for app prose, so leaving it stale contradicts that. fixtures/README.md is generic — leave it.)
- Follow
../e2e-retrofit/patterns.md §2 (selectors) and §3–4 (assertions) while wiring, so the seed is canonical from day one.
Delete every example feature spec (tests/widgets/, tests/collections/ — the CRUD + Manage demos) and any example helper you didn't wire. Setup writes no feature tests — smoke.spec.ts is the only spec left in the project. The harness's example specs remain in the e2e-setup skill folder (harness/tests/) as the pattern reference e2e-retrofit's first batch mirrors; they are not shipped into the project.
Step 6 — Scaffold the coverage matrix (gated choice)
Step 2 copied an example COVERAGE.md (the demo widgets/collections matrix). Its demo content must be replaced, but how is the user's call — this is a hard gate, ask before touching the file:
GATE — ask the user, don't assume:
(a) Seed from observation — I recon the app's nav and enumerate the real sections + workflows as matrix rows (unchecked), or
(b) Blank template — I leave an empty matrix skeleton (headers, legend, env columns, one commented example row) for you to fill in yourself.
Then, whichever they pick:
- Every row stays
[ ] (not covered) in both modes. Setup writes no feature tests, so nothing is covered yet — marking a row [x] would claim coverage that doesn't exist. The matrix is the work-list, not a result.
- Match the env columns to the real targets. The demo ships Local + Remote columns; a single-target app (remote-only or local-only) gets one
Covered column. Don't carry a column for a target that doesn't exist — collapse the matrix (and the Coverage Summary) to the targets the suite actually runs against.
- It's a tracker, not docs. Keep the file to the matrix + legend. Don't add prose describing the app, its architecture, the environment ("static page, no API, no login"), or how a feature behaves — that belongs in the e2e
README.md or a spec comment, not the coverage file.
- Mode (a) — seeded from observation: populate
### {Section} blocks + workflow rows from recon, keeping the file's format (priority-tagged rows, env columns, legend, summary). Recon tells you what the app does, not what matters or what's correct — so treat the section taxonomy and especially the priority labels as proposed, flagged for the user to confirm/correct, not as authoritative fact. Don't manufacture a coverage-summary percentage.
- Mode (b) — blank: strip all demo rows; leave the headers, legend, env columns, and a single commented example row showing the row shape.
This file is the $COVERAGE_MATRIX — the work-list every later retrofit batch scopes from.
Step 7 — Verify
Make sure a target environment is running and reachable first — either start local services ($DEV_UP) or point .env at an already-running staging/remote env. Confirm the base URL responds before running specs.
cd <repo>/e2e
cp .env.example .env
npx playwright test smoke.spec.ts
Note: smoke.spec.ts runs in the authenticated project, so it exercises the full auth path (Step 5 wiring) before its assertions — it's a real end-to-end check, not a bare ping. A green smoke run is the whole success gate — it's the only spec setup writes. (There are no leftover example specs to ignore; Step 5 deleted them.) Always run from the e2e dir, never the repo root (../e2e-retrofit/patterns.md §11).
Step 8 — Commit and hand off
- Commit the scaffold — scope the commit to the e2e dir (plus the repo-root
.gitignore change from Step 4); no secrets (.env, .auth/, .playwright-mcp/ stay gitignored). If the repo has no initial commit yet, or unrelated staged/dirty changes, do not sweep them into the setup commit: do a scoped git add <e2e-dir> commit, or — when scoping is ambiguous — hand the diff to the user to commit rather than bundling. (Setup is the only step that commits a scaffold; branch/PR handling is e2e-retrofit's job.)
- Report what was wired (entity, auth mode, targets), the smoke result, the coverage-matrix mode chosen (seeded vs blank), and — explicitly — that no feature specs were written (that's the next step's job).
- Tell the user: setup is complete — from here use the
e2e-retrofit skill ("retrofit e2e tests for {ticket}"), which writes the actual feature coverage one section at a time. This skill should not be run again on this repo.
Done-when checklist