From 573ef7a83f477fb72bdb84dadab07c83c85199a2 Mon Sep 17 00:00:00 2001 From: Boris Tyshkevich Date: Tue, 4 Aug 2026 18:06:20 +0200 Subject: [PATCH] test(#599): fix resize-then-measure race in inspector-dock-layout and tile-open-workbench specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01R32bb4VZGPgNo3tB9iVSKF --- tests/e2e/inspector-dock-layout.spec.js | 15 +++++++++++---- tests/e2e/tile-open-workbench.spec.js | 10 +++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/e2e/inspector-dock-layout.spec.js b/tests/e2e/inspector-dock-layout.spec.js index 6bd3702a..f175c729 100644 --- a/tests/e2e/inspector-dock-layout.spec.js +++ b/tests/e2e/inspector-dock-layout.spec.js @@ -153,11 +153,18 @@ test.describe('docked right-inspector dock-aware width (#586 findings 2a/2b)', ( expect(Math.round(before.width)).toBe(500); await page.setViewportSize({ width: 900, height: 800 }); + // #599: `setViewportSize` resolves as soon as the viewport is applied, but + // the DISPLAYED width above is recomputed by app-shell.ts's + // `reclampInspectorWidth`, run from a real `window` 'resize' event + // LISTENER — dispatched asynchronously relative to that resolution, and + // more likely to lose the race under parallel load. Poll for the settled + // width instead of reading it on the very next microtask. + await expect.poll(async () => Math.round((await inspectorHost.boundingBox()).width)) + // Default sidebarPx (248) + 2 handles (14) reserved, minus CENTRE_MIN_PX + // (320): ceiling = 900-262-320 = 318, below the shared 320 floor — clamp + // floors it at 320 exactly. + .toBe(320); const after = await inspectorHost.boundingBox(); - // Default sidebarPx (248) + 2 handles (14) reserved, minus CENTRE_MIN_PX - // (320): ceiling = 900-262-320 = 318, below the shared 320 floor — clamp - // floors it at 320 exactly. - expect(Math.round(after.width)).toBe(320); expect(after.width).toBeLessThan(before.width); test.info().annotations.push( diff --git a/tests/e2e/tile-open-workbench.spec.js b/tests/e2e/tile-open-workbench.spec.js index 98a81c08..3dca7b99 100644 --- a/tests/e2e/tile-open-workbench.spec.js +++ b/tests/e2e/tile-open-workbench.spec.js @@ -361,7 +361,15 @@ test('widen doubles a grid tile\'s rendered width, preserves its height, then wr // until #564. expect(wide.width).toBeGreaterThan(before.width); expect(wide.height).toBe(before.height); - expect((await page.evaluate(() => window.__dashboard('sales'))).items['t-sales']) + // #599: the widen press applies OPTIMISTICALLY first (`runCommand`, + // src/ui/dashboard.ts) — the geometry above is already settled by the time + // `widen.click()` resolves, but the PERSISTED commit this reads is a + // separate, fire-and-forget `app.mutateWorkspace` call that lands later. + // Poll rather than reading it on the very next microtask, matching the same + // commit-republish race the later "narrow tile" test already guards + // against (its own "Read defensively" comment, below). + await expect.poll(async () => (await page.evaluate(() => window.__dashboard('sales'))) + ?.items?.['t-sales'] ?? null) .toEqual({ grid: { span: 12, height: 2 } }); // At the maximum the next press wraps to a single column.