Skip to content

Run the Playwright suite in CI - #223

Merged
wormeyman merged 2 commits into
wormeyman-space-age-supportfrom
ci/playwright-suite
Aug 12, 2026
Merged

Run the Playwright suite in CI#223
wormeyman merged 2 commits into
wormeyman-space-age-supportfrom
ci/playwright-suite

Conversation

@wormeyman

@wormeyman wormeyman commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Why now

The 192 browser specs ran nowhere but a maintainer's laptop. That stopped being theoretical this week: PR #222 arrived with every check green and a blueprint-round-trip.spec.ts failure in it.

Measured on that branch — 191 passed, 1 failed:

-   "serializedHash": 825830683,
+   "serializedHash": 120098912,

Every count and both position checksums held, which is the signature of a field dropped from serialized JSON with no geometry change. The field is position-relative-to-grid, and a decode of the corpus says who it affects: 325 of 367 blueprints carry snapping and all 325 are absolute-snapping: true — exactly the branch the new serialize condition excludes.

Neither vp check nor vp test can see any of it. The decode → model → serialize path needs FD loaded, which needs a browser and the sprite server.

What it costs, and what it doesn't need

No Factorio and no unpinned network. The corpus is committed under test-blueprints/ and the sprite data under packages/exporter/data/output, so this stays as offline as the rest of CI. A separate job, parallel to checks like rust is — ~5 minutes at the config's workers: 1.

Three choices worth stating

Each has a wrong version that looks right:

  • Reuses npm run localpreview rather than carrying its own copy of how to start the two servers. A second copy is a bound written down twice, and the one that loses is the one nothing tests.
  • The browser cache keys on the resolved @playwright/test version read out of node_modules, not the caret range in package.json. The resolved version decides the browser build, so keying on ^1.62.0 serves yesterday's browser to today's Playwright — which fails as a missing chrome-headless-shell and reads as a suite-wide regression rather than a cache miss.
  • The server step redirects stdout to a file instead of inheriting it. A backgrounded process holding the step's stdout pipe open can stop that step from ever completing.

Deploy is gated on it, and the sprite server is pinned

Both were flagged as open in the first commit and are closed in the second.

deploy now needs [checks, e2e]. Gating on checks alone leaves exactly the #222 failure class free to reach fbe.factorygamefan.com. Costs ~5 minutes on the deploy path.

serve becomes a declared devDependency instead of an npx --yes fetch. Unpinned was fine while localpreview only ran on a laptop; as a CI dependency it was a run-time-resolved package in a job whose previous step is vp install --frozen-lockfile. ~990 lockfile lines for a dev-only static file server, in exchange for reproducibility and Renovate tracking it.

Stale claims corrected

Eight comments across six files asserted Playwright does not run in CI (playwright.config.ts, vite.config.ts, zoomLevels.ts, zoomLevels.test.ts, zoom-ladder.spec.ts, blueprint-files.test.ts, plus two in CLAUDE.md). The reasoning under each survives — a pure unit test still answers in seconds inside checks where this job takes five minutes and two dev servers — but the flat claim does not. The split is now about cost rather than coverage.

Verification

  • vp check . clean — 230 files formatted, 0 warnings/lint/type errors
  • vp test — 180 passed
  • Full Playwright suite locally on this base — 192 passed (4.6m)
  • The Playwright job passed on this PR's own first run, every step including server startup and all 192 specs
  • npx --no-install serve --version → 14.2.6, so nothing is fetched; localpreview still brings both ports up with data.json 200 off 8081
  • actions/upload-artifact SHA verified against the live v4 tag

🤖 Generated with Claude Code

https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71

wormeyman and others added 2 commits August 12, 2026 15:12
The 192 browser specs ran nowhere but a maintainer's laptop, and the gap was
not theoretical. PR #222 arrived with every check green while dropping
`position-relative-to-grid` from every absolutely-snapped blueprint - 325 of
the corpus's 367 - and failing `blueprint-round-trip.spec.ts` on the one
blueprint that carries a grid position. Neither `vp check` nor `vp test` can
see that: the whole decode -> model -> serialize path needs `FD` loaded,
which needs a browser and the sprite server.

It needs no Factorio and no network beyond npm - the corpus is committed
under test-blueprints/ and the sprite data under
packages/exporter/data/output - so this stays as offline as the rest of CI.

Three choices worth stating, since each has a wrong version that looks right:

- It reuses `npm run localpreview` rather than carrying its own copy of how
  to start the two servers. A second copy is a bound written down twice, and
  the one that loses is the one nothing tests.
- The browser cache is keyed on the *resolved* @playwright/test version read
  out of node_modules, not on the caret range in package.json. The resolved
  version is what decides the browser build, so keying on `^1.62.0` would
  serve yesterday's browser to today's Playwright - which fails as a missing
  chrome-headless-shell and reads as a suite-wide regression, not a cache
  miss.
- The server step redirects stdout to a file instead of inheriting it. A
  backgrounded process holding the step's stdout pipe open can stop that step
  from ever completing.

`deploy` stays gated on `checks` alone. Making it wait on this too is a real
question and a different change from adding the job.

Also corrects the six comments that asserted Playwright does not run in CI.
The reasoning under each survives - a pure unit test still answers in seconds
inside `checks` where this job takes five minutes and two dev servers - but
the flat claim does not, and a stale claim here is the thing this repo treats
as a bug.

