test(e2e): measure the select popup after it settles instead of mid-flight

Both anchoring tests read the trigger's box before the click and the popup's box the
instant it turns visible. Base UI places the popup asynchronously and opening it can
shift the trigger, so both boxes could be sampled before the layout settled. The run
on 1eedaa3a43 missed by 4.2px (expected >= 446.015, got 441.799) on a tree with no UI
changes at all, having passed on 21092d633b, which differs only in a deleted python
test and a budget json.

Each assertion now re-reads both boxes under expect.poll. The conditions themselves
are unchanged: the popup must sit at or below the trigger's bottom edge in the first
test and must not overlap it in the second. Polling cannot mask a genuinely misplaced
popup, since one that never lands correctly still fails when the poll times out.

(cherry picked from commit d8679508d4)
This commit is contained in:
Yuneng Jiang 2026-08-28 00:15:02 -07:00
parent a2aecfea7f
commit da1d292dce
No known key found for this signature in database

View file

@ -1,4 +1,4 @@
import { expect, test, type Page as PlaywrightPage } from "@playwright/test";
import { expect, test, type Locator, type Page as PlaywrightPage } from "@playwright/test";
import { ADMIN_STORAGE_PATH } from "../../constants";
import { navigateToPage } from "../../helpers/navigation";
import { Page } from "../../fixtures/pages";
@ -17,43 +17,49 @@ async function openTemplateSelect(page: PlaywrightPage) {
return trigger;
}
function pollPixelsBelowTrigger(trigger: Locator, popup: Locator) {
return expect.poll(async () => {
const triggerBox = await trigger.boundingBox();
const popupBox = await popup.boundingBox();
if (!triggerBox || !popupBox) return null;
return popupBox.y - (triggerBox.y + triggerBox.height);
});
}
function pollPopupOverlapsTrigger(trigger: Locator, popup: Locator) {
return expect.poll(async () => {
const triggerBox = await trigger.boundingBox();
const popupBox = await popup.boundingBox();
if (!triggerBox || !popupBox) return null;
return popupBox.y < triggerBox.y + triggerBox.height && popupBox.y + popupBox.height > triggerBox.y;
});
}
test.describe("Auto Router template select anchoring", () => {
test.use({ storageState: ADMIN_STORAGE_PATH });
test("opens the options below the trigger rather than over it", async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 900 });
const trigger = await openTemplateSelect(page);
const triggerBox = await trigger.boundingBox();
await trigger.click();
const popup = page.locator('[data-slot="select-content"]');
await expect(popup).toBeVisible();
const popupBox = await popup.boundingBox();
expect(triggerBox).not.toBeNull();
expect(popupBox).not.toBeNull();
// Item-aligned mode reports "none" and puts the active item over the trigger.
await expect(popup).toHaveAttribute("data-side", "bottom");
expect(popupBox!.y).toBeGreaterThanOrEqual(triggerBox!.y + triggerBox!.height);
await pollPixelsBelowTrigger(trigger, popup).toBeGreaterThanOrEqual(0);
});
test("flips above the trigger instead of covering it when there is no room below", async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 560 });
const trigger = await openTemplateSelect(page);
await trigger.scrollIntoViewIfNeeded();
const triggerBox = await trigger.boundingBox();
await trigger.click();
const popup = page.locator('[data-slot="select-content"]');
await expect(popup).toBeVisible();
const popupBox = await popup.boundingBox();
expect(triggerBox).not.toBeNull();
expect(popupBox).not.toBeNull();
const overlaps =
popupBox!.y < triggerBox!.y + triggerBox!.height && popupBox!.y + popupBox!.height > triggerBox!.y;
expect(overlaps).toBe(false);
await pollPopupOverlapsTrigger(trigger, popup).toBe(false);
});
});