T6 · Playwright end-to-end skeleton¶
Done 2026-10-28 · narrowed to the smallest skeleton by the MVP decision of 2026-10-28 · tests/e2e/: Playwright 1.63.0 in TypeScript, projects unit, desktop and phone, 11 tests green locally in 1.8 s and in CI (40699486247) · side-by-side report with the boards deferred · review: approve after 2 rounds

What changed¶
The behaviour is in dev-environment §6.1. This list names the files.
tests/e2e/, a Node package of its own:package.jsonandpackage-lock.json:@playwright/test1.63.0, thePLAYWRIGHT_VERSIONofdev/versions.env(§1). The lockfile holds three packages (@playwright/test,playwright,playwright-core) with their integrity hashes.playwright.config.ts: the base URL frombase-url.ts; three projects,unit(*.unit.ts, no browser),desktop(Desktop Chrome) andphone(Pixel 7, both on*.e2e.ts); the stand-in page served bypython3 -m http.serverwhen no other target is set; traces kept on failure for the stand-in only.base-url.ts: picks the target, first match wins. (1)SPECTIO_E2E_BASE_URL,httporhttpsonly, refused when it has a user, a password or a query, and the error does not repeat the URL. (2)urls.spectiofrom the scenario JSON thatSPECTIO_SCENARIOnames; a JSON without that key is refused with the reason. (3) The stand-in athttp://127.0.0.1:8710.boards.ts:screens/<Board>--<project>.png, where<Board>must match[A-Za-z0-9]+and a filedesign/ui-canvas/<Board>.dc.htmlmust exist.smoke.e2e.ts: one test. The home page returns a 2xx status, its title contains "Spectio", itsmainlandmark is visible, and the page raises no script error. Against the stand-in only, it saves theRunsTablescreenshot.- Unit tests:
base-url.unit.ts(6),boards.unit.ts(2),pin.unit.ts(1:@playwright/testinpackage.jsonequalsPLAYWRIGHT_VERSION). static/index.html, the stand-in page ("Runs · Spectio", onemainregion), and.gitignore(node_modules/,playwright-report/,test-results/,screens/).
.github/workflows/e2e.yml, one job onubuntu-24.04, 15-minute timeout. It runs on a push to any branch that touchestests/e2e/,dev/versions.env,design/ui-canvas/or the workflow itself, and on manual dispatch. Steps: Node fromNODE_VERSIONofdev/versions.envthroughactions/setup-node, thennpm ci --ignore-scripts, thennpx --no-install playwright install --with-deps chromiumandnpx --no-install playwright test. The HTML report andscreens/go to thee2e-reportartifact, kept 14 days. Hardening as inscenarios.yml:permissions: {}at the top,contents: readfor the job, actions pinned by SHA,persist-credentials: false.ci.ymldoes not call it.dev/versions.env: a comment line saying the Playwright pin is also intests/e2e/package.json, checked bytests/e2e/pin.unit.ts. No value changed.- Spec, dev-environment-and-testing.md:
- Review record,
docs/reviews/code/T6.md.
Deferred. These need product code, or wait for the full UI:
- The side-by-side HTML report of the screenshots next to the
design/ui-canvasrenders (tools/canvas/render.js). The MVP UI is ultra simple, and the boards were drawn for the full product, so the pairs would not match. - A destructive-specs project, the tier 4 journeys, and running against the bundle or Compose.
Why¶
Prompt: next-phase.md · T6. It asks for:
tests/e2ewith Playwright in TypeScript;- a config that takes the base URL from the scenario JSON;
- desktop and phone projects;
- screenshots named after the canvas boards;
- a side-by-side HTML report against
design/ui-canvasrenders; - runs against the Cloudflare docs preview and a static page until the product exists.
Thomas's MVP decision of 2026-10-28 narrowed the step to the smallest skeleton. The MVP UI is ultra simple: launch and monitor only, with no Kubernetes, no gates and no visualisation. So the step builds every item above except the side-by-side report (see Deferred). The decision log has no entry for that decision yet (finding 5). The closest entry is 2026-10-27 — 0.0.1 revised. The step decided behaviour that §6.1 records.
How to test¶
In a cloud session, Playwright 1.63.0 uses the Chromium pre-installed under /opt/pw-browsers (chromium-1243). Elsewhere, run npx --no-install playwright install chromium first.
cd tests/e2e && npm ci && npx --no-install playwright test
Against the Cloudflare docs preview (§7.1), browser projects only:
SPECTIO_E2E_BASE_URL=https://spectio.pages.dev/ npx --no-install playwright test --project desktop --project phone
Evidence¶
Local, cloud session, on autopilot/T6 at 5b6947f:
$ cd tests/e2e && npm ci && npx --no-install playwright test
✓ 9 [unit] › base-url.unit.ts:37:5 › SPECTIO_SCENARIO without urls.spectio says why (2ms)
✓ 10 [desktop] › smoke.e2e.ts:5:5 › the home page answers, shows its main region, no script errors (257ms)
✓ 11 [phone] › smoke.e2e.ts:5:5 › the home page answers, shows its main region, no script errors (305ms)
11 passed (1.8s)
$ ls screens
RunsTable--desktop.png
RunsTable--phone.png
That is 9 unit tests and the smoke test on desktop and phone. The worker's own run gave 11 passed (1.7s).
Against the docs preview:
$ SPECTIO_E2E_BASE_URL=https://spectio.pages.dev/ npx playwright test --project desktop --project phone
✓ 1 [phone] › smoke.e2e.ts:5:5 › the home page answers, shows its main region, no script errors (2.3s)
✓ 2 [desktop] › smoke.e2e.ts:5:5 › the home page answers, shows its main region, no script errors (17.4s)
2 passed (18.1s)
The worker's run gave 2 passed (7.0s). Before a fix, the test asserted a visible h1, and that failed on the preview: mkdocs-material hides the home page's h1. The smoke test now asserts the main landmark.
On PR #45's own preview (https://pr-45.spectio.pages.dev/), 3 of 6 runs first failed with page.goto: Test timeout of 30000ms exceeded: the load event waited on same-origin assets (stylesheets/extra.css, a .webp screenshot, gate-review.mp4) that stalled through this cloud session's proxy. The smoke test now waits for domcontentloaded (smoke.e2e.ts); after that change, 8 of 8 runs passed on desktop and phone, and the stand-in still gives 11 passed.
Test first:
base-url.unit.tswas written before the config. Its first run gave "No tests found".pin.unit.tsfailed with@playwright/testset to 1.62.0, then passed at 1.63.0.
CI, e2e.yml:
| Run | Head | Result | Tests | Node |
|---|---|---|---|---|
| 40698975901 | b69fdd2 |
green | 10 | v22.23.3, from the runner image |
| 40699486247 | af7bcf0 |
green | 11 | v24.22.1, from the dev/versions.env pin |
The 11th test is the credential test, added in review round 1 (SEC-T6-01).
Security tooling, run on the step's files:
osv-scanner(tools/ci/security.sh deps) scannedtests/e2e/package-lock.json: 3 packages, no issues.actionlintandzizmor1.31.1 (--offline --min-severity low): clean one2e.yml.semgrep: the repository-wide SAST check does not scantests/(finding 1).tests/e2e/base-url.ts, scanned on its own underp/javascriptandp/typescript, has 0 findings (record, SEC-T6-05).
Review (record):
- Round 1, on
b69fdd2:code-reviewerchanges (0 blockers, 1 major, 6 minors),security-reviewerapprove (0 blockers, 0 majors, 5 minors).- The major, CR-T6-01:
e2e.ymldid not run whendev/versions.envordesign/ui-canvas/changed. Both are now in its pushpaths. - The other fixes: Node from the pin, a credential in the URL refused and never echoed,
npx --no-install, the board screenshot taken against the stand-in only, test outputs intest.info().outputPath().
- The major, CR-T6-01:
- Round 2, on
af7bcf0: both reviewers approve. One minor is deferred: SEC-T6-05. code-simplifier, ona274636: no change; the 11 tests pass.
CODE step=T6 review=approve b/M/m=0/1/6 security=approve b/M/m=0/0/5 rounds=3 simplify=nochange -0/+0 tests=pass
Figures and media¶
t6-e2esketch, light and dark, at the top of this page:python3 tools/diagrams/t6_e2e.py. The PNGs were rendered with the fallback font, because the Virgil font was not available in the session, as for T5. documentation-system §3.3 item 3 expects Virgil, andsketch.pyfalls back without a flag (D6, finding 2). Re-render the PNGs wheredocs/fonts/Virgil.ttfexists. The SVGs that the gate compares do not change.- No screenshot is committed.
RunsTable--desktop.pngandRunsTable--phone.pngare taken of the stand-in page, not of a Spectio screen. Each run ofe2e.ymluploads them to itse2e-reportartifact, kept 14 days. documentation-system §3.1 asks abuildstep for screenshots of each new screen; this step adds no product screen. - No cast.
npx --no-install playwright testis a development command, not a new Spectio CLI flow. Evidence quotes its output.
Findings and open questions¶
tests/is not scanned by the SAST check (SEC-T6-05, deferred). With no root.semgrepignore, semgrep's built-in ignore list seems to skiptests/. The cause is [to be validated] (§11, "Not scanned"). The proposed fix is a root.semgrepignore, which is code-owned. Filed as inbox #63.urls.spectiois a contract that nothing writes yet.base-url.tsreads it, and §5 names it, butdev/scenario.pydoes not write it, because no scenario runs Spectio. Until then,SPECTIO_SCENARIOalways fails with "this scenario does not run Spectio yet".- Two downloads in CI that we do not hash.
actions/setup-nodedownloads Node without checkingNODE_SHA256ofdev/versions.env. Whether it checks a checksum of its own is [to be validated].playwright installdownloads Chromium from Playwright's CDN. Both are recorded under "Not pinned" in §11. e2e.ymlis standalone, outsideci-ok, likescenarios.yml. A red run does not block a merge.- The MVP decision of 2026-10-28 is now in the decision log (recorded on the integration branch while T6 ran); §6.1 links it.
- Media: no committed screenshot or cast (see Figures and media). The first screenshots of real pages come with the first UI milestone, along with the side-by-side report.