test(#599): fix resize-then-measure race in e2e specs - #602
Conversation
… tile-open-workbench specs Two e2e specs raced an async settle with no wait, both surfaced by #587's "surface out-of-scope findings" rule and reproduced again on CI for #601. - inspector-dock-layout.spec.js:155 read `.inspector-host`'s boundingBox() immediately after `page.setViewportSize` — but the displayed width is recomputed by app-shell.ts's `reclampInspectorWidth`, a real `window` 'resize' event LISTENER dispatched asynchronously relative to `setViewportSize`'s own resolution. Under `--repeat-each=15 --workers=12` this reproduced the exact CI signature ("Expected 320, Received 500"); wrapping the read in `expect.poll` closes it (confirmed: 0 failures across the same stress level, run twice, after the fix). - tile-open-workbench.spec.js:364 read the committed `window.__dashboard()` state immediately after `widen.click()` — but the widen press applies OPTIMISTICALLY first (`runCommand`, src/ui/dashboard.ts) while the actual persisted commit is a separate, fire-and-forget `app.mutateWorkspace` call. The geometry assertions right above it are safe (same optimistic doc, no gap); only the persisted-state read raced. Same `expect.poll` fix, mirroring the pattern the file's own later "narrow tile" test already uses for the identical read. Audited both files in full for the same shape (state/viewport change immediately followed by a bare geometry or persisted-state read); every other instance is either already polled or gated behind a prior polling assertion whose pass already implies the read is safe, so no other site needed a change. No production code touched — the optimistic-apply-then-commit behavior powering both races is working as designed. Verified: full gate green (types/arch/schemas/examples/unit/build); both specs run 8x in isolation with 0 failures; the fixed assertions stress-tested at `--repeat-each=15 --workers=12` (chromium+webkit) with 0 failures across multiple rounds; full parallel suite (`--project=chromium --project=webkit`) run 3x, 414 passed/4 skipped every time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R32bb4VZGPgNo3tB9iVSKF
ChatGPT review pass 1Reviewed head: Verdict: no actionable findings. I reviewed the complete two-file PR diff, both full spec files, issue #599, the relevant
The head is five base commits behind current Execution note: direct clone and focused local Playwright execution were not possible in this review runtime because its execution container could not resolve |
What & why
Closes #599. Fixes a resize-then-measure race that made two e2e specs intermittently
flaky under parallel runs — surfaced originally while shipping #587 (phase 2 of #593),
and picked up now because the exact same failure signature hit CI twice in a row on an
unrelated PR (#601, phase 3 of #593) before passing on a third rerun.
Both sites read UI state (a bounding box, a persisted-then-republished data field)
immediately after triggering an async operation (
page.setViewportSize, a click thatapplies optimistically then commits separately), with no wait for the operation's real
completion signal:
tests/e2e/inspector-dock-layout.spec.js:147—setViewportSizeresolves assoon as the viewport is applied, but the displayed width is recomputed by
app-shell.ts'sreclampInspectorWidth, run from a realwindow'resize'eventlistener dispatched asynchronously relative to that resolution. Under parallel
load, the assertion sometimes read the pre-resize width.
tests/e2e/tile-open-workbench.spec.js:365— the widen press appliesoptimistically first (
runCommand,src/ui/dashboard.ts— its own comment saysso), while the actual persisted commit is a separate, fire-and-forget
app.mutateWorkspace(...).then(...)call. The test read the persisted value beforethat commit had necessarily landed.
Both are fixed with
expect.poll(...)instead of a bare read on the next microtask —same expected values, same assertions, just polled until they settle. The second fix
matches the pattern the same file's own later "narrow tile" test already uses for the
identical kind of read (its own "Read defensively" comment).
No production code changed — this is test-infrastructure only. The full audit
#599asked for (checking both entire spec files for any other resize/state-changeimmediately followed by a bare geometry/data read) found no further instance: every
other candidate site is either already polled or is a synchronous local-state read
gated behind a prior assertion that already guarantees the read is safe.
Verification
--repeat-each=15 --workers=12(chromium+webkit) oninspector-dock-layout.spec.jsreproducedExpected: 320, Received: 500— the exact signature seen on CI.--repeat-each=15 --workers=12, 240runs) passed twice in a row: 240/240 both times.
inspector-dock-layout.spec.js: 3 rounds × 128 = 384/384passed (workers 8 and 12).
--repeat-each=8 --workers=12.--project=chromium --project=webkit), run 3×: 414 passed / 4skipped, 0 failed, every time — matching test(e2e): resize-then-measure race makes inspector-dock-layout and tile-open-workbench flaky under parallel runs #599's originally reported clean baseline.
check:types,check:arch,check:schemas,check:examples,npm test,npm run build) — green — plus 3 spotruns of both fixed spec files together — 29/29 passed each time.
Checklist
npm testpasses (the per-file coverage gate is non-negotiable)npm run buildsucceeds (single-filedist/sql.html)CHANGELOG.md— no behavior change, so no entry needed (test-reliabilityfix only, not a feature or user-visible change)
🤖 Generated with Claude Code
https://claude.ai/code/session_01R32bb4VZGPgNo3tB9iVSKF