mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
Four specs navigated and then read the DOM once, with nothing in between that waits for anything but the network. An idle network says nothing about whether React has rendered, so the read can sample a tree that is still mounting and the assertion then blames the application for a race inside the test. Two such reads went red on consecutive CI runs, on different specs. The settings over-scroll guard also slept a fixed 500 ms, which is the wrong shape twice over: too long when the page is ready, too short on a loaded runner, and it never says what it is waiting for. The structural guard runs in the unit suite rather than in Playwright, because e2e runs at PR-CI only and flaky counts as failed there — one unguarded read otherwise costs a whole pipeline before anyone sees it. It refuses to count a wait whose rejection is thrown away, since that reads like a guarantee and is not one, and would leave the check defeatable by appending a catch. The one deliberate swallow is on the allowlist with its reason: a genuine regression there still fails at the width assertion below it, which reports the measured number instead of a timeout message.
40 lines
1.7 KiB
TypeScript
40 lines
1.7 KiB
TypeScript
import { expect, type Locator, type Page } from "@playwright/test";
|
|
|
|
/**
|
|
* Gate a one-shot DOM read on the element it is about to measure.
|
|
*
|
|
* The failure this exists to prevent: a spec navigates with
|
|
* `waitUntil: "networkidle"`, then reads the DOM in a single
|
|
* `page.evaluate` / `evaluateAll` that does not retry. An idle network says
|
|
* nothing about whether React has rendered, so the read can land on a tree
|
|
* that is still mounting and the assertion then blames the application for
|
|
* something the test raced — "language + dob fields must exist" when the
|
|
* fields were simply not there yet, or a measurement helper accused of
|
|
* measuring the wrong element when it was measuring the right one too early.
|
|
*
|
|
* Two such tests went red on consecutive CI runs during v1.33.0. Neither
|
|
* reproduces locally: a developer machine is fast enough that the render
|
|
* always wins the race, so the only place this shows up is a loaded runner,
|
|
* and `failOnFlakyTests` means every occurrence costs a whole red pipeline
|
|
* even when the retry passes.
|
|
*
|
|
* `sentinel` must be something the measurement actually depends on, not just
|
|
* any element on the page. Gating on a layout shell that renders before the
|
|
* data proves nothing.
|
|
*/
|
|
export async function settleBeforeMeasure(
|
|
page: Page,
|
|
sentinel: Locator,
|
|
options: { attachedOnly?: boolean } = {},
|
|
): Promise<void> {
|
|
if (options.attachedOnly) {
|
|
await expect(sentinel).toBeAttached();
|
|
} else {
|
|
await expect(sentinel).toBeVisible();
|
|
}
|
|
// Fonts and late CSS change geometry after the element is visible, and
|
|
// every measurement in this suite is geometric.
|
|
await page.waitForLoadState("networkidle").catch(() => {});
|
|
await page.evaluate(() => document.fonts?.ready);
|
|
}
|