/** * Content clipped where nothing scrolls: the defect the judges could see and * were forbidden to measure. * * `checkHorizontalOverflow` files only when the DOCUMENT scrolls sideways. An * element pushed past the viewport by a header with `overflow: hidden`, or past * the padding edge of any clipping ancestor, produces no page scroll at all, so * that check computes the offenders and throws them away. What was left to * notice it was a judge reading an image, which the rubric rightly tells it * cannot measure, so the finding arrived (when it arrived) as an eye-judged * claim under whichever category the panel that happened to run owned, and the * adversarial verifier could refute it by calling the missing content a * deliberate responsive collapse. A box is not arguable. * * The opposite mistake is the reason for every exclusion below. Content inside * a horizontal scroller is reachable, a truncated label with an ellipsis is * deliberate, a collapsed menu is not rendered, and a visually-hidden helper is * meant to be invisible. Each of those protrudes past something on purpose, and * filing them would bury the real clip in noise. * * Split the way harvest and provenance are: one self-contained `page.evaluate` * collects raw facts, and everything that decides is pure and unit-tested with * no browser. */ import type { Locator, Page } from "playwright"; import type { DeterministicFinding } from "../types.js"; /** What kind of thing did the clipping: decides the attribute, so it is closed. */ export type ClipperKind = "viewport" | "ancestor"; /** One element measured against the thing that clips it. Raw: nothing decided. */ export interface ClipCandidate { /** nth-of-type path, the same shape every other in-page walk produces. */ path: string; tag: string; /** Trimmed innerText, for a finding a person can locate on the screen. */ text: string; clipper: ClipperKind; /** The clipper's own path, empty for the viewport. */ clipperPath: string; /** CSS px past the clipper's right padding edge; 0 or less is inside. */ overRight: number; /** CSS px past the clipper's bottom padding edge. */ overBottom: number; /** An ancestor between the two scrolls horizontally, so this is reachable. */ scrollable: boolean; /** Deliberate truncation, hiding, or a transform the box cannot be read through. */ excused: boolean; } /** A horizontal scroller and how much of it is off screen, for the manifest note. */ export interface ScrollerNote { path: string; tag: string; width: number; hiddenWidth: number; } export interface ClipHarvest { candidates: ClipCandidate[]; scrollers: ScrollerNote[]; /** The walk hit its ceiling; an absence claim is not available from it. */ truncated: boolean; } /** * Runs in the browser. Self-contained: no closure over module scope, the same * rule harvest.ts and provenance.ts follow, which is why the path builder is * duplicated here rather than imported. */ export declare function collectClipsInPage(args: { rootSelector: string | null; ignore: string[]; maxElements: number; slack: number; }): ClipHarvest; /** * Which candidates are defects, as at most one finding per clipper kind. * * Pure, so the whole decision is testable without a browser: the in-page half * measures, this half rules. One finding per kind rather than per element, * mirroring horizontal-overflow, because a header that clips has usually * clipped several things at once and they are one fix. */ export declare function classifyClips(harvest: ClipHarvest): DeterministicFinding[]; /** Measure this shot, and rule. Returns the findings and the scrollers to note. */ export declare function checkEdgeClipping(page: Page, element: Locator | null, opts?: { ignore?: string[]; elementSelector?: string | null; }): Promise<{ findings: DeterministicFinding[]; scrollers: ScrollerNote[]; }>;