import type { Page } from '@playwright/test'; import { type NavigableItem } from './inventory.js'; import { type DataResidueEntry } from './data-residue.js'; /** * Computed-style capture: the browser's final resolved value for every CSS * longhand on every element, keyed by DOM structure (never by class name, so * a CSS-to-Tailwind migration can rewrite classes freely while the map stays * comparable). Three layers per capture: * * elements — every element's computed style, pruned against per-tag UA * defaults (measured in a clean iframe) to keep files small, * plus ::before / ::after / ::marker / ::placeholder. * states — for interactive elements, what :hover, :focus(-visible) and * :active change (forced via CDP, no mouse involved), captured * as a delta over the element's subtree. Rest screenshots cannot * see these; the `*.hover.png` / `*.focus.png` / `*.active.png` * layers can. This is where dropped `hover:` variants get caught. * motion — transition/animation longhands are captured before the * freeze-CSS below nulls them, so declared motion is verified * too, while every other captured value is a settled end state. * * LIMITATIONS (documented, and warned at capture time): * - Shadow DOM (open or closed) is NOT traversed: styles inside a web * component's shadow root are invisible to the diff. A refactor inside a * shadow tree would be falsely certified identical, so capture emits a * one-time warning naming the shadow hosts it skipped. * - Iframe content (same- or cross-origin) is NOT traversed for the same * reason; same-origin frames are listed in the same warning. */ type Props = Record; /** Document-space bounding box: [x, y, width, height], rounded. */ export type Rect = [number, number, number, number]; export type ElementEntry = { tag: string; cls: string; rect?: Rect; style: Props; /** * CSS Typed OM computed values only where they differ from the legacy CSSOM * used value in `style` (for example `auto` versus a layout-resolved pixel * margin, `20%` versus a used pixel width, or `1fr` versus grid track pixels). * This compact signal lets the differ discard layout reflow while preserving * a real declaration/computed-value change. Absent on legacy captures, which * deliberately remain fail-closed. */ computedValueStyle?: Props; pseudo?: Record; /** * Length of the element's own rendered text after whitespace normalization. * Always captured, but never the text itself: this privacy-safe signal lets * the differ distinguish content-length reflow from a genuine sizing-rule * change without enabling the opt-in content layer. */ ownTextLength?: number; /** * The element's OWN rendered text (direct text-node children only, whitespace * collapsed) — present only when capture ran with `captureText: true` (the * opt-in content layer, off by default). Own-text, not subtree text, so each * change is attributed to the single element that owns it. Diffed by * {@link diffContentMaps}, never by the certification diff — content is * advisory, not a computed-style outcome. See README "Optional: content layer". */ text?: string; /** * The React component that rendered this element, and a sanitized subset of its * props — present only when capture ran with `captureComponent: true` (opt-in, * off by default). Extracted in-page from the React fiber. ADVISORY: never fed * to the certification diff or its counts (like {@link ElementEntry.text}); it * only enriches the report so a reviewer sees `Button (variant=primary)` rather * than a bare `