From 9b9b16cfa0acb6e6ca7e56f2821dcfce4be74ba8 Mon Sep 17 00:00:00 2001 From: "arrz.dev" <55147716+arrzdev@users.noreply.github.com> Date: Sun, 1 Nov 2026 23:11:03 +0000 Subject: [PATCH] test(e2e): the arrival probe inserts its notice on the first moving frame of the scroll The probe counted rAF frames on which the scroller had moved and inserted the notice on the third. The aim's smooth scroll runs on the clock and the frames do not: on WebKit the third moving frame sat anywhere from 194 to 448px into a 520px flight, and on a slow first run it sat on 520 itself, so the premise check (the notice went in before the scroll reached its destination) failed at 520 < 519. The first moving frame is the earliest point the aim has committed its destination, and it leaves no frames for a slow one to run the flight out: WebKit now inserts at 22-31px, chromium at 2px. The clearance assertion moves ahead of the premise check, so a field left underneath reports as that: with the second look removed, WebKit stops the flight where the notice went in (landed 130, clearance -466), which would otherwise surface as a broken premise. --- .../routing/pages/lab/avoid-keyboard.page.tsx | 9 ++++--- playground/e2e/avoid-keyboard.spec.ts | 27 +++++++++++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/playground/apps/frontend/src/routing/pages/lab/avoid-keyboard.page.tsx b/playground/apps/frontend/src/routing/pages/lab/avoid-keyboard.page.tsx index 3b44716a..7b86be73 100644 --- a/playground/apps/frontend/src/routing/pages/lab/avoid-keyboard.page.tsx +++ b/playground/apps/frontend/src/routing/pages/lab/avoid-keyboard.page.tsx @@ -143,8 +143,11 @@ function LabAvoidKeyboardPage() { * PROBES * ============================================================================= */ -//the moving frame of the aim's smooth scroll the notice lands on, and how tall it is -const ARRIVAL_FRAME = 3 +//the moving frame of the aim's smooth scroll the notice lands on, and how tall it is. +//The first: the aim has committed its destination by then, and the flight is clock-driven +//while frames are not, so on a slow frame (WebKit's first run) the third moving frame +//already sat on the destination and the run proved nothing +const ARRIVAL_FRAME = 1 const ARRIVAL_PX = 100 //a scroll is over after this many frames with nothing moving past half a pixel — //longer than the 120ms quiet window the no-`scrollend` fallback waits before re-aiming @@ -237,7 +240,7 @@ function ArrivalProbe() { return ( Run: focus, insert mid-scroll diff --git a/playground/e2e/avoid-keyboard.spec.ts b/playground/e2e/avoid-keyboard.spec.ts index 2a5f6117..04344010 100644 --- a/playground/e2e/avoid-keyboard.spec.ts +++ b/playground/e2e/avoid-keyboard.spec.ts @@ -136,14 +136,17 @@ test.describe("AvoidKeyboard driven by the keyboard seam", () => { * * The aim picks an absolute scrollTop on the focus frame; a notice inserted during * the 200-450ms flight moves the field down and leaves the destination where it - * was, so the field lands 100px short, under the box's bottom edge. Measured on - * origin/main on both engines: landed 520, clearance -76 (24 with no insertion). - * The fix looks once more when that scroll ends. + * was, so the field lands 100px short, under the box's bottom edge. Measured with + * the second look removed: chromium lands 520, clearance -76 (24 with no + * insertion); WebKit stops the flight where the notice went in, landed 130, + * clearance -466. The fix looks once more when that scroll ends. * * The lab's Run button drives it, so a device run is the same code path: it * focuses the field with `preventScroll` (the browser's own scroll-into-view would - * otherwise travel first), counts the frames on which the scroller has moved, and - * on the third inserts the notice with `flushSync`, then waits for 30 still frames. + * otherwise travel first), and on the first frame the scroller has moved inserts + * the notice with `flushSync`, then waits for 30 still frames. Not a later frame: the + * flight runs on the clock and frames do not, so on a slow frame the third one had + * already reached the destination (WebKit, inserted at 520 of 520). * * Both Playwright engines have `scrollend`, and adaptv's iOS floor does not, so the * test runs twice: once as shipped, and once with every `onscrollend` handler @@ -158,7 +161,7 @@ test.describe("AvoidKeyboard re-aims when content lands above the field mid-scro test.use({ viewport: { width: 390, height: 844 } }) for (const mode of ["scrollend", "no scrollend"] as const) { - test(`${mode}: a 100px notice inserted on frame 3 of the scroll still leaves the field clear`, async ({ + test(`${mode}: a 100px notice inserted on the first frame of the scroll still leaves the field clear`, async ({ page, }, testInfo) => { await page.addInitScript(() => { @@ -205,6 +208,13 @@ test.describe("AvoidKeyboard re-aims when content lands above the field mid-scro `ARRIVAL ${testInfo.project.name} ${mode} clearance=${clearance} ${scroll}`, ) + //first, so a field left underneath reads as that and not as a broken premise: with + //no second look the landing is short of the notice, which also fails the premise + expect( + clearance, + "the field's bottom sits at or above the box's bottom once the scroll stops", + ).toBeGreaterThanOrEqual(0) + //the premise: the notice went in while the FIRST aim's scroll was still short of its //destination. That destination is where the field would have landed with no notice, //which is the landing minus the notice. Comparing against the landing itself is @@ -228,11 +238,6 @@ test.describe("AvoidKeyboard re-aims when content lands above the field mid-scro 1, ) await expect(page.getByLabel("Arrival field")).toBeFocused() - - expect( - clearance, - "the field's bottom sits at or above the box's bottom once the scroll stops", - ).toBeGreaterThanOrEqual(0) }) } })