Skip to content

Shard the browser suite across four runners - #225

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

Shard the browser suite across four runners#225
wormeyman merged 4 commits into
wormeyman-space-age-supportfrom
ci/shard-playwright

Conversation

@wormeyman

Copy link
Copy Markdown
Collaborator

Follow-up to #223 and #224. The deploy now waits on the browser suite, so the suite's wall clock is the deploy's wall clock.

Why sharding is the lever

Per-step timings from the first push run:

Run Playwright specs:        708s
Install Playwright browsers:  27s   (cold cache)
Checkout / Set up Vite+ / vp install / servers:  ~29s
                             ----
                             774s

708 of 774 seconds are inside the specs. The part that shards is essentially all of it, so each added runner pays roughly a minute of setup to take a quarter off the rest.

Why not just raise workers

That's the other obvious knob and it's the wrong one. playwright.config.ts pins workers: 1 because the specs share ports 8080/8081 and a single editor instance. That conflict is within a machine. Giving each shard its own runner, and so its own pair of dev servers, sidesteps it rather than solving it.

Details worth flagging

  • fail-fast: false — a red shard shouldn't cancel the other three. The reason to run the suite is to learn what broke, and three cancelled shards answer that worse than three finished ones.
  • deploy needs no change. It still says needs: [checks, e2e]; a matrix job isn't satisfied until every leg succeeds.
  • Renaming the job to Playwright n/4 pins nothing loose. Checked: the repo has no branch protection, and its single ruleset is a disabled force-push rule, so no required check is referenced by name.
  • Shards split by test count, not duration, and these specs are nowhere near uniform — the corpus-wide ones (sprite-data, entity-accessors, blueprint-round-trip) dwarf the rest. Expect uneven legs; the slowest is the wall clock.

No wall-clock claim yet

Deliberately. The last time this job's cost went into a comment it was extrapolated from a laptop and wrong by 2.6x (#224). The number goes in after this PR's own run measures it, and before merge.

Verified so far: vp check . clean; parsed YAML confirms matrix.shard [1,2,3,4], fail-fast: false, the --shard flag threaded from strategy.job-total, per-shard artifact names, and deploy needs: ['checks', 'e2e'] unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71

wormeyman and others added 4 commits August 12, 2026 16:34
The per-step timings from the first push run are what make this the lever
worth pulling: 708s of the job's 774 were inside `Run Playwright specs`, and
everything else - checkout, vp install, the cold-cache browser download, both
dev servers - came to 56s. The part that shards is essentially all of it, so
each added runner pays about a minute to take a quarter off the rest.

Sharding rather than raising `workers`, which is the other obvious knob and
the wrong one. playwright.config.ts pins `workers: 1` because the specs share
ports 8080/8081 and a single editor instance, and that conflict is within a
machine. Giving each shard its own runner, and so its own pair of dev servers,
sidesteps it rather than solving it.

`fail-fast: false`, because a red shard should not cancel the other three. The
reason to run the suite is to learn what broke, and three cancelled shards
answer that worse than three finished ones.

`deploy` still says `needs: [checks, e2e]` and needs no change: a matrix job
is not satisfied until every leg succeeds.

Renaming the job to "Playwright n/4" pins nothing loose - checked, the repo
has no branch protection and its single ruleset is a disabled force-push rule,
so no required check is referenced by name.

No wall-clock figure is claimed here yet. The last time this job's cost went
into a comment it was extrapolated from a laptop and wrong by 2.6x (#224);
this one gets written down after the run measures it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71
The four legs on this job's own first run: 201/187/192/213s wall clock, of
which 160/144/144/153 were specs. So 774s becomes 213 - the slowest leg is
the wall clock, not the mean - a 3.6x speedup for 561s saved, and the deploy
behind it waits three and a half minutes rather than thirteen.

The prediction in the previous commit was wrong in a way worth keeping. It
said to expect lopsided shards, reasoning that Playwright splits by test count
while the corpus-wide specs dwarf the rest. Measured, the legs land within 11%
of each other. The reasoning was sound and the answer was not, which is the
usual argument for measuring.

Also fixes CLAUDE.md's deploy-cost sentence, which #224 missed while
correcting six others. It has now carried a wrong number twice - "five
minutes" guessed from a laptop, then thirteen once #224 corrected everything
around it - so it now says to believe ci.yml and re-measure if the two ever
disagree. A sentence that has been wrong twice is worth annotating rather than
just correcting.

Swept the whole tree for stale minute-claims afterwards rather than trusting
one grep pattern, which is how the miss happened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71
The second run measured 214/168/220/225s where the first measured
201/187/192/213. Both are the same suite on the same code, so the slowest leg
is 213-225s rather than 213, and quoting one sample would have read as
precision the number does not have.

Nothing about the change moves: still roughly 3.5x, still under four minutes
for the deploy to wait where it used to be thirteen.

This is the third pass over these same sentences today, after guessing five
and measuring thirteen. Two samples is what it took to stop writing a figure
that the next run contradicts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71
The third run's slowest leg was 250s, outside the 213-225 range the previous
commit had just recorded from two samples. Same suite, same code, third
answer.

So the figure is not 213, and it is not 213-225 either. It is roughly three to
four minutes on a shared runner, and the spread is now written down alongside
all three samples with an instruction not to tighten it back into one number.
A slow run here is also not a regression without a second sample, which is
worth saying before somebody chases one.

This sentence has now been wrong four times in a day: five minutes guessed
from a laptop, thirteen measured unsharded, 213 from one sharded run, 213-225
from two. Each correction was itself quoted too precisely. The pattern is not
that the earlier numbers were careless - each was the honest reading of the
evidence in hand - it is that a single sample of a shared-runner timing reads
as a fact and is not one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Dbza77QEZnkqt1mGssY71
@wormeyman
wormeyman merged commit fa4fed1 into wormeyman-space-age-support Aug 12, 2026
12 checks passed
@wormeyman
wormeyman deleted the ci/shard-playwright branch August 12, 2026 23:55
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