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>
14 KiB
| name | classification | success_metric | skip | status | created | parent | addresses | depends_on | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| adopt-playwright-smoke | convoy | `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. |
|
queued | 2026-05-24 | ship-readiness | P1 |
|
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:
@playwright/testis NOT inpackage.jsondevDependencies (verified viagrep-of-package.json).- There is no
playwright.config.js(or.ts) in the tree. - The smoke spec is
.tsin a JS-only repo (notsconfig.json, no other.tssource files exceptnext-env.d.ts). Decision to ratify: convert to.jsor accept.tsfortests/only? - The
Screenshot diffworkflow runsnpx playwright test --project=visualwhich also needs avisualproject in the config. The visual workflow's "Capture screenshots (PR)" step hascontinue-on-error: trueand a PR-comment step (after PR #16'spull-requests: writeperm fix), so we can ship without baselines on the first run — the comment will say "no baselines yet, run--update-snapshotslocally 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/testtodevDependencies. (Latest stable as of 2026-05-24; architect picks the exact version pin.)package-lock.json— regenerated bynpm install.package.jsonscripts— addtest:smokeandtest:visual(or a singletest:e2e; architect's call). Should the scripts run againstlocalhost:3000by default and require an explicitBASE_URLfor preview, or vice versa? Decision to ratify.playwright.config.js(new) — at minimum:testDir: './tests', twoprojects:blocks (smokeandvisual) matching the workflow invocations (npx playwright test --project=smoke|visual),use.baseURLfromprocess.env.BASE_URL,use.extraHTTPHeadersforwardingx-vercel-protection-bypassfromprocess.env.VERCEL_AUTOMATION_BYPASS_SECRET(per AGENTS.md § 7), and a reasonabletimeout/expect.timeout.tests/smoke/app.smoke.spec.ts(existing) — keep as.tsOR rename to.js, depending on Decision A. If kept as.ts, may need to excludetests/**/*.tsfrom ESLint (the JS-only repo's lint config doesn't currently handle.tsand 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=visualexits 0 and the screenshot workflow has nothing to compare.eslint.config.mjs(possibly) — if Decision A keeps.tsspecs, addtests/**/*.tstoglobalIgnoresOR 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 touchAGENTS.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-limitingadds 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 asreplace-wait-for-vercel-previewif the action ages out further. - Adding
test:smoke:localcron / 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_SECRETis seeded in GitHub Actions repo secrets (gh secret listshows it; seeded 2026-05-24T20:03:31Z).- Both target workflows (
preview-smoke.yml,visual-diff.yml) already export the secret to the test step'senv:. BASE_URLis already wired.pages/api/health.jsalready 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.
.tsvs.jsfor Playwright specs. The existingtests/smoke/app.smoke.spec.tsuses TypeScript-flavored imports (import { test, expect } from '@playwright/test'). The codebase is JS-only (notsconfig.json, no other.tssource files exceptnext-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
.tsfortests/only — Playwright docs default to.ts; tests are isolated from production code; need to addtests/**/*.tstoeslint.config.mjs'sglobalIgnoresor properly configure typescript-eslint for the tests directory. - (c) Mixed —
.jsfor smoke,.tsfor new specs going forward. Inconsistent; not recommended.
- (a) Convert the spec to
- Fail-loud vs warn-and-continue when
VERCEL_AUTOMATION_BYPASS_SECRETis unset. Same pattern aslib/rate-limit.js(per AGENTS.md Gotcha #12): in CI (whereprocess.env.CI === 'true'), fail loudly — throw at config load time with a clear error pointing atgh 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. - One project (
smoke) or two (smoke+visual)? Both workflows already invoke--project=smokeand--project=visualrespectively (post PR #17). Splitting into two projects in the config is required. The question is whethervisualshould reuse the same specs assmoke(with screenshot assertions added) OR live in its owntests/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. - First-run visual baselines. With no committed baseline images,
npx playwright test --project=visualwill either (a) fail (no baselines to diff against) — which the workflow'scontinue-on-error: trueswallows, 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-snapshotslocally first" workflow, OR auto-commit baselines via a separate PR? - ESLint coverage for
tests/. Currentlyeslint.config.mjsdoes NOT explicitly ignoretests/. If Decision A keeps.ts, eslint will try to parse it. The two choices: addtests/**/*.ts(or justtests/**) toglobalIgnores, OR wire typescript-eslint into the test directory. Latter is more work for arguable test-side benefit; recommend the former. - Should we add a
test:smoke:localscript that bootsnext devand runs againstlocalhost:3000automatically? Or leave it as "you boot dev manually, thenBASE_URL=http://localhost:3000 npm run test:smoke"? The latter is simpler; the former is friendlier. Convention-match with the existingtest:runscript shape.
Known constraints
tests/smoke/app.smoke.spec.tsalready exists with 3 tests using@playwright/test's API. Don't rewrite it; just enable it. The third test references/api/health, which exists atpages/api/health.js— confirmed.- Both target workflows already export
VERCEL_AUTOMATION_BYPASS_SECRETandBASE_URLto the test step'senv:—playwright.config.js's job is to read them and apply them viause.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 whatplaywright.config.jsshould 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.extraHTTPHeaderscorrectly wired, everypage.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:
Playwright smokeworkflow on a fresh PR reachesnpx playwright test --project=smoke, browsers are installed, and the 3 existing smoke tests run to completion. Pass OR fail; just not "no config".Screenshot diffworkflow on a fresh PR touchingpages/**orcomponents/**reaches its visual capture step and either passes OR posts a meaningful "Visual Diff" comment to the PR (per the existingcontinue-on-error: true+ comment step pattern).- Both workflows complete in < 5 minutes total.
npm run test:smoke(or whatever Decision 6 chooses) works locally against either localhost or a deployed preview URL, given aBASE_URLenv var.npm run lintexit code matches baseline (still 128 problems per thefix-lint-baselineconvoy; do NOT regress).npm run test:run(vitest) still passes 21/21 (no regression from the existing test surface).- 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.
playwright.config.jsis 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.tsis.tsin 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-namerebrand. Don't over-invest in baselines this convoy; document the--update-snapshotsworkflow and move on. - CI workflow
paths:filter forvisual-diff.yml. Currently the filter ispages/**,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/testsecurity 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 invisual-diff.ymlhardcodes 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 separatepolish-visual-diff-commentconvoy; not in scope here.