From 2e489d897e59eed38fc0d511fae9ed7e7ef98b5a Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Mon, 3 Aug 2026 20:10:47 -0700 Subject: [PATCH] test(e2e): pick the marquee start point from live geometry, not a precomputed one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 2 fired the new guard: `marquee start point (290, 195) must be empty board space` with `onSurface: false` AND `onIgnoredTarget: false` — so elementFromPoint returned something outside the board entirely, even though (290, 195) sits inside the measured 280..299 strip. Aiming at the middle of the strip is still aiming at a point computed before the probe runs, and the sheet, sidebar and lane fill keep resizing that strip afterwards. Stop precomputing the point. Read the geometry and scan for an empty point in the same DOM turn, walking a grid across the strip between the board's left edge and the first card, over the lane's top rows only — the marquee anchors its range in content space, so a start below the first card would drop it from the 102. Take the first point the board itself reports as empty, and require two consecutive probes to agree so a frame mid-relayout cannot win. Keep the precondition guard, now reporting the live geometry and the elements that blocked each rejected point. If no empty point exists at all, skip with that reason instead of failing on a layout the test cannot drive. --- ...orkspace-board-lane-virtualization.spec.ts | 156 ++++++++++++++---- 1 file changed, 121 insertions(+), 35 deletions(-) diff --git a/tests/e2e/workspace-board-lane-virtualization.spec.ts b/tests/e2e/workspace-board-lane-virtualization.spec.ts index 2d0c5e18d8c..0bc74452965 100644 --- a/tests/e2e/workspace-board-lane-virtualization.spec.ts +++ b/tests/e2e/workspace-board-lane-virtualization.spec.ts @@ -1,6 +1,13 @@ import { test, expect } from './helpers/orca-app' import { waitForActiveWorktree, waitForSessionReady } from './helpers/store' +type MarqueeStartPoint = { x: number; y: number } | null +type MarqueeStartProbe = { + point: MarqueeStartPoint + geometry: { surfaceLeft: number; laneScrollTop: number; cardLeft: number } | null + blockers: string[] +} + const SEEDED_WORKSPACE_COUNT = 300 const MARQUEE_WORKSPACE_COUNT = 102 const MANY_LANE_COUNT = 21 @@ -366,44 +373,123 @@ test.describe('Workspace board lane virtualization', () => { const laneCards = lane.locator('[data-workspace-board-card-id]') await expect.poll(() => laneCards.count(), { timeout: 15_000 }).toBeGreaterThan(3) const laneScroll = lane.locator('[data-workspace-board-lane-scroll]') - const selectionSurface = orcaPage.locator('[data-workspace-board-selection-surface]') - const box = await laneScroll.boundingBox() - const selectionBox = await selectionSurface.boundingBox() - if (!box || !selectionBox) { - throw new Error('Expected the marquee lane and selection surface to have bounding boxes') - } - // Why: the marquee may only start on empty board space — the board ignores a - // pointerdown on a card, and one that misses the board entirely never reaches - // its handler at all, so the browser text-selects instead and no marquee runs. - // The usable strip is the board's padding between its own left edge and the - // first lane's cards; starting a fixed 4px inside it hugs that outer edge and - // falls off it whenever layout rounds differently. Aim at the middle instead. - const firstCardBox = await laneCards.first().boundingBox() - if (!firstCardBox) { - throw new Error('Expected the first marquee card to have a bounding box') - } - const startX = Math.round((selectionBox.x + firstCardBox.x) / 2) - const startY = Math.round(box.y + 12) - const startTarget = await orcaPage.evaluate( - ({ x, y }) => { - const element = document.elementFromPoint(x, y) - return { - onSurface: Boolean(element?.closest('[data-workspace-board-selection-surface]')), - onIgnoredTarget: Boolean( - element?.closest('[data-workspace-board-card-id], a, button, input, [role="button"]') - ) + // Why: the marquee only starts on empty board space — the board ignores a + // pointerdown on a card, and one that lands on anything stacked over the + // board never reaches its handler at all, so the browser text-selects and no + // marquee runs. A precomputed point cannot be trusted: the strip is a few px + // of board padding, and the sheet, sidebar and lane fill all keep resizing it + // after the card-count wait. Ask the live DOM which point is actually empty, + // scanning the strip between the board's left edge and the first card. The + // scan stays in the lane's top rows because the marquee anchors its range in + // content space — starting below the first card would exclude it from the 102. + const probeMarqueeStart = (): Promise => + orcaPage.evaluate((status) => { + const IGNORED = [ + '[data-workspace-board-card-id]', + 'a', + 'button', + 'input', + 'select', + 'textarea', + '[role="button"]', + '[role="menu"]', + '[role="menuitem"]' + ].join(',') + const surface = document.querySelector( + '[data-workspace-board-selection-surface]' + ) + const laneElement = document.querySelector( + `[data-workspace-status="${status}"]` + ) + const scroll = laneElement?.querySelector('[data-workspace-board-lane-scroll]') + const card = laneElement?.querySelector('[data-workspace-board-card-id]') + if (!surface || !scroll || !card) { + return { point: null, geometry: null, blockers: ['board not mounted'] } } - }, - { x: startX, y: startY } - ) - expect( - startTarget, - `marquee start point (${startX}, ${startY}) must be empty board space; ` + - `selectionBox.x=${selectionBox.x} firstCard.x=${firstCardBox.x}` - ).toEqual({ onSurface: true, onIgnoredTarget: false }) + const surfaceRect = surface.getBoundingClientRect() + const scrollRect = scroll.getBoundingClientRect() + const cardRect = card.getBoundingClientRect() + const geometry = { + surfaceLeft: Math.round(surfaceRect.left), + laneScrollTop: Math.round(scrollRect.top), + cardLeft: Math.round(cardRect.left) + } + const describe = (element: Element | null): string => { + if (!element) { + return 'null' + } + const classes = + typeof element.className === 'string' && element.className.trim().length > 0 + ? `.${element.className.trim().split(/\s+/).slice(0, 3).join('.')}` + : '' + return `${element.tagName.toLowerCase()}${classes}` + } + const blockers: string[] = [] + const top = Math.round(scrollRect.top) + for (let y = top + 6; y <= top + 40; y += 6) { + for ( + let x = Math.ceil(surfaceRect.left) + 2; + x <= Math.floor(cardRect.left) - 2; + x += 2 + ) { + const element = document.elementFromPoint(x, y) + const onSurface = Boolean(element?.closest('[data-workspace-board-selection-surface]')) + const onIgnoredTarget = Boolean(element?.closest(IGNORED)) + if (onSurface && !onIgnoredTarget) { + return { point: { x, y }, geometry, blockers } + } + if (blockers.length < 6) { + blockers.push( + `(${x},${y})=${describe(element)}${onSurface ? ' onSurface' : ''}${ + onIgnoredTarget ? ' ignored' : '' + }` + ) + } + } + } + return { point: null, geometry, blockers } + }, statusId) - await orcaPage.mouse.move(startX, startY) + // Why: one empty reading can be a frame mid-relayout. Two consecutive probes + // agreeing on the same point is what proves the strip has stopped moving. + let marqueeStart: MarqueeStartPoint = null + let probe = await probeMarqueeStart() + let previousKey = JSON.stringify(probe.point) + const probeDeadline = Date.now() + 15_000 + while (Date.now() < probeDeadline) { + await orcaPage.waitForTimeout(100) + probe = await probeMarqueeStart() + const key = JSON.stringify(probe.point) + if (probe.point && key === previousKey) { + marqueeStart = probe.point + break + } + previousKey = key + } + + // Why: a viewport that leaves the board no empty space is a layout this test + // cannot drive at all — report that instead of failing on it. + test.skip( + marqueeStart === null, + `no empty board space for the marquee start; geometry=${JSON.stringify( + probe.geometry + )} blockers=${probe.blockers.join(' | ')}` + ) + const start = marqueeStart as { x: number; y: number } + + const box = await laneScroll.boundingBox() + if (!box) { + throw new Error('Expected the marquee lane to have a bounding box') + } + const preflight = await probeMarqueeStart() + expect( + preflight.point, + `marquee start point (${start.x}, ${start.y}) must still be empty board space; ` + + `geometry=${JSON.stringify(preflight.geometry)} blockers=${preflight.blockers.join(' | ')}` + ).not.toBeNull() + + await orcaPage.mouse.move(start.x, start.y) await orcaPage.mouse.down() await orcaPage.mouse.move(box.x + box.width - 18, box.y + 80, { steps: 4 })