* fix(ci): skip unrelated required checks cleanly Matrix jobs had to start no-op runners to preserve interpolated required-check names. Static named jobs let GitHub report genuine skipped conclusions while keeping those names stable. * fix(ci): gate changes to install patching script * fix(ci): route cross-package test contracts narrowly * fix(ci): keep routed contracts merge-blocking * fix(server): exclude nested test utilities from builds * fix(ci): cover app-owned native and browser domains * refactor(ci): align tests with runtime domains Own browser specs, desktop specs, and Electron-only app code by stable directories so PR routing does not depend on filename conventions. Keep desktop integration coverage inside the required desktop check. * fix(ci): keep routing and desktop suites isolated The routing regression must run before dependency installation, and desktop Vitest must not collect Playwright-owned specs. * fix(ci): isolate desktop e2e runtime * perf(test): isolate Playwright workers * perf(test): parallelize Playwright within specs * fix(test): keep Playwright file scheduling * fix(test): share dynamic Metro fixture * fix(test): preserve own-host restart fixtures * fix(ci): preserve routed contracts after rebase * Stabilize Playwright wheel-scroll assertions
69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
import { expect, type Locator, type Page } from "@playwright/test";
|
|
|
|
export interface Rails {
|
|
left: number;
|
|
right: number;
|
|
}
|
|
|
|
interface Box {
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
async function readBox(locator: Locator): Promise<Box> {
|
|
const box = await locator.boundingBox();
|
|
expect(box, "Expected the element to be laid out").not.toBeNull();
|
|
return box as Box;
|
|
}
|
|
|
|
/** Leading and trailing edges of an element, in whole pixels. */
|
|
export async function readRails(locator: Locator): Promise<Rails> {
|
|
const box = await readBox(locator);
|
|
return { left: Math.round(box.x), right: Math.round(box.x + box.width) };
|
|
}
|
|
|
|
/**
|
|
* The two rails an AdaptiveModalSheet header indents to: the title's leading
|
|
* glyph and the close button's trailing edge. Sheet content sits on the same
|
|
* two rails, in every presentation.
|
|
*/
|
|
export async function readSheetHeaderRails(
|
|
page: Page,
|
|
sheetTestID: string,
|
|
title: string,
|
|
): Promise<Rails> {
|
|
const header = page.getByTestId(sheetTestID);
|
|
const [titleRails, closeRails] = await Promise.all([
|
|
readRails(header.getByText(title, { exact: true })),
|
|
readRails(header.getByRole("button", { name: "Close" })),
|
|
]);
|
|
return { left: titleRails.left, right: closeRails.right };
|
|
}
|
|
|
|
/** Vertical space between the bottom of one element and the top of the next. */
|
|
export async function readVerticalGap(upper: Locator, lower: Locator): Promise<number> {
|
|
const [above, below] = await Promise.all([readBox(upper), readBox(lower)]);
|
|
return Math.round(below.y - (above.y + above.height));
|
|
}
|
|
|
|
/**
|
|
* A compact sheet slides up into place, so anything measured across two reads
|
|
* has to wait for the surface to stop moving first.
|
|
*/
|
|
export async function waitForSettledPosition(locator: Locator): Promise<void> {
|
|
let previousTop: number | null = null;
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const { y } = await readBox(locator);
|
|
const settled = previousTop === y;
|
|
previousTop = y;
|
|
return settled;
|
|
},
|
|
{ message: "Expected the sheet to stop moving" },
|
|
)
|
|
.toBe(true);
|
|
}
|