One loose end left deliberately: localpreview serves the sprite data with
`npx --yes serve` and `serve` is declared nowhere, so it is the one unpinned
fetch in CI. Declaring it costs ~990 lines of lockfile for a dev-only static
file server, which is a dependency decision rather than a CI one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71
Two loose ends the previous commit named and left, both now closed.

`deploy` waits on `e2e` as well as `checks`. The class of bug the browser
suite exists to catch is precisely the one that reaches production
otherwise: PR #222 type-checks, lints and passes every unit test while
dropping `position-relative-to-grid` from 325 of the corpus's 367
blueprints, and `checks` was green on it. Gating on `checks` alone leaves
that free to deploy. The price is about five minutes on the deploy's
critical path.

`serve` becomes a declared devDependency instead of something localpreview
fetches with `npx --yes`. Unpinned was tolerable while that command only ran
on a laptop; as a CI dependency it was a package resolved at run time in a
job whose previous step is `vp install --frozen-lockfile`, which is the one
thing that pipeline is meant not to do. It costs ~990 lines of lockfile for
a dev-only static file server and buys a reproducible sprite server plus
Renovate tracking it.

Verified: `npx --no-install serve --version` answers 14.2.6, so nothing is
fetched, and localpreview still brings both ports up - data.json 200 off
8081.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71
@wormeyman
wormeyman merged commit 20bdfb7 into wormeyman-space-age-support Aug 12, 2026
9 checks passed
@wormeyman
wormeyman deleted the ci/playwright-suite branch August 12, 2026 22:38
wormeyman added a commit that referenced this pull request Aug 17, 2026
A vitest text scan over tests/*.spec.ts for `Control+A` and its four
siblings. On macOS `Control+A` is the emacs "beginning of line" binding,
not select-all, so nothing is selected, the typed text lands beside the
old text, and the assertion reads "New textOld text". That is #197, fixed
in 402fbe3.

It reads source rather than driving a browser because the bug **passes on
Linux**, and every runner this project has is ubuntu-latest - `vp test`
and all four `e2e` shards. #223 and #225 put Playwright in CI, which does
not help here: a sharded browser suite gating every PR will go green on
this forever.

The rule is not "never write Control". A chord reaching the app through
actions.ts stays Control, where ModifierKey is Control | Shift | Alt and
no Meta binding exists; a chord reaching a focused DOM <input> wants
ControlOrMeta. A regex cannot separate them, so it flags both and the
ALLOWLIST carries the distinction in writing - one entry today.

Mutation-checked against the live instance: dropping PR #222's
blueprint-grid-position.spec.ts into tests/ fails the guard naming
line 97, and removing it goes green again.

The knowledge already existed in prose. After 402fbe3 there is a
five-line comment at display-panel-editor.spec.ts:159 explaining the
whole trap, one line above its own corrected call, and a contributor then
wrote `Control+A` in a different file. A comment at the scene of the last
occurrence does not reach the next one.

Every figure in the new file's header is measured rather than recalled,
which caught two: the display panel comment is five lines and not three,
and four specs hold `keyboard.down('Control')` for the ctrl-drag gesture
and not five. Both came from #208's own write-up.

Part 1 of #208. Deliberately not `Closes`: that issue's part 2, the
display panel editor's uncovered surface (the alt-mode checkbox, the icon
picker and the connected branch), is untouched here and is real work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mg2oe7JgL3xEzSoN5emTsd
wormeyman added a commit that referenced this pull request Aug 17, 2026
A vitest text scan over tests/*.spec.ts for `Control+A` and its four
siblings. On macOS `Control+A` is the emacs "beginning of line" binding,
not select-all, so nothing is selected, the typed text lands beside the
old text, and the assertion reads "New textOld text". That is #197, fixed
in 402fbe3.

It reads source rather than driving a browser because the bug **passes on
Linux**, and every runner this project has is ubuntu-latest - `vp test`
and all four `e2e` shards. #223 and #225 put Playwright in CI, which does
not help here: a sharded browser suite gating every PR will go green on
this forever.

The rule is not "never write Control". A chord reaching the app through
actions.ts stays Control, where ModifierKey is Control | Shift | Alt and
no Meta binding exists; a chord reaching a focused DOM <input> wants
ControlOrMeta. A regex cannot separate them, so it flags both and the
ALLOWLIST carries the distinction in writing - one entry today.

Mutation-checked against the live instance: dropping PR #222's
blueprint-grid-position.spec.ts into tests/ fails the guard naming
line 97, and removing it goes green again.

The knowledge already existed in prose. After 402fbe3 there is a
five-line comment at display-panel-editor.spec.ts:159 explaining the
whole trap, one line above its own corrected call, and a contributor then
wrote `Control+A` in a different file. A comment at the scene of the last
occurrence does not reach the next one.

Every figure in the new file's header is measured rather than recalled,
which caught two: the display panel comment is five lines and not three,
and four specs hold `keyboard.down('Control')` for the ctrl-drag gesture
and not five. Both came from #208's own write-up.

Part 1 of #208. Deliberately not `Closes`: that issue's part 2, the
display panel editor's uncovered surface (the alt-mode checkbox, the icon
picker and the connected branch), is untouched here and is real work.


Claude-Session: https://claude.ai/code/session_01Mg2oe7JgL3xEzSoN5emTsd

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant