From 6a8e23785c4051430b987a7b625bbc7a90bd7158 Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Sun, 6 Sep 2026 11:59:26 +0200 Subject: [PATCH 1/2] test(frontend): the save's wait begins at the submit it is evidence of MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `waitForRequest` was opened above the navigation, so its five second budget covered the page load and both fills as well as the click it exists to watch. The trace of the failing run spends it exactly there: the wait opens at +573549ms, the navigation alone takes 3.09s and the description fill starts at +578678ms, past the 5000ms mark, so the budget was gone before the submit was ever clicked. The 3000ms the spec holds `GET /users` for is not what spends it — that read is concurrent and the save does not wait on it, which is the whole point of the case. Opened at the submit, the budget covers the click and the request it makes. Refs #1173 --- services/frontend/tests/e2e/committee-save.spec.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/services/frontend/tests/e2e/committee-save.spec.ts b/services/frontend/tests/e2e/committee-save.spec.ts index 82942fd1c..a402324b4 100644 --- a/services/frontend/tests/e2e/committee-save.spec.ts +++ b/services/frontend/tests/e2e/committee-save.spec.ts @@ -34,19 +34,16 @@ test.describe("the committee manager", () => { return route.fallback() }) - // Waited for before the page is opened, so a save that lands early is not missed — which - // means its budget also covers opening the page. A cold dev server compiles this route on - // the first visit, and on a loaded runner that alone outran the default five seconds. - const saved = page.waitForRequest( - request => request.method() === "PUT" && /\/committees\/900$/.test(new URL(request.url()).pathname), - {timeout: 60_000}, - ) - await page.goto("/committees/manage") await page.getByTestId("committee-edit-btn-900").click() await page.getByLabel("Committee name").fill("Events Committee Renamed") await page.getByLabel("Description").fill("A description long enough to satisfy the rule.") + // Opened here rather than above the page: the five second budget is for the submit and the + // request it makes, and a window opened earlier spends it on the load and the two fills too. + const saved = page.waitForRequest( + request => request.method() === "PUT" && /\/committees\/900$/.test(new URL(request.url()).pathname), + ) await page.getByTestId("committee-form-submit-btn").click() const request = await saved From 60e7592fe84e01b981402b22c25a6bc59f3dbead Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Sun, 6 Sep 2026 11:59:34 +0200 Subject: [PATCH 2/2] test(island): the line's landing is measured once it has landed Both specs prove the strip travels by polling until its scroll position differs from the one it opened at, which answers on the first frame of a smooth scroll, and then read the arrived node's offset from the middle exactly once. That reading is taken somewhere along the travel rather than at the end of it: the esports case failed in CI at 44px against a bound of 24. The bound is untouched and the claim is the same. Reproduced with the renderer throttled 8x, which is what starves the frames a smooth scroll advances on: 15 of 15 runs failed at 225-308px before, 0 of 15 after, and 0 of 15 at 12x. Refs #1173 --- .../frontend/tests/e2e/boards-swipe.motion.spec.ts | 10 +++++++--- .../tests/e2e/esports-season-drag.motion.spec.ts | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/services/frontend/tests/e2e/boards-swipe.motion.spec.ts b/services/frontend/tests/e2e/boards-swipe.motion.spec.ts index 15528e1b3..7f36d9d6a 100644 --- a/services/frontend/tests/e2e/boards-swipe.motion.spec.ts +++ b/services/frontend/tests/e2e/boards-swipe.motion.spec.ts @@ -243,9 +243,13 @@ test.describe("dragging the board page", () => { // arrived board's node in the middle of the window. await expect.poll(async () => (await scrollsAsked(page)).at(-1)).toBe("smooth") await expect.poll(async () => Math.round(await scrolled(page))).not.toBe(Math.round(opened)) - const node = (await page.getByTestId("board-node-3").boundingBox())! - const box = (await page.locator(SCROLLER).boundingBox())! - expect(Math.abs((node.x + node.width / 2) - (box.x + box.width / 2))).toBeLessThan(24) + // Polled rather than read once: the poll above answers the moment the smooth scroll starts, + // so a single reading is taken somewhere along the travel rather than at the end of it. + await expect.poll(async () => { + const node = (await page.getByTestId("board-node-3").boundingBox())! + const box = (await page.locator(SCROLLER).boundingBox())! + return Math.abs((node.x + node.width / 2) - (box.x + box.width / 2)) + }, {message: "the arrived board's node off the middle of the window"}).toBeLessThan(24) // A hit on one of the strip's own nodes is what it always was: the line does not move at // all, because the node the visitor aimed at would slide out from under their finger. diff --git a/services/frontend/tests/e2e/esports-season-drag.motion.spec.ts b/services/frontend/tests/e2e/esports-season-drag.motion.spec.ts index b970d1d84..c75afa854 100644 --- a/services/frontend/tests/e2e/esports-season-drag.motion.spec.ts +++ b/services/frontend/tests/e2e/esports-season-drag.motion.spec.ts @@ -347,9 +347,13 @@ test.describe("dragging a game's page between seasons", () => { // arrived season's node in the middle of the window. await expect.poll(async () => (await scrollsAsked(page)).at(-1)).toBe("smooth") await expect.poll(async () => Math.round(await scrolledIn(page, STRIP))).not.toBe(Math.round(opened)) - const node = (await page.getByTestId("esports-season-node-63").boundingBox())! - const box = (await page.locator(STRIP).boundingBox())! - expect(Math.abs((node.x + node.width / 2) - (box.x + box.width / 2))).toBeLessThan(24) + // Polled rather than read once: the poll above answers the moment the smooth scroll starts, + // so a single reading is taken somewhere along the travel rather than at the end of it. + await expect.poll(async () => { + const node = (await page.getByTestId("esports-season-node-63").boundingBox())! + const box = (await page.locator(STRIP).boundingBox())! + return Math.abs((node.x + node.width / 2) - (box.x + box.width / 2)) + }, {message: "the arrived season's node off the middle of the window"}).toBeLessThan(24) // A hit on one of the strip's own nodes is what it always was: the line does not move at // all, because the node the visitor aimed at would slide out from under their finger.