test(e2e): measure the heading caption atomically

The wrap assertion gated on the section heading, but the signals card paints a
skeleton heading first and swaps it for the resolved one — so the element could
detach between the visibility check and the measurement, and the skeleton
heading carries no action slot to measure in the first place. Gate on the
caption itself and read both rects in a single evaluate.
This commit is contained in:
Marc-André Bombeck committed 2026-07-26 17:44:15 +02:00
1 parent 29188b2262
commit c941e7e5cb
1 file changed
+26 -13
+26 -13
View File
@@ -130,27 +130,40 @@ test.describe("content-heavy routes have no horizontal page scroll at phone widt
await page.setViewportSize({ width: 390, height: 851 });
await page.goto("/insights", { waitUntil: "domcontentloaded" });
const heading = page
// Wait on the CAPTION, not the heading: the card first paints a skeleton
// whose heading carries no action slot, then swaps to the resolved shell.
// Both render `[data-slot="section-heading"]`, so gating on the heading
// alone can measure the skeleton — or measure nothing at all, because the
// element detaches during the swap.
const method = page
.locator(
'[data-slot="coincident-deviation-section"] [data-slot="section-heading"]',
'[data-slot="coincident-deviation-section"] [data-slot="provenance-explainer-method"]',
)
.first();
await expect(heading).toBeVisible();
await expect(method).toBeVisible();
const box = await heading.boundingBox();
const method = heading.locator('[data-slot="provenance-explainer-method"]');
const methodBox = await method.boundingBox();
expect(box, "heading has no box").not.toBeNull();
expect(methodBox, "provenance caption has no box").not.toBeNull();
// Read both rects in ONE evaluate so the swap cannot land between them.
const box = await method.evaluate((el) => {
const headingEl = el.closest('[data-slot="section-heading"]');
if (!headingEl) return null;
const caption = el.getBoundingClientRect();
const heading = headingEl.getBoundingClientRect();
return {
captionWidth: caption.width,
headingWidth: heading.width,
viewportWidth: window.innerWidth,
};
});
expect(box, "caption is not inside a section heading").not.toBeNull();
expect(
Math.round(methodBox!.width),
`caption width ${methodBox!.width} exceeds its heading row ${box!.width} — the action slot is sizing to max-content instead of wrapping`,
).toBeLessThanOrEqual(Math.round(box!.width) + 1);
Math.round(box!.captionWidth),
`caption width ${box!.captionWidth} exceeds its heading row ${box!.headingWidth} — the action slot is sizing to max-content instead of wrapping`,
).toBeLessThanOrEqual(Math.round(box!.headingWidth) + 1);
expect(
Math.round(box!.width),
Math.round(box!.headingWidth),
"heading row is wider than the viewport",
).toBeLessThanOrEqual(390);
).toBeLessThanOrEqual(box!.viewportWidth);
});
/**