Something went wrong. Try again.
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849/** * What the lock file remembers about an element (docs/design/data-formats.md "Element descriptor"). The page builds it, * the runner stores and compares it, so the shape lives here where both can see it. */export const ATTR_KEYS = ["aria-label", "data-testid", "id", "name", "type", "placeholder"] as const;export type AttrKey = (typeof ATTR_KEYS)[number];
export interface ElementDescriptor { tag: string; role: string; name: string; text: string; attrs: Record<AttrKey, string | null>; classes: string[]; css: string; xpath: string; near: { heading: string | null; previousText: string | null; nextText: string | null }; rect: { x: number; y: number; w: number; h: number };}
const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v);const isString = (v: unknown): v is string => typeof v === "string";const isStringOrNull = (v: unknown): v is string | null => v === null || typeof v === "string";
export function isElementDescriptor(v: unknown): v is ElementDescriptor { if (!isRecord(v)) return false; if (![v.tag, v.role, v.name, v.text, v.css, v.xpath].every(isString)) return false; if (!isRecord(v.attrs) || !ATTR_KEYS.every((k) => isStringOrNull((v.attrs as Record<string, unknown>)[k]))) return false; if (!Array.isArray(v.classes) || !v.classes.every(isString)) return false; if (!isRecord(v.near) || !(["heading", "previousText", "nextText"] as const).every((k) => isStringOrNull((v.near as Record<string, unknown>)[k]))) return false; return isRecord(v.rect) && (["x", "y", "w", "h"] as const).every((k) => Number.isFinite((v.rect as Record<string, unknown>)[k]));}
/** The same descriptor with its keys in one fixed order, so it is written the same way every time. */export function normalizeDescriptor(d: ElementDescriptor): ElementDescriptor { return { tag: d.tag, role: d.role, name: d.name, text: d.text, attrs: Object.fromEntries(ATTR_KEYS.map((k) => [k, d.attrs[k]])) as ElementDescriptor["attrs"], classes: [...d.classes], css: d.css, xpath: d.xpath, near: { heading: d.near.heading, previousText: d.near.previousText, nextText: d.near.nextText }, rect: { x: d.rect.x, y: d.rect.y, w: d.rect.w, h: d.rect.h }, };}