convoy: scope adopt-playwright-smoke (P1 #10 step 2)
Scaffolds the convoy file for the next sequential convoy in .convoys/ship-readiness.md's launch sequence (step 10). The infra prerequisites have already shipped: - bump-next-js (e57ea17) — Vercel previews actually deploy - fix-vercel-deployment-protection-in-ci (9a3e077) — wait-action healthcheck reaches 200; BASE_URL and VERCEL_AUTOMATION_BYPASS_SECRET already exported to the test step's env: in both workflows What remains is small and well-bounded: add @playwright/test to devDependencies, scaffold playwright.config.js with two projects (smoke + visual), decide what to do with the existing tests/smoke/app.smoke.spec.ts (which is .ts in a JS-only repo — Decision A queued for architect), and put a trivial visual baseline in place so the screenshot workflow has something to do on first run. 6 decisions queued for architect ratification (TS vs JS spec, fail-loud vs warn-and-continue on missing secret, project shape, visual baseline strategy, lint coverage for tests/, local-test script ergonomics). No operator action required — all secrets and env vars are already wired. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
c2a43a0ce2
commit
c8f1541fa9
1 changed files with 276 additions and 0 deletions
276
.convoys/adopt-playwright-smoke.md
Normal file
276
.convoys/adopt-playwright-smoke.md
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
---
|
||||
name: adopt-playwright-smoke
|
||||
classification: convoy
|
||||
success_metric: |
|
||||
`Playwright smoke` on every PR reaches `npx playwright test` and either
|
||||
passes (smoke green) or fails on real test assertions. The current
|
||||
fast-fail at the test step ("playwright not installed" / "no config")
|
||||
goes away. `Screenshot diff` workflow either passes (snapshots stable)
|
||||
or fails on a real pixel diff with the standard upload + PR comment
|
||||
artifacts. Both checks complete in < 5 minutes.
|
||||
skip:
|
||||
- role-design-system-auditor # spec authoring, not visual design
|
||||
- role-a11y-auditor # tested-from-outside; a11y belongs in deeper specs
|
||||
- role-ux-reviewer # no UX surface
|
||||
- role-ia-architect # no IA surface
|
||||
status: queued
|
||||
created: 2026-05-24
|
||||
parent: ship-readiness
|
||||
addresses: P1 #10 step 2 (launch sequence step 10)
|
||||
depends_on:
|
||||
- fix-vercel-deployment-protection-in-ci (shipped — PR #17, 9a3e077)
|
||||
- bump-next-js (shipped — e57ea17; needed for working Vercel previews)
|
||||
---
|
||||
|
||||
# Convoy: adopt-playwright-smoke
|
||||
|
||||
Stand up Playwright end-to-end. The infrastructure scaffolding has already
|
||||
landed in three earlier convoys; this one wires the actual `@playwright/test`
|
||||
dependency, the `playwright.config.js` that points it at the Vercel preview
|
||||
URL, and turns the existing `tests/smoke/app.smoke.spec.ts` from "drafted
|
||||
but inert" into "executed by CI on every PR".
|
||||
|
||||
## Why now
|
||||
|
||||
PR #17 (`fix-vercel-deployment-protection-in-ci`, squash `9a3e077`) closed
|
||||
the last infra gap blocking Playwright smoke. The wait-action now reaches a
|
||||
2xx in ~200ms, both `BASE_URL` and `VERCEL_AUTOMATION_BYPASS_SECRET` are
|
||||
already exported to the smoke / screenshot-capture step's `env:`, and
|
||||
`tests/smoke/app.smoke.spec.ts` is already drafted (3 tests: home renders
|
||||
without 5xx, sign-in page renders, `/api/health` responds 2xx — and
|
||||
`pages/api/health.js` already exists, so the third test won't 404).
|
||||
|
||||
What's missing is small and well-bounded:
|
||||
|
||||
1. `@playwright/test` is NOT in `package.json` devDependencies (verified via
|
||||
`grep`-of-package.json).
|
||||
2. There is no `playwright.config.js` (or `.ts`) in the tree.
|
||||
3. The smoke spec is `.ts` in a JS-only repo (no `tsconfig.json`, no other
|
||||
`.ts` source files except `next-env.d.ts`). Decision to ratify: convert
|
||||
to `.js` or accept `.ts` for `tests/` only?
|
||||
4. The `Screenshot diff` workflow runs `npx playwright test --project=visual`
|
||||
which also needs a `visual` project in the config. The visual workflow's
|
||||
"Capture screenshots (PR)" step has `continue-on-error: true` and a
|
||||
PR-comment step (after PR #16's `pull-requests: write` perm fix), so
|
||||
we can ship without baselines on the first run — the comment will say
|
||||
"no baselines yet, run `--update-snapshots` locally to seed them."
|
||||
|
||||
This convoy is the next step in the launch sequence (step 10 of
|
||||
`.convoys/ship-readiness.md`'s "Proposed launch sequence"). After it ships,
|
||||
every PR gets real smoke regression signal — which materially de-risks
|
||||
every subsequent convoy (`single-auth-provider`, `single-sql-client`,
|
||||
`god-component-split`, etc.) because those will start touching live page
|
||||
flows that nothing currently exercises automatically.
|
||||
|
||||
## Scope
|
||||
|
||||
**In scope:**
|
||||
|
||||
- `package.json` — add `@playwright/test` to `devDependencies`. (Latest
|
||||
stable as of 2026-05-24; architect picks the exact version pin.)
|
||||
- `package-lock.json` — regenerated by `npm install`.
|
||||
- `package.json` `scripts` — add `test:smoke` and `test:visual` (or
|
||||
a single `test:e2e`; architect's call). Should the scripts run against
|
||||
`localhost:3000` by default and require an explicit `BASE_URL` for
|
||||
preview, or vice versa? Decision to ratify.
|
||||
- `playwright.config.js` (new) — at minimum: `testDir: './tests'`, two
|
||||
`projects:` blocks (`smoke` and `visual`) matching the workflow
|
||||
invocations (`npx playwright test --project=smoke|visual`),
|
||||
`use.baseURL` from `process.env.BASE_URL`, `use.extraHTTPHeaders`
|
||||
forwarding `x-vercel-protection-bypass` from
|
||||
`process.env.VERCEL_AUTOMATION_BYPASS_SECRET` (per AGENTS.md § 7),
|
||||
and a reasonable `timeout` / `expect.timeout`.
|
||||
- `tests/smoke/app.smoke.spec.ts` (existing) — keep as `.ts` OR rename
|
||||
to `.js`, depending on Decision A. If kept as `.ts`, may need to
|
||||
exclude `tests/**/*.ts` from ESLint (the JS-only repo's lint config
|
||||
doesn't currently handle `.ts` and will likely error).
|
||||
- `tests/visual/` (new directory + at least one trivial spec) — needs
|
||||
a single `.spec.ts` (or `.js`) that takes a screenshot of the
|
||||
homepage. Without a spec, `npx playwright test --project=visual`
|
||||
exits 0 and the screenshot workflow has nothing to compare.
|
||||
- `eslint.config.mjs` (possibly) — if Decision A keeps `.ts` specs,
|
||||
add `tests/**/*.ts` to `globalIgnores` OR wire typescript-eslint
|
||||
to parse them safely.
|
||||
- `.gitignore` (possibly) — `test-results/`, `playwright-report/`,
|
||||
`.playwright/` should be ignored (Playwright generates these on
|
||||
every local run).
|
||||
- `AGENTS.md` — section on running smoke tests locally (`npm run
|
||||
test:smoke`) + the "expect baselines to drift on UI changes; run
|
||||
`--update-snapshots`" guidance. The doc-writer pass at convoy
|
||||
close handles this; the brief should NOT touch `AGENTS.md`.
|
||||
|
||||
**Out of scope:**
|
||||
|
||||
- **Writing deep E2E tests beyond the 3 existing smoke checks.** This
|
||||
convoy makes smoke green; deeper coverage is per-feature work in
|
||||
feature convoys (`add-rate-limiting` adds a rate-limit smoke check,
|
||||
etc.).
|
||||
- **Authoring real visual baselines.** First-run snapshots can be
|
||||
trivial (homepage only). Real baseline curation across critical
|
||||
pages is a separate convoy (`adopt-visual-baselines`?) once UX has
|
||||
stabilized post-`pick-a-name`.
|
||||
- **Re-enabling the `test:` job in `.github/workflows/ci.yml`.** Per
|
||||
ship-readiness P1 #10 step 3, that re-enable is a separate task —
|
||||
this convoy's job is the Playwright side only.
|
||||
- **Replacing `wait-for-vercel-preview`.** Still queued as
|
||||
`replace-wait-for-vercel-preview` if the action ages out further.
|
||||
- **Adding `test:smoke:local` cron / pre-commit hooks.** Smoke specs
|
||||
should be runnable locally on demand; automatic cron is a separate
|
||||
scope.
|
||||
- **Migrating any source files to TypeScript.** Decision A may keep
|
||||
the spec as `.ts`, but that's a test-only file — no source code
|
||||
migrates. See AGENTS.md Gotcha #9.
|
||||
|
||||
## Operator action required
|
||||
|
||||
**None.** All prerequisites are already in place:
|
||||
|
||||
- `VERCEL_AUTOMATION_BYPASS_SECRET` is seeded in GitHub Actions repo
|
||||
secrets (`gh secret list` shows it; seeded 2026-05-24T20:03:31Z).
|
||||
- Both target workflows (`preview-smoke.yml`, `visual-diff.yml`)
|
||||
already export the secret to the test step's `env:`.
|
||||
- `BASE_URL` is already wired.
|
||||
- `pages/api/health.js` already exists for the existing smoke spec.
|
||||
|
||||
## Decisions to ratify with operator
|
||||
|
||||
Queued; do not pre-decide. Architect picks recommended option per
|
||||
decision and routes back at gate 1.
|
||||
|
||||
1. **`.ts` vs `.js` for Playwright specs.** The existing
|
||||
`tests/smoke/app.smoke.spec.ts` uses TypeScript-flavored imports
|
||||
(`import { test, expect } from '@playwright/test'`). The codebase
|
||||
is JS-only (no `tsconfig.json`, no other `.ts` source files except
|
||||
`next-env.d.ts`). Three options:
|
||||
- **(a)** Convert the spec to `.js` — matches codebase convention,
|
||||
no eslint config change needed (the import syntax works fine in
|
||||
ESM `.js`).
|
||||
- **(b)** Keep `.ts` for `tests/` only — Playwright docs default
|
||||
to `.ts`; tests are isolated from production code; need to add
|
||||
`tests/**/*.ts` to `eslint.config.mjs`'s `globalIgnores` or
|
||||
properly configure typescript-eslint for the tests directory.
|
||||
- **(c)** Mixed — `.js` for smoke, `.ts` for new specs going
|
||||
forward. Inconsistent; not recommended.
|
||||
2. **Fail-loud vs warn-and-continue when
|
||||
`VERCEL_AUTOMATION_BYPASS_SECRET` is unset.** Same pattern as
|
||||
`lib/rate-limit.js` (per AGENTS.md Gotcha #12): in CI (where
|
||||
`process.env.CI === 'true'`), fail loudly — throw at config load
|
||||
time with a clear error pointing at `gh secret set ...`. In dev
|
||||
(where the secret might be missing but you're hitting localhost),
|
||||
warn-and-continue. Architect picks the exact predicate and
|
||||
error-message wording.
|
||||
3. **One project (`smoke`) or two (`smoke` + `visual`)?** Both
|
||||
workflows already invoke `--project=smoke` and `--project=visual`
|
||||
respectively (post PR #17). Splitting into two projects in the
|
||||
config is required. The question is whether `visual` should reuse
|
||||
the same specs as `smoke` (with screenshot assertions added) OR
|
||||
live in its own `tests/visual/` directory with separate specs.
|
||||
The convoy's success metric only requires the workflows to run to
|
||||
completion — the actual visual-coverage scope is a follow-up.
|
||||
4. **First-run visual baselines.** With no committed baseline images,
|
||||
`npx playwright test --project=visual` will either (a) fail
|
||||
(no baselines to diff against) — which the workflow's
|
||||
`continue-on-error: true` swallows, then the upload + comment step
|
||||
surfaces the missing-baseline state — OR (b) Playwright treats
|
||||
no-baseline as "create on first run" (depends on config).
|
||||
Decision: do we commit a trivial homepage baseline now, OR
|
||||
document the "run `--update-snapshots` locally first" workflow,
|
||||
OR auto-commit baselines via a separate PR?
|
||||
5. **ESLint coverage for `tests/`.** Currently
|
||||
`eslint.config.mjs` does NOT explicitly ignore `tests/`. If
|
||||
Decision A keeps `.ts`, eslint will try to parse it. The two
|
||||
choices: add `tests/**/*.ts` (or just `tests/**`) to
|
||||
`globalIgnores`, OR wire typescript-eslint into the test
|
||||
directory. Latter is more work for arguable test-side benefit;
|
||||
recommend the former.
|
||||
6. **Should we add a `test:smoke:local` script that boots
|
||||
`next dev` and runs against `localhost:3000` automatically?** Or
|
||||
leave it as "you boot dev manually, then `BASE_URL=http://localhost:3000
|
||||
npm run test:smoke`"? The latter is simpler; the former is
|
||||
friendlier. Convention-match with the existing `test:run` script
|
||||
shape.
|
||||
|
||||
## Known constraints
|
||||
|
||||
- **`tests/smoke/app.smoke.spec.ts` already exists** with 3 tests
|
||||
using `@playwright/test`'s API. Don't rewrite it; just enable it.
|
||||
The third test references `/api/health`, which exists at
|
||||
`pages/api/health.js` — confirmed.
|
||||
- **Both target workflows already export
|
||||
`VERCEL_AUTOMATION_BYPASS_SECRET` and `BASE_URL` to the test
|
||||
step's `env:`** — `playwright.config.js`'s job is to read them
|
||||
and apply them via `use.baseURL` + `use.extraHTTPHeaders`.
|
||||
- **The header form** of the Vercel bypass
|
||||
(`x-vercel-protection-bypass: <secret>`) is the correct shape
|
||||
for a browser cookie-jar context. Per AGENTS.md § 7 and PR #17's
|
||||
Decision A reservation, this is what `playwright.config.js`
|
||||
should use — NOT the query-param form (that's reserved for
|
||||
curl/axios contexts without cookie jars).
|
||||
- **Playwright versions** ship browsers as a separate install step
|
||||
(`npx playwright install --with-deps chromium` — already in
|
||||
both workflows). Picking a Playwright version pin should consider
|
||||
the workflow's expectation that the binary exists.
|
||||
- **Vercel preview URLs are auth-protected.** This is why the bypass
|
||||
exists. Without `use.extraHTTPHeaders` correctly wired, every
|
||||
`page.goto(BASE)` call will hit Vercel SSO and the test will fail
|
||||
with a content-mismatch (not a 401, because Vercel returns an HTML
|
||||
SSO challenge page with 401 status).
|
||||
- **Test runtime budget:** the smoke spec's comment says "<60s
|
||||
total". Three trivial smoke tests should run in well under 30s.
|
||||
The convoy's success metric is < 5 min for the whole workflow
|
||||
(which includes ~2 min of `npm ci` + browser install). Comfortable.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
The convoy is shippable when ALL of the following hold:
|
||||
|
||||
1. `Playwright smoke` workflow on a fresh PR reaches `npx playwright
|
||||
test --project=smoke`, browsers are installed, and the 3 existing
|
||||
smoke tests run to completion. Pass OR fail; just not "no config".
|
||||
2. `Screenshot diff` workflow on a fresh PR touching `pages/**` or
|
||||
`components/**` reaches its visual capture step and either passes
|
||||
OR posts a meaningful "Visual Diff" comment to the PR (per the
|
||||
existing `continue-on-error: true` + comment step pattern).
|
||||
3. Both workflows complete in < 5 minutes total.
|
||||
4. `npm run test:smoke` (or whatever Decision 6 chooses) works
|
||||
locally against either localhost or a deployed preview URL,
|
||||
given a `BASE_URL` env var.
|
||||
5. `npm run lint` exit code matches baseline (still 128 problems
|
||||
per the `fix-lint-baseline` convoy; do NOT regress).
|
||||
6. `npm run test:run` (vitest) still passes 21/21 (no regression
|
||||
from the existing test surface).
|
||||
7. Bypass secret does NOT appear in any workflow run log. Verify
|
||||
by downloading the raw log of a passing run and grepping for
|
||||
the secret's first 8 chars.
|
||||
8. `playwright.config.js` is well-commented (every non-obvious
|
||||
choice has a one-line explanation), so the next agent doesn't
|
||||
need to re-derive context from the convoy file.
|
||||
|
||||
## Anything flagged but not acted on (in advance)
|
||||
|
||||
- **`tests/smoke/app.smoke.spec.ts` is `.ts` in a JS-only repo.**
|
||||
This is a real anomaly. Decision A resolves it one way or another.
|
||||
If we keep `.ts`, document the special-case treatment in
|
||||
AGENTS.md so future agents don't try to "normalize" by converting
|
||||
to `.js` (or vice versa).
|
||||
- **Visual baseline curation strategy.** First-run baselines will
|
||||
inevitably need re-capturing as the UI evolves toward the
|
||||
`pick-a-name` rebrand. Don't over-invest in baselines this
|
||||
convoy; document the `--update-snapshots` workflow and move on.
|
||||
- **CI workflow `paths:` filter for `visual-diff.yml`.** Currently
|
||||
the filter is `pages/**`, `components/**`, `styles/**`,
|
||||
`tailwind.config.js`, `postcss.config.js`. After this convoy
|
||||
ships, the filter is still correct — visual tests should
|
||||
re-run when any of those change. No change needed in this
|
||||
convoy.
|
||||
- **`@playwright/test` security advisories.** Pin a recent version
|
||||
and document the rationale (avoid security CVEs, avoid known
|
||||
buggy versions). Architect picks; no operator ratification
|
||||
needed unless a specific advisory is relevant.
|
||||
- **PR-comment template for `Screenshot diff`.** The existing
|
||||
comment-on-PR step in `visual-diff.yml` hardcodes the comment
|
||||
body ("Screenshots and diffs uploaded as artifacts: [view
|
||||
run](...)"). After this convoy, the body should arguably
|
||||
include a quick diff summary (pixel count, % difference per
|
||||
page). That's an enhancement for a separate `polish-visual-diff-comment`
|
||||
convoy; not in scope here.
|
||||
Loading…
Reference in a new issue