@ralphralphai/playwright 2.7.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"file":"upload-BsCzkoup.mjs","names":["delay","delay","packageInfo.version"],"sources":["../src/config.ts","../package.json","../src/actions.ts","../src/animations.ts","../src/image_responses.ts","../src/aria_snapshot.ts","../src/animation_observer.ts","../src/capture_freezer.ts","../src/conditions.ts","../src/image.ts","../src/layout.ts","../src/visit_links.ts","../src/recorder.ts","../src/upload.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport configPackage from '@ralphralphai/config/package.json' with { type: 'json' };\nimport { WebRalphConfigSchema } from '@ralphralphai/config/zod';\nimport { parse, printParseErrorCode, type ParseError } from 'jsonc-parser';\n\nimport type { ConfigSnapshot, RalphOptions } from './types';\n\nexport function resolveMode(options: RalphOptions): 'off' | 'local' | 'upload' {\n const mode =\n options.mode ??\n process.env['RALPH_MODE'] ??\n (process.env['RALPH_UPLOAD_KEY'] ? 'upload' : 'off');\n if (mode === 'off' || mode === 'local' || mode === 'upload') {\n return mode;\n }\n\n throw new Error('RALPH_MODE must be off, local, or upload.');\n}\n\ntype ConfigSource = Pick<RalphOptions, 'config' | 'configPath'>;\n\nexport async function loadConfig(\n options: ConfigSource,\n root: string,\n): Promise<ConfigSnapshot> {\n const file = configFile(options, root);\n return snapshot(options.config ?? parseConfig(await readFile(file, 'utf8')));\n}\n\n// Playwright config files may be CommonJS, so project generation cannot await.\nexport function loadConfigSync(\n options: ConfigSource,\n root: string,\n): ConfigSnapshot {\n const file = configFile(options, root);\n return snapshot(options.config ?? parseConfig(readFileSync(file, 'utf8')));\n}\n\nfunction configFile(options: ConfigSource, root: string): string {\n if (options.config && options.configPath) {\n throw new Error(\n 'Provide either ralphOptions.config or configPath, not both.',\n );\n }\n return path.resolve(root, options.configPath ?? 'ralph.jsonc');\n}\n\nfunction parseConfig(text: string): unknown {\n const errors: ParseError[] = [];\n const value: unknown = parse(text, errors, { allowTrailingComma: true });\n if (errors.length) {\n throw new Error(\n 'Invalid Ralph config: ' +\n errors\n .map(\n (error) => printParseErrorCode(error.error) + ' at ' + error.offset,\n )\n .join(', '),\n );\n }\n return value;\n}\n\nfunction snapshot(value: unknown): ConfigSnapshot {\n WebRalphConfigSchema.parse(value);\n return {\n config: JSON.parse(JSON.stringify(value)) as ConfigSnapshot['config'],\n packageVersion: configPackage.version,\n };\n}\n","","import path from 'node:path';\n\nimport type { BrowserContext, Page } from '@playwright/test';\n\nimport type { ActionKind, ActionTarget, RawAction } from './types';\n\nconst BINDING = '__ralphAction';\n\nconst MAX_TEXT = 200;\n\n/** An event reaches Node within a few actions of firing; scanning further only adds wrong matches. */\nconst RECENT_ACTIONS = 20;\n\ntype ActionSpec = {\n kind: ActionKind;\n /** The DOM events that show which element the action landed on, first one wins. */\n events: readonly string[];\n value?: (params: Record<string, unknown>) => string | undefined;\n};\n\nconst text = (key: string) => {\n return (params: Record<string, unknown>) => {\n return typeof params[key] === 'string' ? params[key] : undefined;\n };\n};\n\n/** Keyed by Playwright protocol method, which is what `Locator`, `Page`, `Frame`, `ElementHandle`, `Keyboard` and `Mouse` calls all arrive as. */\nconst ACTIONS: Record<string, ActionSpec> = {\n click: { kind: 'click', events: ['click'] },\n mouseClick: { kind: 'click', events: ['click'] },\n dblclick: { kind: 'dblclick', events: ['dblclick', 'click'] },\n tap: { kind: 'tap', events: ['touchstart', 'click'] },\n hover: { kind: 'hover', events: ['mouseover'] },\n check: { kind: 'check', events: ['click'] },\n uncheck: { kind: 'uncheck', events: ['click'] },\n fill: { kind: 'fill', events: ['input'], value: text('value') },\n type: { kind: 'type', events: ['input'], value: text('text') },\n keyboardType: { kind: 'type', events: ['input'], value: text('text') },\n keyboardInsertText: { kind: 'type', events: ['input'], value: text('text') },\n press: { kind: 'press', events: ['keydown'], value: text('key') },\n keyboardPress: { kind: 'press', events: ['keydown'], value: text('key') },\n selectOption: {\n kind: 'selectOption',\n events: ['input'],\n value: (params) => {\n const options = params['options'];\n if (!Array.isArray(options)) {\n return undefined;\n }\n return options\n .map((option: Record<string, unknown>) => {\n return String(\n option['valueOrLabel'] ??\n option['label'] ??\n option['value'] ??\n '#' + String(option['index']),\n );\n })\n .join(', ');\n },\n },\n setInputFiles: {\n kind: 'setInputFiles',\n events: ['input'],\n value: (params) => {\n const payloads = params['payloads'];\n const localPaths = params['localPaths'];\n const names = Array.isArray(payloads)\n ? payloads.map((payload: { name?: unknown }) => {\n return String(payload.name);\n })\n : Array.isArray(localPaths)\n ? localPaths.map((file) => {\n return path.basename(String(file));\n })\n : [];\n return names.length > 0 ? names.join(', ') : undefined;\n },\n },\n dragAndDrop: {\n kind: 'dragTo',\n events: ['mousedown', 'dragstart'],\n value: text('target'),\n },\n goto: { kind: 'goto', events: [], value: text('url') },\n goBack: { kind: 'goBack', events: [] },\n goForward: { kind: 'goForward', events: [] },\n reload: { kind: 'reload', events: [] },\n};\n\n/**\n * What Playwright may perform by setting the value from script, whose `input`\n * event is untrusted. An untrusted event the app fires itself is not taken\n * for any other action.\n */\nconst SCRIPTED = new Set<ActionKind>(['fill', 'selectOption', 'setInputFiles']);\n\nconst NAVIGATIONS = new Set<ActionKind>([\n 'goto',\n 'goBack',\n 'goForward',\n 'reload',\n]);\n\n/** Only the page can tell a password field from another, so these values wait\n * for its report. */\nconst VALUE_NEEDS_TARGET = new Set<ActionKind>(['fill', 'type', 'press']);\n\ntype DomEvent = {\n type: string;\n /** `Date.now()` in the page when the event fired. */\n at: number;\n /** How long the field's value was then, for an `input` on a text field. */\n valueLength?: number;\n /** Set on `keydown` for a named key, such as `Enter`. A character is never sent. */\n key?: string;\n trusted: boolean;\n sensitive: boolean;\n target: ActionTarget;\n};\n\ntype LoggedAction = {\n kind: ActionKind;\n /** Where it falls among node requests, which share the counter. */\n order: number;\n startedAt: string;\n startedAtMs: number;\n endedAtMs?: number;\n awaits: readonly string[];\n selector?: string;\n value?: string;\n pageId?: string;\n target?: ActionTarget;\n sensitive?: boolean;\n valueLength?: number;\n};\n\n/** The edge into `nodeId`, which takes the actions ordered after `after` up to\n * `upTo`. */\nexport type ActionWindow = {\n nodeId: string;\n after: number;\n upTo: number;\n pageIds: ReadonlySet<string>;\n};\n\n/** The listener interface of Playwright's client instrumentation, which its own\n * test runner uses to turn API calls into steps. */\ntype ApiCallListener = {\n onApiCallBegin?: (\n zone: object,\n call: { type: string; method: string; params?: Record<string, unknown> },\n ) => void;\n onApiCallEnd?: (zone: object) => void;\n};\ntype ClientInstrumentation = {\n addListener(listener: ApiCallListener): void;\n removeListener(listener: ApiCallListener): void;\n};\n\n/**\n * The actions a test takes between two nodes, each with the element it\n * landed on, for the edge between them. Two sources are needed because\n * neither knows enough alone.\n *\n * 1. What the test asked for. The Playwright client turns every call, such\n * as `locator.click()`, into a protocol message, such as `Frame.click\n * { selector }`, that it sends to the Playwright server driving the\n * browser. Before sending, it calls `onApiCallBegin(zone, { type, method,\n * params })` on its instrumentation, and `onApiCallEnd(zone)` once the\n * reply arrives. `@playwright/test` builds its report steps from the same\n * hook. It gives the exact action and its selector, value, key or URL, but\n * not the tab it ran in or the element the selector resolved to. It is\n * not public API, so when a Playwright release changes it, `attach` finds\n * no listener interface and only the page's reports of clicks, typing and\n * Enter remain.\n *\n * 2. Where it landed. An init script in every page reports the trusted DOM\n * events Playwright's input causes: the element's role, name,\n * `data-track-id` and box in the screenshot's space, whether it is a\n * password field, and when the event fired. The binding it reports\n * through tells which tab it came from. A navigation causes no such\n * event, so its tab is the one that navigates while it runs (see\n * `navigated`).\n *\n * A report travels from the page through the Playwright server separately\n * from the call's reply, so it can arrive after the next call began. It is\n * therefore matched by the time the event fired against each call's start\n * and end, never by arrival (see `landed`). A typed value survives only once\n * the page has shown that its field is not sensitive.\n *\n * Calls and node requests draw from one counter (`next`), so at the end of\n * the test `assign` can give each action to the edge into the first node\n * requested after it.\n */\nexport class ActionLog {\n private readonly actions: LoggedAction[] = [];\n private readonly byZone = new WeakMap<object, LoggedAction>();\n private instrumentation?: ClientInstrumentation;\n private readonly listener: ApiCallListener = {\n onApiCallBegin: (zone, call) => {\n this.begin(zone, call);\n },\n onApiCallEnd: (zone) => {\n const action = this.byZone.get(zone);\n if (action) {\n action.endedAtMs = Date.now();\n }\n },\n };\n\n constructor(\n private readonly next: () => number,\n private readonly recording: () => boolean,\n private readonly pageIdOf: (page: Page) => string | undefined,\n private readonly openPageIds: () => string[],\n ) {}\n\n attach(playwright: unknown): void {\n const instrumentation = (\n playwright as { _instrumentation?: Partial<ClientInstrumentation> }\n )?._instrumentation;\n\n if (\n typeof instrumentation?.addListener === 'function' &&\n typeof instrumentation.removeListener === 'function'\n ) {\n this.instrumentation = instrumentation as ClientInstrumentation;\n this.instrumentation.addListener(this.listener);\n }\n }\n\n detach(): void {\n this.instrumentation?.removeListener(this.listener);\n }\n\n async observe(context: BrowserContext): Promise<void> {\n // Sent together, so all are queued before the test's next action. The\n // init script only reaches documents created after it, so a page loaded\n // before the context was observed gets the reporter directly.\n await Promise.all([\n context\n .exposeBinding(BINDING, ({ page }, event: unknown) => {\n if (isDomEvent(event)) {\n this.landed(page, event);\n }\n })\n .catch(() => {\n // A context the test reuses across tests already has the binding,\n // and reports to the recorder of the test that installed it.\n // Actions then have no element, which only costs their boxes.\n }),\n context.addInitScript(installActionReporter, BINDING).catch(() => {}),\n ...context.pages().map((page) => {\n return page.evaluate(installActionReporter, BINDING).catch(() => {});\n }),\n ]);\n }\n\n /**\n * A navigation reaches no element for the page to report, so the tab whose\n * main frame navigates while the call runs is the tab it ran in.\n */\n navigated(page: Page): void {\n const pageId = this.pageIdOf(page);\n const running = this.actions\n .slice(-RECENT_ACTIONS)\n .reverse()\n .find((action) => {\n return (\n NAVIGATIONS.has(action.kind) &&\n action.pageId === undefined &&\n action.endedAtMs === undefined\n );\n });\n\n if (running && pageId !== undefined) {\n running.pageId = pageId;\n }\n }\n\n /**\n * Each window's actions. An action goes to the earliest window\n * that holds it, so the click that opened a popup is on the edge into the\n * popup and not also on the next edge of its own tab. An action whose tab\n * is still unknown goes to none: it could have run in any tab open then.\n */\n assign(windows: ActionWindow[], limit: number): Map<string, RawAction[]> {\n const taken = new Set<LoggedAction>();\n const assigned = new Map<string, RawAction[]>();\n const byEnd = [...windows].sort((a, b) => a.upTo - b.upTo);\n for (const { nodeId, after, upTo, pageIds } of byEnd) {\n const actions = this.actions.filter((action) => {\n return (\n !taken.has(action) &&\n action.order > after &&\n action.order <= upTo &&\n action.pageId !== undefined &&\n pageIds.has(action.pageId)\n );\n });\n for (const action of actions) {\n taken.add(action);\n }\n\n if (actions.length > 0) {\n assigned.set(nodeId, actions.slice(-limit).map(toRawAction));\n }\n }\n return assigned;\n }\n\n private begin(\n zone: object,\n call: { type: string; method: string; params?: Record<string, unknown> },\n ): void {\n const spec = ACTIONS[call.method];\n if (\n !spec ||\n !['Frame', 'Page', 'ElementHandle'].includes(call.type) ||\n !this.recording()\n ) {\n return;\n }\n\n const params = call.params ?? {};\n const selector =\n typeof params['selector'] === 'string'\n ? params['selector']\n : typeof params['source'] === 'string'\n ? params['source']\n : undefined;\n\n // The call does not say which tab it is on. With one tab open, that is\n // the one; otherwise the page's report or the navigation it causes says.\n const open = this.openPageIds();\n const action: LoggedAction = {\n kind: spec.kind,\n order: this.next(),\n startedAt: new Date().toISOString(),\n startedAtMs: Date.now(),\n awaits: spec.events,\n selector,\n value: clip(spec.value?.(params)),\n pageId: open.length === 1 ? open[0] : undefined,\n };\n this.byZone.set(zone, action);\n this.actions.push(action);\n }\n\n /**\n * The event goes to the action that was running when it fired. Its report\n * can reach here after the next action started, so arrival order says\n * nothing. When one action ends in the millisecond the next starts, the\n * earlier one takes it, since an event fires before its action returns.\n */\n private landed(page: Page, event: DomEvent): void {\n const pageId = this.pageIdOf(page);\n const action = this.actions.slice(-RECENT_ACTIONS).find((candidate) => {\n return (\n candidate.target === undefined &&\n candidate.awaits.includes(event.type) &&\n (event.trusted || SCRIPTED.has(candidate.kind)) &&\n candidate.startedAtMs <= event.at &&\n event.at <= (candidate.endedAtMs ?? Infinity)\n );\n });\n\n if (action) {\n action.target = event.target;\n action.sensitive = event.sensitive;\n if (event.valueLength !== undefined) {\n action.valueLength = event.valueLength;\n }\n\n if (pageId !== undefined) {\n action.pageId = pageId;\n }\n return;\n }\n\n if (!this.instrumentation && event.trusted && this.recording()) {\n this.logFromPage(event, pageId);\n }\n }\n\n private logFromPage(event: DomEvent, pageId: string | undefined): void {\n const kind: ActionKind | undefined =\n event.type === 'click'\n ? 'click'\n : event.type === 'input'\n ? 'fill'\n : event.type === 'keydown' && event.key === 'Enter'\n ? 'press'\n : undefined;\n if (kind === undefined) {\n return;\n }\n\n const previous = this.actions.at(-1);\n const sameField =\n kind === 'fill' &&\n previous?.kind === 'fill' &&\n JSON.stringify(previous.target) === JSON.stringify(event.target);\n if (sameField) {\n return;\n }\n\n this.actions.push({\n kind,\n order: this.next(),\n startedAt: new Date().toISOString(),\n startedAtMs: Date.now(),\n awaits: [],\n target: event.target,\n sensitive: event.sensitive,\n value: kind === 'press' ? event.key : undefined,\n pageId,\n });\n }\n}\n\nfunction toRawAction(action: LoggedAction): RawAction {\n return {\n kind: action.kind,\n startedAt: action.startedAt,\n pageId: action.pageId,\n selector: action.selector,\n value: revealedValue(action),\n target: action.target,\n };\n}\n\nfunction revealedValue(action: LoggedAction): string | undefined {\n if (!VALUE_NEEDS_TARGET.has(action.kind)) {\n return action.value;\n }\n\n const namedKey = action.kind === 'press' && (action.value?.length ?? 0) > 1;\n if (namedKey) {\n return action.value;\n }\n\n if (action.target === undefined || action.sensitive !== false) {\n return undefined;\n }\n\n // The field's length checks that the event really was this action's: a\n // browser whose clock is off from this one could hand one field's event to\n // the action on another, and show a password under the wrong field.\n const length = action.value?.length ?? 0;\n const fits =\n action.kind === 'fill'\n ? action.valueLength === length\n : action.kind === 'type'\n ? (action.valueLength ?? -1) >= length\n : true;\n return fits ? action.value : undefined;\n}\n\nfunction clip(value: string | undefined): string | undefined {\n return value === undefined || value.length <= MAX_TEXT\n ? value\n : value.slice(0, MAX_TEXT - 1) + '…';\n}\n\nfunction isDomEvent(event: unknown): event is DomEvent {\n const candidate = event as Partial<DomEvent> | null;\n const box = candidate?.target?.box;\n return (\n typeof candidate?.type === 'string' &&\n typeof candidate.at === 'number' &&\n typeof candidate.trusted === 'boolean' &&\n typeof candidate.sensitive === 'boolean' &&\n typeof candidate.target?.tag === 'string' &&\n typeof box?.x === 'number' &&\n typeof box.y === 'number' &&\n typeof box.width === 'number' &&\n typeof box.height === 'number'\n );\n}\n\n/**\n * Runs in every page. Serialized by Playwright, so it may use nothing from\n * its module.\n */\nfunction installActionReporter(binding: string): void {\n // An iframe's boxes would be relative to the iframe, not the screenshot.\n if (window !== window.top) {\n return;\n }\n\n // A document loaded while its context was being observed gets the reporter\n // both from the init script and directly, and must report each event once.\n const installed = binding + 'Installed';\n if (installed in window) {\n return;\n }\n Object.defineProperty(window, installed, { value: true });\n\n const maxText = 200;\n const clean = (value: string | null | undefined) => {\n const collapsed = value?.replace(/\\s+/g, ' ').trim();\n return collapsed ? collapsed.slice(0, maxText) : undefined;\n };\n const inputRoles: Record<string, string> = {\n checkbox: 'checkbox',\n radio: 'radio',\n button: 'button',\n submit: 'button',\n reset: 'button',\n image: 'button',\n range: 'slider',\n number: 'spinbutton',\n search: 'searchbox',\n };\n const roleOf = (element: Element): string | undefined => {\n const explicit = element.getAttribute('role')?.trim().split(/\\s+/)[0];\n if (explicit) {\n return explicit;\n }\n\n const tag = element.tagName.toLowerCase();\n if (tag === 'a') {\n return element.hasAttribute('href') ? 'link' : undefined;\n }\n\n if (tag === 'button' || tag === 'summary') {\n return 'button';\n }\n\n if (tag === 'select') {\n const select = element as HTMLSelectElement;\n return select.multiple || select.size > 1 ? 'listbox' : 'combobox';\n }\n\n if (tag === 'textarea') {\n return 'textbox';\n }\n\n if (tag === 'option') {\n return 'option';\n }\n\n if (tag === 'img') {\n return 'img';\n }\n\n if (/^h[1-6]$/.test(tag)) {\n return 'heading';\n }\n\n if (tag === 'input') {\n const input = element as HTMLInputElement;\n return (\n inputRoles[input.type] ??\n (input.hasAttribute('list') ? 'combobox' : 'textbox')\n );\n }\n return undefined;\n };\n // An approximation of the accessible name. A text field's value is never\n // part of it, since that is what was typed.\n const nameOf = (element: Element): string | undefined => {\n const labelledBy = element.getAttribute('aria-labelledby');\n if (labelledBy) {\n const labelled = clean(\n labelledBy\n .split(/\\s+/)\n .map((id) => document.getElementById(id)?.textContent ?? '')\n .join(' '),\n );\n if (labelled) {\n return labelled;\n }\n }\n\n const ariaLabel = clean(element.getAttribute('aria-label'));\n if (ariaLabel) {\n return ariaLabel;\n }\n\n if (\n element instanceof HTMLInputElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLTextAreaElement\n ) {\n const labels = clean(\n [...(element.labels ?? [])]\n .map((label) => label.textContent ?? '')\n .join(' '),\n );\n if (labels) {\n return labels;\n }\n\n if (\n element instanceof HTMLInputElement &&\n ['submit', 'button', 'reset'].includes(element.type)\n ) {\n return clean(element.value);\n }\n return (\n clean(element.getAttribute('placeholder')) ??\n clean(element.getAttribute('title'))\n );\n }\n\n if (element instanceof HTMLImageElement) {\n return clean(element.alt) ?? clean(element.title);\n }\n return (\n clean((element as HTMLElement).innerText ?? element.textContent) ??\n clean(element.getAttribute('title'))\n );\n };\n const interactive =\n 'a[href],button,input,select,textarea,summary,label,option,[role],[data-track-id],[contenteditable=\"\"],[contenteditable=\"true\"],[tabindex]';\n\n let lastHovered: Element | undefined;\n const report = (event: Event) => {\n // Playwright sets a select's or a date field's value from script, which\n // fires untrusted events, so whether to believe one is decided in Node.\n if (!event.isTrusted && event.type !== 'input') {\n return;\n }\n\n const origin = event.composedPath()[0];\n const element =\n origin instanceof Element\n ? origin\n : origin instanceof Node\n ? origin.parentElement\n : null;\n if (!element) {\n return;\n }\n\n const actual = element.closest(interactive) ?? element;\n if (event.type === 'mouseover') {\n if (actual === lastHovered) {\n return;\n }\n lastHovered = actual;\n }\n\n const key = (event as KeyboardEvent).key;\n const rect = actual.getBoundingClientRect();\n const trackId = actual\n .closest('[data-track-id]')\n ?.getAttribute('data-track-id');\n const role = roleOf(actual);\n const name = nameOf(actual);\n const sensitive =\n actual instanceof HTMLInputElement &&\n (actual.type === 'password' ||\n /password|one-time-code|cc-/.test(actual.autocomplete));\n const valueLength =\n event.type === 'input' &&\n (actual instanceof HTMLInputElement ||\n actual instanceof HTMLTextAreaElement)\n ? actual.value.length\n : undefined;\n const send = (window as unknown as Record<string, unknown>)[binding];\n if (typeof send !== 'function') {\n return;\n }\n\n void (send as (payload: unknown) => Promise<void>)({\n type: event.type,\n trusted: event.isTrusted,\n at: Date.now(),\n ...(valueLength === undefined ? {} : { valueLength }),\n ...(event.type === 'keydown' && key && key.length > 1 ? { key } : {}),\n sensitive,\n target: {\n tag: actual.tagName.toLowerCase(),\n ...(role ? { role } : {}),\n ...(name ? { name } : {}),\n ...(trackId ? { trackId } : {}),\n box: {\n x: rect.x + window.scrollX,\n y: rect.y + window.scrollY,\n width: rect.width,\n height: rect.height,\n },\n },\n }).catch(() => undefined);\n };\n for (const type of [\n 'click',\n 'dblclick',\n 'touchstart',\n 'mouseover',\n 'mousedown',\n 'dragstart',\n 'input',\n 'keydown',\n ]) {\n window.addEventListener(type, report, { capture: true, passive: true });\n }\n}\n","import { errors, type Page } from '@playwright/test';\n\n/**\n * Waits until no finite CSS animation, CSS transition, or Web Animation is\n * running. Infinite ones (spinners, shimmers) never finish, so they are\n * ignored. Animations driven by `requestAnimationFrame` are invisible here.\n *\n * Stops waiting after `timeoutMs` without failing; the screenshot then jumps\n * whatever is left to its end state.\n */\nexport async function waitForAnimations(\n page: Page,\n timeoutMs: number,\n): Promise<void> {\n try {\n await page.waitForFunction(\n () =>\n document\n .getAnimations()\n .every(\n (animation) =>\n animation.playState !== 'running' ||\n animation.playbackRate === 0 ||\n !Number.isFinite(animation.effect?.getComputedTiming().endTime),\n ),\n undefined,\n // Polled every frame: animations often start a frame or two late.\n { polling: 'raf', timeout: Math.max(1, timeoutMs) },\n );\n } catch (error) {\n if (!(error instanceof errors.TimeoutError)) {\n throw error;\n }\n }\n}\n","import { createHash } from 'node:crypto';\n\nimport type { BrowserContext, Page, Request } from '@playwright/test';\n\ntype ImageHashes = Map<string, Promise<string | undefined>>;\n\n// Matches the default timeout of the `page.request` fetch this replaced.\nconst IMAGE_HASH_TIMEOUT_MS = 30_000;\n\nconst hashesByPage = new WeakMap<Page, ImageHashes>();\n\n/**\n * The latest hash per url across all of a context's pages. The HTTP cache is\n * shared by the context, so this is the copy a 304 tells any of them to reuse.\n */\nconst hashesByContext = new WeakMap<BrowserContext, ImageHashes>();\n\nexport function sha256(data: string | Buffer): string {\n return createHash('sha256').update(data).digest('hex');\n}\n\n/**\n * Hashes every image the context's pages load, as they load, so a snapshot\n * hashes the bytes the page showed instead of fetching them a second time. A\n * second fetch of a page's images comes as a burst, which hosts such as\n * upload.wikimedia.org answer with 429 for a share of them, and the image then\n * reads as replaced. Only the hash is kept, not the body. Entries persist\n * across navigations until another request for the same image replaces them\n * or the page closes.\n *\n * Watched on the context rather than each page: Playwright reports a popup\n * only once its first navigation has committed, and the images that document\n * requests early are gone by the time a listener can be added to the page.\n */\nexport function watchImageResponses(context: BrowserContext): void {\n if (hashesByContext.has(context)) {\n return;\n }\n\n const latest: ImageHashes = new Map();\n hashesByContext.set(context, latest);\n context.on('request', (request) => {\n // `request.frame()` throws for a service worker's own requests, which\n // belong to no page.\n if (request.resourceType() !== 'image' || request.serviceWorker()) {\n return;\n }\n\n const hashes = pageHashes(request.frame().page());\n // `response.body()` throws on a 304, and the page shows the cached copy,\n // which is the latest one any page in this context loaded.\n const cached = latest.get(request.url());\n // Replace the entry as soon as the load starts, including failed loads.\n // Store the promise now so an older response finishing late cannot\n // overwrite a newer request's hash.\n const hash = request\n .response()\n .then(async (response) => {\n if (response?.status() === 304) {\n return cached;\n }\n\n return response?.ok() ? sha256(await response.body()) : undefined;\n })\n .catch(() => undefined);\n // A redirect's own response has no body, and the element still names the\n // url it asked for, so the hash is kept under every url of the chain.\n for (const url of redirectChain(request)) {\n hashes.set(url, hash);\n latest.set(url, hash);\n }\n });\n}\n\nfunction pageHashes(page: Page): ImageHashes {\n const existing = hashesByPage.get(page);\n if (existing) {\n return existing;\n }\n\n const hashes: ImageHashes = new Map();\n hashesByPage.set(page, hashes);\n page.once('close', () => hashesByPage.delete(page));\n return hashes;\n}\n\nfunction redirectChain(request: Request): string[] {\n const urls: string[] = [];\n for (\n let redirected: Request | null = request;\n redirected !== null;\n redirected = redirected.redirectedFrom()\n ) {\n urls.push(redirected.url());\n }\n return urls;\n}\n\n/**\n * The hash from the latest observed request for this URL, across navigations.\n * Undefined when no request was observed, the latest load failed, or it has\n * not finished within the timeout, so an image that never finishes loading\n * cannot stall a snapshot.\n */\nexport async function loadedImageHash(\n page: Page,\n url: string,\n timeoutMs = IMAGE_HASH_TIMEOUT_MS,\n): Promise<string | undefined> {\n const hash = hashesByPage.get(page)?.get(url.split('#')[0]!);\n if (hash === undefined) {\n return undefined;\n }\n\n let timer: NodeJS.Timeout | undefined;\n const timeout = new Promise<undefined>((resolve) => {\n timer = setTimeout(resolve, timeoutMs, undefined);\n });\n try {\n return await Promise.race([hash, timeout]);\n } finally {\n clearTimeout(timer);\n }\n}\n","import type { Page } from '@playwright/test';\n\nimport { loadedImageHash, sha256 } from './image_responses';\nimport type { PageLayout } from './types';\n\n// Mirrors the server's limits. A snapshot past them is left out rather than\n// sent, so the node still uploads, without a snapshot to diff.\nexport const MAX_SNAPSHOT_BYTES = 4 * 1024 * 1024;\nexport const MAX_SNAPSHOT_JSON_DEPTH = 128;\n\n// Each aria level is two JSON levels, the element and its `children` array,\n// and a leaf's `box` and `style` add two more under the element.\nconst MAX_ARIA_DEPTH = Math.floor(MAX_SNAPSHOT_JSON_DEPTH / 2) - 2;\n\nconst STYLE_PROPERTIES = [\n 'font-family',\n 'font-size',\n 'font-weight',\n 'font-style',\n 'line-height',\n 'letter-spacing',\n 'color',\n 'background-color',\n 'text-align',\n 'text-transform',\n];\n// Each with its initial value. Unlike fonts these are not inherited, so a\n// child that repeats its parent's padding really has that padding: they are\n// kept when set at all, not when they differ from the parent's.\nconst UNINHERITED_PROPERTIES: Record<string, string> = {\n 'padding-top': '0px',\n 'padding-right': '0px',\n 'padding-bottom': '0px',\n 'padding-left': '0px',\n 'margin-top': '0px',\n 'margin-right': '0px',\n 'margin-bottom': '0px',\n 'margin-left': '0px',\n 'row-gap': 'normal',\n 'column-gap': 'normal',\n 'justify-content': 'normal',\n 'align-items': 'normal',\n 'flex-direction': 'row',\n 'grid-template-columns': 'none',\n order: '0',\n 'border-width': '0px',\n 'border-color': 'currentcolor',\n 'border-radius': '0px',\n 'box-shadow': 'none',\n opacity: '1',\n 'text-decoration-line': 'none',\n};\n// Anything else takes its size from its text or its parent, which is reported\n// as that change. An image has neither, so its size is the only sign it was\n// resized.\nconst IMAGE_PROPERTIES: Record<string, string> = {\n width: 'auto',\n height: 'auto',\n};\nconst TRANSPARENT = 'rgba(0, 0, 0, 0)';\nconst ELEMENT_LOOKUP_TIMEOUT_MS = 2000;\n// Marks a subtree whose text, links and images the server generates on every\n// request, such as a blog listing or a price quote. The diff compares only its\n// structure and styles.\nconst DYNAMIC_ATTRIBUTE = 'data-ralph-dynamic';\n// Tracking pixels and hidden widget loaders (GTM, Stripe, chat) mount as\n// iframes this small. Nobody sees them, and their content changes on every\n// load.\nconst MIN_IFRAME_SIZE = 5;\n\ntype Box = { x: number; y: number; width: number; height: number };\ntype Offset = Pick<Box, 'x' | 'y'>;\ntype Edges = { left: number; top: number; right: number; bottom: number };\ntype Style = Record<string, string>;\ntype AriaNode = {\n role?: string;\n name?: string;\n ref?: string;\n box?: Box;\n style?: Style;\n imageHash?: string;\n dynamic?: boolean;\n children?: AriaChild[];\n};\ntype AriaChild = AriaNode | string;\ntype ImageSource = { src: string } | { markup: string };\ntype ElementFacts = { style: Style; dynamic: boolean; image?: ImageSource };\n\n/**\n * The page's aria tree as JSON, with each element's box in document\n * coordinates, its distinctive styles, and a hash of its image. Undefined when\n * the snapshot is past the server's limits.\n *\n * The page is not scrolled first, because scrolling changes the page the\n * screenshot just showed: sticky headers, lazy loading, scroll-triggered\n * animation.\n */\nexport async function captureAriaSnapshot(\n page: Page,\n { viewport, scroll }: Pick<PageLayout, 'viewport' | 'scroll'>,\n): Promise<Buffer | undefined> {\n // The default mode drops unnamed `generic` wrappers, so a div-built page\n // comes out flat. `ai` mode keeps them, which preserves the DOM nesting.\n const snapshot = (await page.locator('body').ariaSnapshotJSON({\n mode: 'ai',\n boxes: true,\n depth: MAX_ARIA_DEPTH,\n })) as AriaNode[];\n const placed = await Promise.all(\n snapshot.map((node) => toDocument(page, node, scroll)),\n );\n const clips = await readClips(page, placed);\n const visible = placed.flatMap((node) => {\n const kept = keepShown(node, viewport.width, clips, undefined);\n return kept === undefined ? [] : [kept];\n });\n // Generated refs do not resolve as `aria-ref=` locators, so the elements\n // are described while they still carry only Playwright's refs.\n const described = await Promise.all(\n visible.map((node) => describe(page, node, {}, false)),\n );\n\n const json = Buffer.from(JSON.stringify(fillMissingRefs(described)));\n return json.byteLength > MAX_SNAPSHOT_BYTES ? undefined : json;\n}\n\n/**\n * Moves the viewport boxes Playwright reports into the full-page screenshot's\n * space. This has to happen before `keepShown`, which in viewport space would\n * drop everything scrolled above the viewport, and no later move brings those\n * back.\n *\n * An iframe's descendants are boxed in its own viewport, so they move by\n * where that viewport sits: the iframe's box plus its border and padding.\n */\nasync function toDocument(\n page: Page,\n node: AriaNode,\n offset: Offset,\n): Promise<AriaNode> {\n const box = node.box && {\n ...node.box,\n x: node.box.x + offset.x,\n y: node.box.y + offset.y,\n };\n let childOffset = offset;\n if (node.role === 'iframe' && box !== undefined) {\n const inset = (node.ref === undefined\n ? undefined\n : await readContentInset(page, node.ref)) ?? { x: 0, y: 0 };\n childOffset = { x: box.x + inset.x, y: box.y + inset.y };\n }\n return {\n ...node,\n ...(box && { box }),\n ...(node.children && {\n children: await Promise.all(\n node.children.map((child) =>\n typeof child === 'string'\n ? child\n : toDocument(page, child, childOffset),\n ),\n ),\n }),\n };\n}\n\n// The iframe's viewport starts inside its border and padding.\nasync function readContentInset(\n page: Page,\n ref: string,\n): Promise<Offset | undefined> {\n try {\n return await page.locator(`aria-ref=${ref}`).evaluate(\n (element) => {\n const computed = getComputedStyle(element);\n return {\n x: element.clientLeft + parseFloat(computed.paddingLeft),\n y: element.clientTop + parseFloat(computed.paddingTop),\n };\n },\n undefined,\n { timeout: ELEMENT_LOOKUP_TIMEOUT_MS },\n );\n } catch {\n return undefined;\n }\n}\n\n/**\n * In document space, the only boxes no scroll reveals are those past the left\n * or right of the viewport's width (a collapsed sidebar, an off-canvas menu)\n * or above the top of the document.\n */\nfunction isReachable(box: Box | undefined, viewportWidth: number): boolean {\n if (box === undefined) {\n return false;\n }\n return (\n box.x < viewportWidth && box.x + box.width > 0 && box.y + box.height > 0\n );\n}\n\n// `ai` mode gives a ref only to an element with a non-empty box that receives\n// pointer events, so a missing ref marks a zero-size, covered or\n// `pointer-events: none` element.\nfunction isShown(node: AriaNode, viewportWidth: number): boolean {\n return node.ref !== undefined && isReachable(node.box, viewportWidth);\n}\n\nfunction edgesOf(box: Box): Edges {\n return {\n left: box.x,\n top: box.y,\n right: box.x + box.width,\n bottom: box.y + box.height,\n };\n}\n\nfunction intersect(a: Edges | undefined, b: Edges): Edges {\n if (a === undefined) {\n return b;\n }\n return {\n left: Math.max(a.left, b.left),\n top: Math.max(a.top, b.top),\n right: Math.min(a.right, b.right),\n bottom: Math.min(a.bottom, b.bottom),\n };\n}\n\n// Playwright rounds every box to whole pixels, so two rows that only touch\n// can read as a pixel apart or a pixel overlapping.\nconst ROUNDING_SLACK = 1;\n\nfunction sticksOut(inner: Box, outer: Box): boolean {\n return (\n inner.x < outer.x - ROUNDING_SLACK ||\n inner.y < outer.y - ROUNDING_SLACK ||\n inner.x + inner.width > outer.x + outer.width + ROUNDING_SLACK ||\n inner.y + inner.height > outer.y + outer.height + ROUNDING_SLACK\n );\n}\n\nfunction overlaps(a: Box, b: Box): boolean {\n return (\n Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x) >\n ROUNDING_SLACK &&\n Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y) >\n ROUNDING_SLACK\n );\n}\n\nfunction isOutside(box: Box, clip: Edges): boolean {\n return (\n box.x >= clip.right ||\n box.x + box.width <= clip.left ||\n box.y >= clip.bottom ||\n box.y + box.height <= clip.top\n );\n}\n\n/**\n * The area each element is clipped to by its ancestors' `overflow`, in\n * document space, by ref.\n *\n * The boxes are not clipped (the rows of a 448px scroll box run thousands of\n * pixels down the page), and reading an element's ancestors is a page round\n * trip. Only an element that may overflow one is read: a root, one sticking\n * out of its parent, or one running into its next sibling. The clipping\n * ancestor itself is often missing from the tree, since Playwright unwraps a\n * nameless wrapper with one child. Every other element takes its parent's\n * clip.\n */\nasync function readClips(\n page: Page,\n nodes: AriaNode[],\n): Promise<Map<string, Edges>> {\n const candidates: { node: AriaNode; frame: Edges | undefined }[] = [];\n const visit = (\n node: AriaNode,\n parent: Box | undefined,\n next: AriaNode | undefined,\n frame: Edges | undefined,\n ) => {\n if (\n node.box !== undefined &&\n (parent === undefined ||\n sticksOut(node.box, parent) ||\n (next?.box !== undefined && overlaps(node.box, next.box)))\n ) {\n candidates.push({ node, frame });\n }\n\n const childFrame =\n node.role === 'iframe' && node.box !== undefined\n ? intersect(frame, edgesOf(node.box))\n : frame;\n const elements = (node.children ?? []).filter((child) => {\n return typeof child !== 'string';\n });\n elements.forEach((child, index) => {\n visit(child, node.box, elements[index + 1], childFrame);\n });\n };\n nodes.forEach((node, index) => {\n visit(node, undefined, nodes[index + 1], undefined);\n });\n\n const clips = await Promise.all(\n candidates.map(async ({ node, frame }): Promise<[string, Edges][]> => {\n const clip = await readClip(page, node);\n return clip === undefined || node.ref === undefined\n ? []\n : [[node.ref, intersect(frame, clip)]];\n }),\n );\n return new Map(clips.flat());\n}\n\nasync function readClip(\n page: Page,\n node: AriaNode,\n): Promise<Edges | undefined> {\n if (node.box === undefined || node.ref === undefined) {\n return undefined;\n }\n\n let read;\n try {\n read = await page.locator(`aria-ref=${node.ref}`).evaluate(\n (element) => {\n const clip = {\n left: -Infinity,\n top: -Infinity,\n right: Infinity,\n bottom: Infinity,\n };\n let position = getComputedStyle(element).position;\n // <body> and <html> pass their overflow to the viewport, which the\n // full-page screenshot does not clip.\n for (\n let ancestor = element.parentElement;\n ancestor !== null &&\n ancestor !== document.body &&\n ancestor !== document.documentElement;\n ancestor = ancestor.parentElement\n ) {\n if (position === 'fixed') {\n break;\n }\n\n const style = getComputedStyle(ancestor);\n // An absolute element escapes every ancestor below its containing\n // block, overflow included.\n if (\n position === 'absolute' &&\n style.position === 'static' &&\n style.transform === 'none'\n ) {\n continue;\n }\n\n position = style.position;\n const clipsX = style.overflowX !== 'visible';\n const clipsY = style.overflowY !== 'visible';\n // `overflow` does not apply to an inline box, whose client size is 0\n // and would otherwise clip everything inside it.\n if ((!clipsX && !clipsY) || style.display === 'inline') {\n continue;\n }\n\n // Content is clipped at the padding edge, inside the border and any\n // scrollbar.\n const rect = ancestor.getBoundingClientRect();\n const left = rect.left + ancestor.clientLeft;\n const top = rect.top + ancestor.clientTop;\n if (clipsX) {\n clip.left = Math.max(clip.left, left);\n clip.right = Math.min(clip.right, left + ancestor.clientWidth);\n }\n\n if (clipsY) {\n clip.top = Math.max(clip.top, top);\n clip.bottom = Math.min(clip.bottom, top + ancestor.clientHeight);\n }\n }\n const own = element.getBoundingClientRect();\n return { x: own.x, y: own.y, clip };\n },\n undefined,\n { timeout: ELEMENT_LOOKUP_TIMEOUT_MS },\n );\n } catch {\n return undefined;\n }\n\n // The page measures in its frame's viewport. The element's own box gives the\n // offset from there to document space, so iframes and scroll need no other\n // correction.\n const dx = node.box.x - read.x;\n const dy = node.box.y - read.y;\n return {\n left: read.clip.left + dx,\n top: read.clip.top + dy,\n right: read.clip.right + dx,\n bottom: read.clip.bottom + dy,\n };\n}\n\n// A positioned child can sit on screen while its wrapper's box does not, so a\n// node is kept when it or any descendant is shown. One clipped out entirely\n// is dropped with its descendants without walking them, because the ancestor\n// that clips it clips them too.\nfunction keepShown(\n node: AriaNode,\n viewportWidth: number,\n clips: Map<string, Edges>,\n inherited: Edges | undefined,\n): AriaNode | undefined {\n if (\n node.role === 'iframe' &&\n node.box !== undefined &&\n (node.box.width < MIN_IFRAME_SIZE || node.box.height < MIN_IFRAME_SIZE)\n ) {\n return undefined;\n }\n\n const clip =\n (node.ref === undefined ? undefined : clips.get(node.ref)) ?? inherited;\n if (\n clip !== undefined &&\n node.box !== undefined &&\n isOutside(node.box, clip)\n ) {\n return undefined;\n }\n\n const shown = isShown(node, viewportWidth);\n const childClip =\n node.role === 'iframe' && node.box !== undefined\n ? intersect(clip, edgesOf(node.box))\n : clip;\n const children = (node.children ?? []).flatMap((child): AriaChild[] => {\n if (typeof child === 'string') {\n return shown ? [child] : [];\n }\n const kept = keepShown(child, viewportWidth, clips, childClip);\n return kept === undefined ? [] : [kept];\n });\n if (!shown && children.length === 0) {\n return undefined;\n }\n\n // Playwright unwraps a nameless generic with one child, but counts children\n // before the hidden ones are dropped here. A hidden sibling that comes and\n // goes, such as the 1px `alert` Next.js mounts in <body> once it hydrates,\n // would otherwise decide whether everything under the wrapper sits one level\n // deeper, and the diff then matches none of it across two captures.\n const [only] = children;\n if (\n node.role === 'generic' &&\n !node.name &&\n (node.children?.length ?? 0) > 1 &&\n children.length === 1 &&\n typeof only !== 'string' &&\n only?.ref !== undefined\n ) {\n return only;\n }\n\n return node.children === undefined ? node : { ...node, children };\n}\n\n// A wrapper kept only for its shown descendants has no Playwright ref, but\n// every element needs an id to be diffed. The `n` prefix cannot collide with\n// Playwright's `e14` / `f1e3` refs.\nfunction fillMissingRefs(nodes: AriaChild[]): AriaChild[] {\n let generated = 0;\n const fill = (child: AriaChild): AriaChild => {\n if (typeof child === 'string') {\n return child;\n }\n generated += 1;\n const ref = child.ref ?? `n${generated}`;\n return child.children === undefined\n ? { ...child, ref }\n : { ...child, ref, children: child.children.map(fill) };\n };\n return nodes.map(fill);\n}\n\nasync function readElement(\n page: Page,\n ref: string,\n isImage: boolean,\n): Promise<ElementFacts | undefined> {\n try {\n return await page.locator(`aria-ref=${ref}`).evaluate(\n (element, { properties, isImage, dynamicAttribute }) => {\n const computed = getComputedStyle(element);\n // `getComputedStyle` resolves an `auto` margin to its used length, and\n // Chromium reports that length inconsistently: the same centered\n // element, laid out identically, reads `0px` on one load and `40px` on\n // the next. The typed computed value keeps it `auto`. Firefox has no\n // `computedStyleMap`.\n const typed =\n 'computedStyleMap' in element\n ? element.computedStyleMap()\n : undefined;\n const style = Object.fromEntries(\n properties.map((property) => [\n property,\n property.startsWith('margin-') &&\n typed?.get(property)?.toString() === 'auto'\n ? 'auto'\n : computed.getPropertyValue(property),\n ]),\n );\n // `closest`, because the marked element need not be in the aria\n // tree: a plain wrapper `div` around a card often is not.\n const dynamic = element.closest(`[${dynamicAttribute}]`) !== null;\n\n if (!isImage) {\n return { style, dynamic };\n }\n\n if (element instanceof HTMLImageElement) {\n return {\n style,\n dynamic,\n image: { src: element.currentSrc || element.src },\n };\n }\n\n if (element instanceof SVGElement) {\n return { style, dynamic, image: { markup: element.outerHTML } };\n }\n\n const backgroundUrl = /url\\(\"(.+)\"\\)/.exec(\n computed.backgroundImage,\n )?.[1];\n return backgroundUrl === undefined\n ? { style, dynamic }\n : {\n style,\n dynamic,\n image: { src: new URL(backgroundUrl, document.baseURI).href },\n };\n },\n {\n properties: [\n ...STYLE_PROPERTIES,\n ...Object.keys(UNINHERITED_PROPERTIES),\n ...(isImage ? Object.keys(IMAGE_PROPERTIES) : []),\n ],\n isImage,\n dynamicAttribute: DYNAMIC_ATTRIBUTE,\n },\n { timeout: ELEMENT_LOOKUP_TIMEOUT_MS },\n );\n } catch {\n return undefined;\n }\n}\n\n// The bytes are hashed rather than the url, so the same image moved to a new\n// CDN path is not a change.\nasync function hashImage(\n page: Page,\n image: ImageSource,\n): Promise<string | undefined> {\n if ('markup' in image) {\n return sha256(image.markup);\n }\n\n if (image.src === '') {\n return undefined;\n }\n\n if (image.src.startsWith('data:')) {\n return sha256(image.src);\n }\n\n return loadedImageHash(page, image.src);\n}\n\n// Fonts and color are inherited, so recording them on every element would\n// turn one change on the body into a change on every element. Only what an\n// element sets differently from its parent is kept, except the uninherited and\n// image properties.\nfunction distinctStyle(style: Style, inherited: Style): Style | undefined {\n const distinct = Object.entries(style).filter(([property, value]) => {\n // Computed, `currentcolor` is the text color, so without a border it\n // would record every element's color a second time.\n if (property === 'border-color') {\n return style['border-width'] !== '0px';\n }\n\n const initial =\n UNINHERITED_PROPERTIES[property] ?? IMAGE_PROPERTIES[property];\n if (initial !== undefined) {\n return value !== initial;\n }\n\n if (property === 'background-color' && value === TRANSPARENT) {\n return false;\n }\n return inherited[property] !== value;\n });\n return distinct.length === 0 ? undefined : Object.fromEntries(distinct);\n}\n\n// `dynamic` is recorded only at the top of a marked subtree, and the diff\n// applies it to everything below, which keeps it off every descendant.\nasync function describe(\n page: Page,\n child: AriaChild,\n inherited: Style,\n insideDynamic: boolean,\n): Promise<AriaChild> {\n if (typeof child === 'string') {\n return child;\n }\n\n const facts =\n child.ref === undefined\n ? undefined\n : await readElement(page, child.ref, child.role === 'img');\n const style =\n facts === undefined ? undefined : distinctStyle(facts.style, inherited);\n const imageHash =\n facts?.image === undefined ? undefined : await hashImage(page, facts.image);\n const dynamic = facts?.dynamic === true && !insideDynamic;\n const children =\n child.children === undefined\n ? undefined\n : await Promise.all(\n child.children.map((grandchild) =>\n describe(\n page,\n grandchild,\n facts?.style ?? inherited,\n insideDynamic || dynamic,\n ),\n ),\n );\n\n return {\n ...child,\n ...(style && { style }),\n ...(imageHash && { imageHash }),\n ...(dynamic && { dynamic }),\n ...(children && { children }),\n };\n}\n","/** Runs in the page. Keeps native animations and DOM changes observable without app hooks. */\nexport function observeAnimations() {\n const paused = new Set<Animation>();\n const roots = new Set<Document | ShadowRoot>();\n let dirty = true;\n const observer = new MutationObserver(() => {\n dirty = true;\n });\n\n const collect = (root: Document | ShadowRoot, all: Set<Animation>) => {\n if (!roots.has(root)) {\n roots.add(root);\n observer.observe(root, {\n subtree: true,\n childList: true,\n attributes: true,\n characterData: true,\n });\n dirty = true;\n }\n root.getAnimations().forEach((animation) => all.add(animation));\n for (const element of root.querySelectorAll('*')) {\n if (element.shadowRoot) {\n collect(element.shadowRoot, all);\n }\n }\n };\n\n return {\n sample(finishFinite: boolean): boolean {\n const all = new Set<Animation>();\n collect(document, all);\n let changed = dirty;\n dirty = false;\n\n for (const animation of all) {\n // Preserve animations the app deliberately paused.\n if (animation.playState !== 'running') {\n continue;\n }\n if (\n finishFinite &&\n animation.playbackRate !== 0 &&\n Number.isFinite(animation.effect?.getComputedTiming().endTime)\n ) {\n animation.finish();\n } else {\n animation.pause();\n paused.add(animation);\n }\n changed = true;\n }\n return changed;\n },\n async ready(): Promise<void> {\n await Promise.all([...paused].map((animation) => animation.ready));\n },\n resume(): void {\n observer.disconnect();\n for (const animation of paused) {\n // Do not restart an animation the app canceled meanwhile.\n if (animation.playState === 'paused') {\n animation.play();\n }\n }\n },\n };\n}\n\nexport type AnimationObserver = ReturnType<typeof observeAnimations>;\n","import { setTimeout as delay } from 'node:timers/promises';\n\nimport type { BrowserContext, Frame, JSHandle, Page } from '@playwright/test';\n\nimport {\n observeAnimations,\n type AnimationObserver,\n} from './animation_observer';\n\n// pauseAt accepts an absolute future time, not \"pause now\". Leave room for\n// the command to reach the browser. Use page time so repeated captures do not\n// fast-forward through the wall-clock time spent on earlier captures.\nconst PAUSE_LEAD_MS = 100;\nconst ANIMATION_QUIET_MS = 150;\nconst MAX_ANIMATION_SETTLE_MS = 1500;\n\ntype AnimationObservers = Map<Frame, JSHandle<AnimationObserver>>;\n\n/** One owner of the context-wide clock; captures in different tabs queue here. */\nexport class CaptureFreezer {\n private tail: Promise<unknown> = Promise.resolve();\n private closed = false;\n\n constructor(private readonly context: BrowserContext) {\n context.once('close', () => {\n this.closed = true;\n });\n }\n\n install(): Promise<void> {\n if (this.context.pages().some((page) => page.url() !== 'about:blank')) {\n throw new Error(\n 'Ralph freezeDuringCapture requires the clock before page scripts run. Await ralph.observe(context) before creating or navigating pages in a custom context.',\n );\n }\n return this.context.clock.install();\n }\n\n async capture<T>(\n page: Page,\n signal: AbortSignal,\n capture: () => Promise<T>,\n deadline = Date.now() + 5000,\n ): Promise<T> {\n const task = this.tail.then(async () => {\n signal.throwIfAborted();\n const animations: AnimationObservers = new Map();\n // Do not race these mutations against a timeout: a late pause could\n // otherwise complete after cleanup and leave the context frozen.\n try {\n await this.settleAnimations(page, animations, signal, deadline);\n await this.pauseClock(page, signal);\n signal.throwIfAborted();\n // Once time is frozen, only pause: finishing here could start new JS\n // work whose animation frames cannot run until after the screenshot.\n // Recheck after pause readiness for newly started native animations.\n for (let pass = 0; pass < 2; pass++) {\n await this.sampleAnimations(page, animations, signal, false);\n await this.waitForAnimationsReady(animations, signal);\n }\n return await capture();\n } finally {\n try {\n await this.resumeAnimations(animations);\n } finally {\n if (!this.closed) {\n await this.context.clock.resume();\n }\n }\n }\n });\n\n // A failed or expired capture must not poison the next tab's turn.\n this.tail = task.catch(() => undefined);\n return task;\n }\n\n private async settleAnimations(\n page: Page,\n animations: AnimationObservers,\n signal: AbortSignal,\n deadline: number,\n ): Promise<void> {\n const now = Date.now();\n // Leave at least half the remaining recording budget for capture. DOM\n // updates from looping JS animations must not consume the whole deadline.\n const endsAt =\n now +\n Math.min(MAX_ANIMATION_SETTLE_MS, Math.max(0, (deadline - now) / 2));\n let quietSince = now;\n while (Date.now() < endsAt) {\n if (await this.sampleAnimations(page, animations, signal, true)) {\n quietSince = Date.now();\n }\n if (Date.now() - quietSince >= ANIMATION_QUIET_MS) {\n return;\n }\n // The clock is still running. This gives completion callbacks, chained\n // animations and JS/rAF style updates a chance to reach their end state.\n await delay(Math.max(0, Math.min(50, endsAt - Date.now())), undefined, {\n signal,\n }).catch((error: unknown) => {\n signal.throwIfAborted();\n throw error;\n });\n }\n }\n\n private async sampleAnimations(\n page: Page,\n animations: AnimationObservers,\n signal: AbortSignal,\n finishFinite: boolean,\n ): Promise<boolean> {\n let changed = false;\n for (const frame of page.frames()) {\n signal.throwIfAborted();\n try {\n let handle = animations.get(frame);\n if (!handle) {\n handle = await frame.evaluateHandle(observeAnimations);\n animations.set(frame, handle);\n }\n changed =\n (await handle.evaluate(\n (state, finish) => state.sample(finish),\n finishFinite,\n )) || changed;\n } catch (error) {\n // Completion handlers can remove an iframe while it is settling.\n if (!frame.isDetached()) {\n throw error;\n }\n }\n }\n signal.throwIfAborted();\n return changed;\n }\n\n private async waitForAnimationsReady(\n animations: AnimationObservers,\n signal: AbortSignal,\n ): Promise<void> {\n signal.throwIfAborted();\n let onAbort!: () => void;\n const aborted = new Promise<never>((_, reject) => {\n onAbort = () => reject(signal.reason);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n try {\n // pause() may finish on a later rendering frame. Wait before measuring,\n // keeping the handles available for cleanup if the deadline interrupts.\n // This only waits; it cannot perform a late pause after cleanup.\n await Promise.race([\n Promise.all(\n [...animations].map(async ([frame, handle]) => {\n if (frame.isDetached()) {\n return;\n }\n try {\n await handle.evaluate((state) => state.ready());\n } catch (error) {\n if (!frame.isDetached()) {\n throw error;\n }\n }\n }),\n ),\n aborted,\n ]);\n } finally {\n signal.removeEventListener('abort', onAbort);\n }\n }\n\n private async resumeAnimations(\n animations: AnimationObservers,\n ): Promise<void> {\n const restored = await Promise.allSettled(\n [...animations.values()].map(async (handle) => {\n try {\n await handle.evaluate((state) => state.resume());\n } catch (error) {\n // Navigating or closing destroys these animations with their document.\n // Other failures must not silently leave them paused.\n if (!destroyedContext(error)) {\n throw error;\n }\n } finally {\n await handle.dispose();\n }\n }),\n );\n for (const result of restored) {\n if (result.status === 'rejected') {\n throw result.reason;\n }\n }\n }\n\n private async pauseClock(page: Page, signal: AbortSignal): Promise<void> {\n for (let attempt = 0; ; attempt++) {\n const time = await page.evaluate(() => Date.now());\n signal.throwIfAborted();\n try {\n await this.context.clock.pauseAt(time + PAUSE_LEAD_MS);\n return;\n } catch (error) {\n // A slow transport can consume the lead time. Resample once; cleanup\n // still resumes every frame if either command only partially succeeds.\n if (\n attempt > 0 ||\n !(error instanceof Error) ||\n !error.message.includes('Cannot fast-forward to the past')\n ) {\n throw error;\n }\n }\n }\n }\n}\n\nfunction destroyedContext(error: unknown): boolean {\n return (\n error instanceof Error &&\n /Execution context was destroyed|Cannot find context|Target page, context or browser has been closed|Frame was detached/.test(\n error.message,\n )\n );\n}\n","import type {\n ConditionsDeclaration,\n NodeConditions,\n RecordedTags,\n TagConditions,\n TagDeclaration,\n} from './types';\n\nconst declared = (declaration: TagDeclaration = {}): TagConditions => {\n const conditions: TagConditions = {};\n for (const [key, value] of Object.entries(declaration)) {\n if (value === undefined) {\n continue;\n }\n const values = Array.isArray(value) ? value : [value];\n const fractional = values.find(\n (item) => typeof item === 'number' && !Number.isSafeInteger(item),\n );\n if (fractional !== undefined) {\n throw new Error(\n `Tag \"${key}\" is declared as ${fractional}: a tag number must be an integer, since the tracker drops any other.`,\n );\n }\n conditions[key] = [...new Set(values.map((item) => item ?? null))];\n }\n return conditions;\n};\n\n/** A declaration as sets: a scalar is a one-element set, `undefined` is `null`. */\nexport function declaredConditions(\n declaration: ConditionsDeclaration,\n): NodeConditions {\n return {\n visitorTags: declared(declaration.visitorTags),\n pageTags: declared(declaration.pageTags),\n };\n}\n\n/** Later declarations win per key, as later `tagPage` calls do in the app. */\nexport function mergeConditions(layers: NodeConditions[]): NodeConditions {\n return {\n visitorTags: Object.assign({}, ...layers.map((layer) => layer.visitorTags)),\n pageTags: Object.assign({}, ...layers.map((layer) => layer.pageTags)),\n };\n}\n\n/**\n * Declared keys the app's tracker disagrees with, as `visitorTags.plan`. A\n * node's conditions come from the test alone, so this is the only check that\n * production events will carry the tags its conditions name: a key the app\n * never sets, or sets under another name, leaves the node without traffic.\n */\nexport function mismatchesOf(\n declared: NodeConditions,\n reported: RecordedTags | undefined,\n): string[] {\n if (reported === undefined) {\n return [];\n }\n const mismatches: string[] = [];\n for (const kind of ['visitorTags', 'pageTags'] as const) {\n for (const [key, allowed] of Object.entries(declared[kind] ?? {})) {\n if (!allowed.includes(reported[kind][key] ?? null)) {\n mismatches.push(`${kind}.${key}`);\n }\n }\n }\n return mismatches.sort();\n}\n\n/** Absent kinds are left out, so an unconditional node has no conditions. */\nexport function compactConditions(\n conditions: NodeConditions,\n): NodeConditions | undefined {\n const compact: NodeConditions = {};\n for (const kind of ['visitorTags', 'pageTags'] as const) {\n if (Object.keys(conditions[kind] ?? {}).length > 0) {\n compact[kind] = conditions[kind];\n }\n }\n return Object.keys(compact).length === 0 ? undefined : compact;\n}\n\n/**\n * The node a state belongs to, as a string, whatever the order of keys or\n * values. Equal keys are one node, so a tag change that leaves this alone\n * records nothing.\n */\nexport function nodeKey(conditions: NodeConditions): string {\n const sorted = (tags: TagConditions = {}) =>\n Object.entries(tags)\n .map(\n ([key, values]) =>\n [key, values.map((value) => JSON.stringify(value)).sort()] as const,\n )\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n return JSON.stringify([\n sorted(conditions.visitorTags),\n sorted(conditions.pageTags),\n ]);\n}\n","import type { Page } from '@playwright/test';\n\n/**\n * Matches the crawler's full-page screenshots: lossless measured larger than this\n * on real sites, which are mostly photographic content.\n */\nconst WEBP_QUALITY = 80;\nconst WEBP_MAX_DIMENSION = 16_383;\n\nexport type Screenshot = {\n bytes: Buffer;\n contentType: 'image/webp' | 'image/png';\n width: number;\n height: number;\n};\n\nexport async function takeScreenshot(\n page: Page,\n deadline: number,\n frozen = false,\n): Promise<Screenshot> {\n // When capture already paused animations, preserve that same position for\n // the layout and aria snapshot. Otherwise use Playwright's screenshot-only\n // animation suppression as before.\n const options = {\n fullPage: true,\n scale: 'css',\n animations: frozen ? 'allow' : 'disabled',\n } as const;\n const webp = await page.screenshot({\n ...options,\n type: 'webp',\n quality: WEBP_QUALITY,\n timeout: Math.max(1, deadline - Date.now()),\n });\n if (webp.length > 0) {\n return { bytes: webp, contentType: 'image/webp', ...webpSize(webp) };\n }\n\n // WebP stores each dimension in 14 bits, so a page over 16383px has no valid\n // encoding, and Chromium signals that with an empty buffer rather than an\n // error. The page is clipped to what fits rather than scaled down, because\n // the layout's boxes and the viewer's clicks are CSS pixels from the page's\n // top left, and a scaled screenshot would misplace every one of them. What\n // lies past the clip has no image.\n const documentSize = await page.evaluate(() => {\n return {\n width: document.documentElement.scrollWidth,\n height: document.documentElement.scrollHeight,\n };\n });\n const clipped = await page.screenshot({\n ...options,\n type: 'webp',\n quality: WEBP_QUALITY,\n clip: {\n x: 0,\n y: 0,\n width: Math.min(documentSize.width, WEBP_MAX_DIMENSION),\n height: Math.min(documentSize.height, WEBP_MAX_DIMENSION),\n },\n timeout: Math.max(1, deadline - Date.now()),\n });\n if (clipped.length > 0) {\n return { bytes: clipped, contentType: 'image/webp', ...webpSize(clipped) };\n }\n\n const png = await page.screenshot({\n ...options,\n type: 'png',\n timeout: Math.max(1, deadline - Date.now()),\n });\n return {\n bytes: png,\n contentType: 'image/png',\n width: png.readUInt32BE(16),\n height: png.readUInt32BE(20),\n };\n}\n\nexport function imageExtension(contentType: Screenshot['contentType']): string {\n return contentType === 'image/png' ? '.png' : '.webp';\n}\n\nfunction webpSize(bytes: Buffer): { width: number; height: number } {\n if (\n bytes.toString('ascii', 0, 4) !== 'RIFF' ||\n bytes.toString('ascii', 8, 12) !== 'WEBP'\n ) {\n throw new Error('The screenshot is not a WebP image.');\n }\n const chunk = bytes.toString('ascii', 12, 16);\n if (chunk === 'VP8X') {\n return {\n width: 1 + bytes.readUIntLE(24, 3),\n height: 1 + bytes.readUIntLE(27, 3),\n };\n }\n if (chunk === 'VP8 ') {\n return {\n width: bytes.readUInt16LE(26) & 0x3fff,\n height: bytes.readUInt16LE(28) & 0x3fff,\n };\n }\n if (chunk === 'VP8L') {\n const bits = bytes.readUInt32LE(21);\n return { width: 1 + (bits & 0x3fff), height: 1 + ((bits >> 14) & 0x3fff) };\n }\n throw new Error('Unsupported WebP chunk: ' + chunk);\n}\n","import type { Page } from '@playwright/test';\n\nimport type { PageLayout } from './types';\n\nexport function readLayout(page: Page): Promise<PageLayout> {\n return page\n .locator('[data-track-id]')\n .filter({ visible: true })\n .evaluateAll((elements) => ({\n viewport: { width: window.innerWidth, height: window.innerHeight },\n document: {\n width: Math.max(\n document.documentElement.scrollWidth,\n document.documentElement.clientWidth,\n document.body?.scrollWidth ?? 0,\n ),\n height: Math.max(\n document.documentElement.scrollHeight,\n document.documentElement.clientHeight,\n document.body?.scrollHeight ?? 0,\n ),\n },\n scroll: { x: window.scrollX, y: window.scrollY },\n devicePixelRatio: window.devicePixelRatio,\n trackedElements: elements.flatMap((element) => {\n const trackId = element.getAttribute('data-track-id');\n const rect = element.getBoundingClientRect();\n // Off-screen horizontally (drawers, carousel slides) is dropped like\n // the crawler does; below the fold is kept, since the screenshot is\n // full-page.\n if (\n !trackId ||\n rect.width <= 0 ||\n rect.height <= 0 ||\n rect.right <= 0 ||\n rect.left >= window.innerWidth\n ) {\n return [];\n }\n return [\n {\n trackId,\n x: rect.x + window.scrollX,\n y: rect.y + window.scrollY,\n width: rect.width,\n height: rect.height,\n },\n ];\n }),\n }));\n}\n","import type { Locator, Page } from '@playwright/test';\n\nimport type { VisitAllLinksOptions } from './types';\n\n/**\n * Opens each link in a popup of the page it is on. A popup's first node links\n * to its opener's latest, so every target links from that page. Following the\n * links in one tab would link each target to the one before it, and a\n * modifier click opens a tab without an opener, which links to nothing.\n * `rel=\"opener\"` keeps the opener that `target=\"_blank\"` alone would drop.\n */\nexport const visitAllLinks = async (\n links: Locator,\n waitForCapture: () => Promise<void>,\n options: VisitAllLinksOptions = {},\n): Promise<void> => {\n await links.first().waitFor();\n const all = await links.all();\n\n // Checked before opening any, so a failure leaves no partial walk behind.\n const notLinks = (await Promise.all(all.map(describeIfNotLink))).filter(\n (tag) => tag !== undefined,\n );\n if (notLinks.length > 0) {\n throw new Error(\n 'visitAllLinks opens each match in a new tab, so each must be a link (<a href>). Not links: ' +\n notLinks.join(', '),\n );\n }\n\n for (const link of all) {\n const opened = await openInPopup(link);\n await opened.waitForLoadState('domcontentloaded');\n await options.onVisit?.(opened);\n await waitForCapture();\n await opened.close();\n }\n};\n\nconst describeIfNotLink = (link: Locator): Promise<string | undefined> =>\n link.evaluate((element) => {\n if (element instanceof HTMLAnchorElement && element.hasAttribute('href')) {\n return undefined;\n }\n return element.outerHTML.slice(0, element.outerHTML.indexOf('>') + 1);\n });\n\nconst openInPopup = async (link: Locator): Promise<Page> => {\n const original = await link.evaluate((element) => {\n const attributes = {\n target: element.getAttribute('target'),\n rel: element.getAttribute('rel'),\n };\n\n // Instead of ctrl+click, we inject the target and rel manually because the\n // browser does not pass the opener when clicked open in a new tab.\n element.setAttribute('target', '_blank');\n element.setAttribute('rel', 'opener');\n\n return attributes;\n });\n\n try {\n const [popup] = await Promise.all([\n link.page().waitForEvent('popup'),\n link.click(),\n ]);\n return popup;\n } finally {\n // Restored so a later step that clicks the link opens it as the page would.\n // The click may have re-rendered it away, leaving nothing to restore.\n await link\n .evaluate((element, attributes) => {\n for (const [name, value] of Object.entries(attributes)) {\n if (value === null) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value);\n }\n }\n }, original)\n .catch(() => undefined);\n }\n};\n","import { randomUUID } from 'node:crypto';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { setTimeout as delay } from 'node:timers/promises';\n\nimport type {\n BrowserContext,\n Frame,\n Locator,\n Page,\n TestInfo,\n} from '@playwright/test';\nimport playwrightPackage from '@playwright/test/package.json' with { type: 'json' };\n\nimport packageInfo from '../package.json';\nimport { ActionLog } from './actions';\nimport { waitForAnimations } from './animations';\nimport { captureAriaSnapshot } from './aria_snapshot';\nimport { CaptureFreezer } from './capture_freezer';\nimport {\n compactConditions,\n declaredConditions,\n mergeConditions,\n mismatchesOf,\n nodeKey,\n} from './conditions';\nimport { imageExtension, takeScreenshot, type Screenshot } from './image';\nimport { watchImageResponses } from './image_responses';\nimport { readLayout } from './layout';\nimport type {\n RawNode,\n RawPage,\n RawGraphFile,\n ConfigSnapshot,\n ConditionsDeclaration,\n NodeConditions,\n RecordedTags,\n RecordNodeOptions,\n Ralph,\n RalphOptions,\n VisitAllLinksOptions,\n} from './types';\nimport { visitAllLinks } from './visit_links';\n\n/** The attachment holding a test's `RawGraphFile`, which the reporter merges. */\nexport const MANIFEST_ATTACHMENT = 'ralph-raw-graph';\n\n/**\n * Set by the reporter in the main process before any worker starts, so the\n * fixtures can refuse to upload without it.\n */\nexport const REPORTER_ENV = 'RALPH_PLAYWRIGHT_REPORTER';\n\n/** Prefix of the attachment holding each recorded node's screenshot; the node id follows. */\nexport const SCREENSHOT_ATTACHMENT_PREFIX = 'ralph-screenshot-';\n\n/** Prefix of the attachment holding each recorded node's aria snapshot; the node id follows. */\nexport const ARIA_SNAPSHOT_ATTACHMENT_PREFIX = 'ralph-aria-snapshot-';\n\n/** Once per worker: a run without the tracker says so once, not per node. */\nlet warnedNoTracker = false;\n\nconst DEFAULT_SETTLE_MS = 150;\nconst DEFAULT_RECORD_TIMEOUT_MS = 5_000;\n\n/** The actions kept on one edge, the latest ones, so a long setup does not bloat the graph. */\nconst MAX_ACTIONS_PER_EDGE = 100;\n\n/** Where the raw graph and screenshots are written, inside Playwright's per-test output directory. */\nconst OUTPUT_DIR = 'ralph';\n\ntype PageInfo = RawPage;\n\ntype CreateNodeRequest = Omit<RawNode, 'status'>;\n\ntype RecordedNode = Extract<RawNode, { status: 'recorded' }>;\n\n/** What the recorder tracks for one browser tab. */\ntype PageState = {\n page: Page;\n info: PageInfo;\n\n /** The URL of the last navigation, so a reload is not recorded again. */\n lastUrl: string;\n\n /**\n * Bumped on every main-frame navigation, including reloads. A recording that\n * sees a different generation when it finishes was overtaken by one.\n */\n generation: number;\n\n /** The next node's position within this tab. */\n sequence: number;\n\n /**\n * The test's `setNodeConditions` declarations for the current URL, one layer per call\n * so each `untag` removes its own. Cleared when the URL changes.\n */\n declared: Map<symbol, NodeConditions>;\n\n /** The last node recorded here, so a declaration that leaves the page on it records nothing. */\n lastRecorded?: { url: string; node: string; nodeId: string };\n\n /** The last queued recording. Recordings in a tab run one at a time. */\n tail: Promise<void>;\n\n /** Resolves once `info.openerPageId` is known. */\n opener: Promise<void>;\n\n /**\n * For a tab first used while recording was paused: the last node requested\n * before the pause. Its first node links here when neither the tab nor its\n * opener has an earlier one. Popups inherit it from their opener.\n */\n pauseAnchor?: string;\n\n onNavigation: (frame: Frame) => void;\n};\n\nexport class Recorder implements Ralph {\n private readonly startedAt = new Date().toISOString();\n\n /** How long to wait after a navigation's DOM loads before recording. */\n private readonly settleMs: number;\n\n /**\n * The budget for recording one node, from settling through the screenshot.\n * Past it, the node is recorded as failed.\n */\n private readonly recordTimeoutMs: number;\n\n /**\n * Every context being watched, with its page listener, kept so\n * `stopContext` can remove the exact listener `observe` added.\n */\n private readonly contexts = new Map<\n BrowserContext,\n {\n onPage: (page: Page) => void;\n ready: Promise<void>;\n freezer?: CaptureFreezer;\n }\n >();\n\n /**\n * Every tab seen in a watched context, including closed ones, so their\n * nodes and opener links still reach the manifest.\n */\n private readonly pages = new Map<Page, PageState>();\n\n /**\n * Each node's outcome, in completion order. Nodes in one tab complete\n * in sequence, but tabs interleave; `finish` sorts them.\n */\n private readonly nodes: RawNode[] = [];\n\n /**\n * When each node was requested, keyed by node id, for linking a\n * popup's first node to its opener's latest. Numbers, so they compare\n * without parsing each node's `requestedAt` string.\n */\n private readonly requestedAtMs = new Map<string, number>();\n\n /** Set by `finish`, after which `observe` throws. */\n private closed = false;\n\n /** Set by `pause`; while set, no new nodes are requested. */\n private paused = false;\n\n /** The most recently requested node, the anchor for tabs used while paused. */\n private lastRequestedNodeId?: string;\n\n /**\n * Orders node requests and actions on one counter, which decides the edge\n * an action belongs to. Timestamps could tie.\n */\n private order = 0;\n\n private readonly requestOrder = new Map<string, number>();\n\n private readonly actions = new ActionLog(\n () => ++this.order,\n () => !this.paused && !this.closed,\n (page) => this.pages.get(page)?.info.pageId,\n () =>\n [...this.pages.values()]\n .filter((state) => !state.page.isClosed())\n .map((state) => state.info.pageId),\n );\n\n constructor(\n private readonly options: RalphOptions,\n private readonly config: ConfigSnapshot,\n private readonly testInfo: TestInfo,\n private readonly mode: RawGraphFile['mode'],\n playwright?: unknown,\n ) {\n this.settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;\n this.recordTimeoutMs = options.recordTimeoutMs ?? DEFAULT_RECORD_TIMEOUT_MS;\n\n const validTimings =\n Number.isFinite(this.settleMs) &&\n this.settleMs >= 0 &&\n Number.isFinite(this.recordTimeoutMs) &&\n this.recordTimeoutMs > this.settleMs;\n if (!validTimings) {\n throw new Error(\n 'Ralph recordTimeoutMs must be finite and greater than settleMs, which must be non-negative.',\n );\n }\n\n this.actions.attach(playwright);\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /** Starts recording URL changes in every current and future tab of `context`. */\n observe(context: BrowserContext): Promise<void> {\n if (this.closed) {\n throw new Error('The Ralph recorder has finished.');\n }\n\n const existing = this.contexts.get(context);\n if (existing) {\n return existing.ready;\n }\n\n const freezer = this.options.freezeDuringCapture\n ? new CaptureFreezer(context)\n : undefined;\n\n const ready = freezer?.install() ?? Promise.resolve();\n const onPage = (page: Page) => this.watchPage(page);\n this.contexts.set(context, { onPage, ready, freezer });\n void this.actions.observe(context);\n watchImageResponses(context);\n\n context.on('page', onPage);\n for (const page of context.pages()) {\n onPage(page);\n }\n\n return ready;\n }\n\n async recordNode(page: Page, options?: RecordNodeOptions): Promise<void> {\n await this.observe(page.context());\n const state = this.pages.get(page);\n if (!state) {\n throw new Error('Cannot record a closed browser page.');\n }\n\n if (this.paused) {\n return;\n }\n\n await this.enqueueCreateNodeRequest(state, 'explicit', options);\n }\n\n async setNodeConditions(\n page: Page,\n declaration: ConditionsDeclaration,\n ): Promise<() => Promise<void>> {\n await this.observe(page.context());\n const state = this.pages.get(page);\n if (!state) {\n throw new Error('Cannot tag a closed browser page.');\n }\n\n const key = Symbol();\n state.declared.set(key, declaredConditions(declaration));\n await this.recordConditionChange(state);\n\n return async () => {\n if (state.declared.delete(key)) {\n await this.recordConditionChange(state);\n }\n };\n }\n\n /**\n * Stops requesting nodes, automatic or explicit, until `resume`. Recordings\n * already requested still complete. The next node after `resume` links to\n * the last one before `pause`, so the skipped steps drop out of the graph.\n */\n pause(): void {\n this.paused = true;\n }\n\n /** Undoes `pause`. The current page is not recorded until its URL changes. */\n resume(): void {\n this.paused = false;\n }\n\n /** Waits until every page recording requested so far has finished. */\n async waitForCapture(): Promise<void> {\n await Promise.all([...this.pages.values()].map((state) => state.tail));\n }\n\n visitAllLinks(links: Locator, options?: VisitAllLinksOptions): Promise<void> {\n return visitAllLinks(links, () => this.waitForCapture(), options);\n }\n\n /** Stops watching `context` and waits for its queued recordings. */\n async stopContext(context: BrowserContext): Promise<void> {\n const observed = this.contexts.get(context);\n if (observed) {\n context.off('page', observed.onPage);\n await observed.ready;\n }\n\n const states = [...this.pages.values()].filter(\n (state) => state.page.context() === context,\n );\n for (const state of states) {\n state.page.off('framenavigated', state.onNavigation);\n }\n await Promise.all(states.map((state) => state.tail));\n }\n\n /**\n * Stops recording, then writes the test's raw graph for the reporter, and\n * fails a passing test whose nodes failed to record.\n */\n async finish(): Promise<void> {\n this.closed = true;\n this.actions.detach();\n await Promise.all(\n [...this.contexts.keys()].map((context) => this.stopContext(context)),\n );\n await Promise.all([...this.pages.values()].map((state) => state.opener));\n\n const nodes = this.nodes.sort(\n (a, b) => a.pageId.localeCompare(b.pageId) || a.sequence - b.sequence,\n );\n linkPreviousNodes(\n nodes,\n (pageId) => this.pageInfo(pageId)?.openerPageId,\n (nodeId) => this.requestedAtMs.get(nodeId) ?? 0,\n (pageId) => this.pageState(pageId)?.pauseAnchor,\n );\n this.attachActions(nodes);\n\n const failures = nodes.filter(\n (item) => item.status === 'uncaptured' && item.reason === 'error',\n );\n await this.writeRawGraph(this.buildRawGraph(nodes, failures.length));\n\n const mismatched = nodes.filter((item) => item.tagMismatches?.length);\n if (\n this.options.strictNodeTags &&\n mismatched.length &&\n this.testInfo.status === 'passed'\n ) {\n throw new Error(\n 'Ralph: the app reported tags outside what the test declared, on ' +\n mismatched\n .map(\n (item) => item.url + ' (' + item.tagMismatches!.join(', ') + ')',\n )\n .join('; ') +\n '.',\n );\n }\n\n if (failures.length && this.testInfo.status === 'passed') {\n throw new Error(\n 'Ralph could not record ' +\n failures.length +\n ' page state(s). See the ralph-raw-graph attachment.',\n );\n }\n }\n\n // ---------------------------------------------------------------------------\n // Watching tabs\n // ---------------------------------------------------------------------------\n\n private watchPage(page: Page): void {\n if (this.pages.has(page)) {\n return;\n }\n\n const state: PageState = {\n page,\n info: {\n pageId: 'page-' + this.pages.size,\n browserName: page.context().browser()?.browserType().name(),\n },\n lastUrl: '',\n generation: 0,\n sequence: 0,\n declared: new Map(),\n tail: Promise.resolve(),\n opener: Promise.resolve(),\n onNavigation: (frame) => {\n if (frame === page.mainFrame()) {\n this.actions.navigated(page);\n this.urlChanged(state);\n }\n },\n };\n this.pages.set(page, state);\n\n state.opener = page\n .opener()\n .then((opener) => {\n if (opener) {\n const openerState = this.pages.get(opener);\n state.info.openerPageId = openerState?.info.pageId;\n state.pauseAnchor ??= openerState?.pauseAnchor;\n }\n })\n .catch(() => undefined);\n\n page.on('framenavigated', state.onNavigation);\n this.urlChanged(state);\n }\n\n private urlChanged(state: PageState): void {\n if (this.paused && state.sequence === 0) {\n state.pauseAnchor ??= this.lastRequestedNodeId;\n }\n\n // A same-URL navigation is not leaving the page.\n const url = state.page.url();\n if (url === state.lastUrl) {\n return;\n }\n\n state.generation++;\n state.lastUrl = url;\n state.declared.clear();\n\n if (!this.paused && isHttpUrl(url)) {\n // Automatic recordings never fail the test; failures land in the manifest.\n void this.enqueueCreateNodeRequest(state, 'url-change').catch(\n () => undefined,\n );\n }\n }\n\n private async recordConditionChange(state: PageState): Promise<void> {\n if (!this.paused && isHttpUrl(state.page.url())) {\n await this.enqueueCreateNodeRequest(state, 'condition-change');\n }\n }\n\n private declaredConditions(state: PageState): NodeConditions {\n return mergeConditions([...state.declared.values()]);\n }\n\n private async reportedTags(page: Page): Promise<RecordedTags | undefined> {\n return page\n .evaluate(() =>\n (window as { __ralphTags?: () => RecordedTags }).__ralphTags?.(),\n )\n .catch(() => undefined);\n }\n\n // ---------------------------------------------------------------------------\n // Recording\n // ---------------------------------------------------------------------------\n\n /** Queues a recording behind the tab's earlier ones. */\n private enqueueCreateNodeRequest(\n state: PageState,\n trigger: RawNode['trigger'],\n { name, edgeLabel }: RecordNodeOptions = {},\n ): Promise<void> {\n const url = state.page.url();\n if (!isHttpUrl(url)) {\n throw new Error('Ralph recordings require an HTTP or HTTPS page.');\n }\n\n const request: CreateNodeRequest = {\n nodeId: randomUUID(),\n pageId: state.info.pageId,\n sequence: state.sequence++,\n requestedAt: new Date().toISOString(),\n actualUrl: url,\n url,\n trigger,\n ...(name === undefined ? {} : { name }),\n ...(edgeLabel === undefined ? {} : { edgeLabel }),\n };\n this.requestedAtMs.set(request.nodeId, Date.now());\n this.requestOrder.set(request.nodeId, ++this.order);\n this.lastRequestedNodeId = request.nodeId;\n\n const generation = state.generation;\n const task = state.tail.then(() =>\n this.createNode(state, generation, request),\n );\n state.tail = task.catch(() => undefined);\n\n return task;\n }\n\n /**\n * Creates the requested node: `recorded`, or `uncaptured` if the tab\n * navigates or closes first, or the record timeout passes.\n */\n private async createNode(\n state: PageState,\n generation: number,\n request: CreateNodeRequest,\n ): Promise<void> {\n const { page } = state;\n const deadline = startDeadline(\n this.recordTimeoutMs,\n 'Ralph recording timed out.',\n );\n const { wait } = deadline;\n const navigatedAway = () =>\n state.generation !== generation || page.url() !== request.actualUrl;\n\n const checkPage = () => {\n if (navigatedAway()) {\n throw new Error('The page navigated before its recording completed.');\n }\n if (page.isClosed()) {\n throw new Error('The page closed before its recording completed.');\n }\n };\n const check = () => {\n deadline.signal.throwIfAborted();\n checkPage();\n };\n\n try {\n check();\n await wait(this.contexts.get(page.context())!.ready);\n if (request.trigger !== 'explicit') {\n await this.waitForPageToSettle(page, deadline, check);\n }\n\n check();\n\n const conditions = this.declaredConditions(state);\n const reported = await wait(this.reportedTags(page));\n const mismatches = mismatchesOf(conditions, reported);\n const node = nodeKey(conditions);\n\n // The upload merges captures of one URL and conditions into one node,\n // so a second automatic capture of the node just recorded would be\n // discarded. An explicit one is the test retaking that node's\n // screenshot, so it replaces the capture instead of adding one.\n const sameNode =\n state.lastRecorded?.url === request.url &&\n state.lastRecorded.node === node;\n if (sameNode && request.trigger !== 'explicit') {\n return;\n }\n\n if (\n reported === undefined &&\n compactConditions(conditions) !== undefined &&\n !warnedNoTracker\n ) {\n warnedNoTracker = true;\n console.warn(\n 'Ralph: this page runs no tracker, so the node conditions the test declares cannot be checked against the tags the app reports. Production events must carry the same tags, or these nodes will match no visitors.',\n );\n }\n\n const freezer = this.contexts.get(page.context())!.freezer;\n if (this.options.reduceMotion && !freezer) {\n check();\n // Half the remaining budget, so the screenshot still has time.\n await wait(waitForAnimations(page, (deadline.endsAt - Date.now()) / 2));\n }\n\n const capture = async () => {\n // Geometry is read on both sides of the screenshot to detect layout drift.\n check();\n const before = await wait(readLayout(page));\n check();\n const image = await wait(\n takeScreenshot(page, deadline.endsAt, !!freezer),\n );\n check();\n const after = await wait(readLayout(page));\n check();\n // Optional, so a snapshot that fails or runs out of time leaves the node\n // recorded without one, rather than losing its screenshot too.\n const snapshot = await Promise.race([\n captureAriaSnapshot(page, after).catch(() => undefined),\n delay(Math.max(0, deadline.endsAt - Date.now()), undefined, {\n ref: false,\n }),\n ]);\n\n return { before, image, after, snapshot };\n };\n\n // Cleanup completes before saving files or starting the next capture.\n const { before, image, after, snapshot } = freezer\n ? await freezer.capture(page, deadline.signal, capture, deadline.endsAt)\n : await capture();\n // A spent snapshot budget must not discard the screenshot already taken.\n checkPage();\n\n const recorded: RecordedNode = {\n ...request,\n ...withConditions(conditions),\n ...(reported === undefined ? {} : { reportedTags: reported }),\n ...(mismatches.length > 0 ? { tagMismatches: mismatches } : {}),\n status: 'recorded',\n recordedAt: new Date().toISOString(),\n layout: before,\n geometryStable: JSON.stringify(before) === JSON.stringify(after),\n screenshot: await this.saveScreenshot(request.nodeId, image),\n ...(snapshot && {\n ariaSnapshot: await this.saveAriaSnapshot(request.nodeId, snapshot),\n }),\n };\n\n const retaken = sameNode\n ? this.nodes.findIndex(\n ({ nodeId }) => nodeId === state.lastRecorded?.nodeId,\n )\n : -1;\n\n if (retaken === -1) {\n this.nodes.push(recorded);\n state.lastRecorded = { url: request.url, node, nodeId: request.nodeId };\n } else {\n // Keeps the capture's id and place, which later captures link from.\n const { nodeId, sequence, requestedAt, name, edgeLabel } =\n this.nodes[retaken]!;\n this.nodes[retaken] = {\n ...(name === undefined ? {} : { name }),\n ...(edgeLabel === undefined ? {} : { edgeLabel }),\n ...recorded,\n nodeId,\n sequence,\n requestedAt,\n };\n }\n } catch (error) {\n this.nodes.push({\n ...request,\n ...withConditions(this.declaredConditions(state)),\n status: 'uncaptured',\n reason: navigatedAway() ? 'navigated-away' : 'error',\n message: error instanceof Error ? error.message : String(error),\n });\n // What the test asked for directly fails it; what the page did on its\n // own lands in the manifest.\n if (request.trigger !== 'url-change') {\n throw error;\n }\n } finally {\n deadline.cancel();\n }\n }\n\n /** After a navigation: the DOM, the settle delay, then the `ready` hook. */\n private async waitForPageToSettle(\n page: Page,\n { wait, signal }: Deadline,\n check: () => void,\n ): Promise<void> {\n await wait(\n page.waitForLoadState('domcontentloaded', {\n timeout: this.recordTimeoutMs,\n }),\n );\n await wait(delay(this.settleMs, undefined, { signal }));\n check();\n if (this.options.ready) {\n await wait(this.options.ready(page, signal));\n }\n }\n\n /** Writes the image to the test output and attaches it to the report. */\n private async saveScreenshot(\n nodeId: string,\n image: Screenshot,\n ): Promise<RecordedNode['screenshot']> {\n const path = OUTPUT_DIR + '/' + nodeId + imageExtension(image.contentType);\n const attachmentName = SCREENSHOT_ATTACHMENT_PREFIX + nodeId;\n\n await mkdir(this.testInfo.outputPath(OUTPUT_DIR), { recursive: true });\n await writeFile(this.testInfo.outputPath(path), image.bytes);\n await this.testInfo.attach(attachmentName, {\n // Playwright can delete test output immediately after testEnd, before\n // the reporter merges it. Body attachments survive that cleanup.\n ...(this.testInfo.config.preserveOutput === 'always'\n ? { path: this.testInfo.outputPath(path) }\n : { body: image.bytes }),\n contentType: image.contentType,\n });\n\n return {\n path,\n attachmentName,\n width: image.width,\n height: image.height,\n scale: 'css',\n contentType: image.contentType,\n };\n }\n\n /** Writes the snapshot to the test output and attaches it to the report. */\n private async saveAriaSnapshot(\n nodeId: string,\n snapshot: Buffer,\n ): Promise<NonNullable<RecordedNode['ariaSnapshot']>> {\n const path = OUTPUT_DIR + '/' + nodeId + '.aria.json';\n const attachmentName = ARIA_SNAPSHOT_ATTACHMENT_PREFIX + nodeId;\n\n await mkdir(this.testInfo.outputPath(OUTPUT_DIR), { recursive: true });\n await writeFile(this.testInfo.outputPath(path), snapshot);\n await this.testInfo.attach(attachmentName, {\n ...(this.testInfo.config.preserveOutput === 'always'\n ? { path: this.testInfo.outputPath(path) }\n : { body: snapshot }),\n contentType: 'application/json',\n });\n\n return { path, attachmentName };\n }\n\n // ---------------------------------------------------------------------------\n // Manifest\n // ---------------------------------------------------------------------------\n\n /** Gives each node the actions taken since the node it was reached from was requested. */\n private attachActions(nodes: RawNode[]): void {\n const byId = new Map(nodes.map((item) => [item.nodeId, item]));\n const windows = nodes.flatMap((item) => {\n const from = byId.get(item.previousNodeId ?? '');\n return from\n ? [\n {\n nodeId: item.nodeId,\n after: this.requestOrder.get(from.nodeId) ?? 0,\n upTo: this.requestOrder.get(item.nodeId) ?? 0,\n pageIds: new Set([from.pageId, item.pageId]),\n },\n ]\n : [];\n });\n\n const actions = this.actions.assign(windows, MAX_ACTIONS_PER_EDGE);\n for (const item of nodes) {\n const taken = actions.get(item.nodeId);\n if (taken) {\n item.actions = taken;\n }\n }\n }\n\n private pageState(pageId: string): PageState | undefined {\n return [...this.pages.values()].find(\n (state) => state.info.pageId === pageId,\n );\n }\n\n private pageInfo(pageId: string): PageInfo | undefined {\n return this.pageState(pageId)?.info;\n }\n\n private buildRawGraph(nodes: RawNode[], failureCount: number): RawGraphFile {\n const { testInfo } = this;\n const testPassed = testInfo.status === 'passed';\n\n return {\n formatVersion: 1,\n producer: {\n name: '@ralphralphai/playwright',\n version: packageInfo.version,\n playwrightVersion: playwrightPackage.version,\n },\n runId: this.options.runId ?? process.env['RALPH_RUN_ID'],\n buildId: this.options.buildId ?? process.env['RALPH_BUILD_ID'],\n config: this.config,\n mode: this.mode,\n rawGraph: {\n id: testInfo.testId,\n titlePath: testInfo.titlePath,\n project: testInfo.project.name,\n retry: testInfo.retry,\n repeatEachIndex: testInfo.repeatEachIndex,\n workerIndex: testInfo.workerIndex,\n parallelIndex: testInfo.parallelIndex,\n // Failed nodes fail the test in `finish`, after this is written. The\n // reporter replaces it with the final status anyway.\n status:\n failureCount && testPassed\n ? 'failed'\n : (testInfo.status ?? 'unknown'),\n expectedStatus: testInfo.expectedStatus,\n startedAt: this.startedAt,\n finishedAt: new Date().toISOString(),\n pages: [...this.pages.values()].map((state) => state.info),\n nodes,\n complete:\n testPassed &&\n nodes.length > 0 &&\n nodes.every(\n (item) => item.status === 'recorded' && item.geometryStable,\n ),\n },\n };\n }\n\n private async writeRawGraph(file: RawGraphFile): Promise<void> {\n const path = this.testInfo.outputPath(OUTPUT_DIR, 'raw_graph.json');\n const body = JSON.stringify(file, null, 2) + '\\n';\n await mkdir(this.testInfo.outputPath(OUTPUT_DIR), { recursive: true });\n await writeFile(path, body);\n await this.testInfo.attach(MANIFEST_ATTACHMENT, {\n ...(this.testInfo.config.preserveOutput === 'always'\n ? { path }\n : { body }),\n contentType: 'application/json',\n });\n }\n}\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\nfunction withConditions(conditions: NodeConditions) {\n const compact = compactConditions(conditions);\n return compact === undefined ? {} : { conditions: compact };\n}\n\nfunction isHttpUrl(url: string): boolean {\n return /^https?:/.test(url);\n}\n\ntype Deadline = {\n signal: AbortSignal;\n /** Epoch milliseconds at which the deadline passes. */\n endsAt: number;\n /** Rejects with the timeout error if `promise` outlives the deadline. */\n wait: <T>(promise: Promise<T>) => Promise<T>;\n cancel: () => void;\n};\n\nfunction startDeadline(timeoutMs: number, message: string): Deadline {\n const controller = new AbortController();\n const { signal } = controller;\n const timer = setTimeout(\n () => controller.abort(new Error(message)),\n timeoutMs,\n );\n const aborted = new Promise<never>((_, reject) => {\n signal.addEventListener('abort', () => reject(signal.reason), {\n once: true,\n });\n });\n // Nothing may be waiting when the deadline passes.\n aborted.catch(() => undefined);\n\n return {\n signal,\n endsAt: Date.now() + timeoutMs,\n wait: (promise) => Promise.race([promise, aborted]),\n cancel: () => clearTimeout(timer),\n };\n}\n\n/**\n * Sets `previousNodeId` on each node: the one before it in the same\n * tab, or for a popup's first node, its opener's latest. Failing both, a tab\n * first used while paused links to the last node before the pause.\n * Uncaptured nodes stay in the chain, as regular nodes in the server's graph.\n *\n * `nodes` must be sorted by page, then sequence.\n */\nfunction linkPreviousNodes(\n nodes: RawNode[],\n openerOf: (pageId: string) => string | undefined,\n requestedAtMs: (nodeId: string) => number,\n pauseAnchorOf: (pageId: string) => string | undefined,\n): void {\n const byPage = new Map<string, RawNode[]>();\n const byId = new Map<string, RawNode>();\n for (const item of nodes) {\n byPage.set(item.pageId, [...(byPage.get(item.pageId) ?? []), item]);\n byId.set(item.nodeId, item);\n }\n\n const openerNodeBefore = (item: RawNode) => {\n const openerPageId = openerOf(item.pageId);\n const requestedAt = requestedAtMs(item.nodeId);\n return byPage\n .get(openerPageId ?? '')\n ?.filter((candidate) => requestedAtMs(candidate.nodeId) <= requestedAt)\n .at(-1);\n };\n\n for (const items of byPage.values()) {\n items.forEach((item, index) => {\n const from =\n index > 0\n ? items[index - 1]\n : (openerNodeBefore(item) ??\n byId.get(pauseAnchorOf(item.pageId) ?? ''));\n if (from) {\n item.previousNodeId = from.nodeId;\n }\n });\n }\n}\n","import { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { createGzip } from 'node:zlib';\n\nimport { pack } from 'tar-stream';\n\nimport { imageExtension, type Screenshot } from './image';\nimport type { ConfigSnapshot, RawGraphManifest } from './types';\n\nexport type UploadOptions = {\n apiUrl: string;\n appId: string;\n uploadKey: string;\n timeoutMs?: number;\n};\nexport type RawGraphScreenshot = {\n nodeId: string;\n contentType: Screenshot['contentType'];\n bytes: Uint8Array;\n};\nexport type RawGraphAriaSnapshot = {\n nodeId: string;\n bytes: Uint8Array;\n};\n/** An artifact the server reserved, which the upload and the manifest must name. */\nexport type PreparedUpload = {\n artifactId: string;\n /** Where the result opens in the Ralph dashboard, when the server knows. */\n resultUrl?: string;\n};\n/** The bundle is stored and queued; the result page shows it being processed. */\nexport type UploadReceipt = {\n result: 'ok';\n status: 'queued';\n artifactId: string;\n resultUrl?: string;\n replayed: boolean;\n};\n\nexport const DEFAULT_API_URL = 'https://api.ralphralph.ai';\nexport const DASHBOARD_URL = 'https://dash.ralphralph.ai';\n\n/** Where the uploaded run opens: the server's link, else the Ralph dashboard. */\nexport function resultLink({\n artifactId,\n resultUrl,\n}: {\n artifactId: string;\n resultUrl?: string;\n}): string {\n return (\n resultUrl ?? `${DASHBOARD_URL}/uploads/${encodeURIComponent(artifactId)}`\n );\n}\n\n/** Where the reporter uploads: its options, else the environment, else Ralph's production server. */\nexport function resolveUploadOptions(options: {\n apiUrl?: string;\n appId?: string;\n uploadTimeoutMs?: number;\n}): UploadOptions {\n const uploadKey = process.env['RALPH_UPLOAD_KEY'];\n const apiUrl =\n options.apiUrl || process.env['RALPH_API_URL'] || DEFAULT_API_URL;\n const appId = options.appId ?? process.env['RALPH_APP_ID'];\n if (!uploadKey) {\n throw new Error(\n `To upload to Ralph, set RALPH_UPLOAD_KEY. ${CREATE_KEY_HINT}`,\n );\n }\n if (!appId) {\n throw new Error(\n 'To upload to Ralph, set RALPH_APP_ID (or the reporter option appId) to the app the recordings belong to.',\n );\n }\n return { uploadKey, apiUrl, appId, timeoutMs: options.uploadTimeoutMs };\n}\n\n// Mirrors the server's limits, so an oversized run fails before it is sent.\nexport const MAX_MANIFEST_BYTES = 16 * 1024 * 1024;\nexport const MAX_SCREENSHOTS = 5000;\nexport const MAX_SCREENSHOT_BYTES = 256 * 1024 * 1024;\nexport const MAX_COMPRESSED_BYTES = 256 * 1024 * 1024;\n\nexport const RAW_GRAPH_BUNDLE_INDEX = 'raw_graph.json';\nexport const RAW_GRAPH_BUNDLE_IMAGE_DIR = 'assets/images';\nexport const RAW_GRAPH_BUNDLE_SNAPSHOT_DIR = 'assets/snapshots';\n\nexport type RawGraphBundleIndex = {\n manifest: RawGraphManifest;\n screenshots: { nodeId: string; path: string }[];\n snapshots: { nodeId: string; path: string }[];\n};\n\n/** Where a node's screenshot sits inside the bundle. */\nexport function bundleImagePath(\n nodeId: string,\n contentType: Screenshot['contentType'],\n): string {\n if (!/^[A-Za-z0-9-]{1,64}$/.test(nodeId)) {\n throw new Error(`Ralph node id is not path-safe: ${nodeId}`);\n }\n return `${RAW_GRAPH_BUNDLE_IMAGE_DIR}/${nodeId}${imageExtension(contentType)}`;\n}\n\n/** Where a node's aria snapshot sits inside the bundle. */\nexport function bundleSnapshotPath(nodeId: string): string {\n if (!/^[A-Za-z0-9-]{1,64}$/.test(nodeId)) {\n throw new Error(`Ralph node id is not path-safe: ${nodeId}`);\n }\n return `${RAW_GRAPH_BUNDLE_SNAPSHOT_DIR}/${nodeId}.json`;\n}\n\nconst TOO_BIG =\n 'This run is too big to upload to Ralph. Record fewer pages or screen sizes, or split the tests across runs.';\n\nclass TooBigError extends Error {\n constructor() {\n super(TOO_BIG);\n }\n}\n\n/**\n * Packs the manifest and screenshots into a gzipped tar archive, produced as it\n * is read so the compressed bundle is never held whole:\n *\n * raw_graph.json { manifest, screenshots: [{ nodeId, path }], snapshots: [...] }\n * assets/images/<id>.webp raw screenshot bytes (.png when too large for WebP)\n * assets/snapshots/<id>.json the node's aria snapshot, when it has one\n *\n * Fails with a `TooBigError` once the compressed bytes pass the limit.\n */\nfunction rawGraphBundleStream(\n manifest: RawGraphManifest,\n screenshots: readonly RawGraphScreenshot[],\n snapshots: readonly RawGraphAriaSnapshot[],\n): Readable {\n if (\n Buffer.byteLength(JSON.stringify(manifest)) > MAX_MANIFEST_BYTES ||\n screenshots.length > MAX_SCREENSHOTS ||\n screenshots.reduce(\n (total, screenshot) => total + screenshot.bytes.byteLength,\n 0,\n ) > MAX_SCREENSHOT_BYTES\n ) {\n throw new TooBigError();\n }\n\n const images = screenshots.map(({ nodeId, contentType, bytes }) => ({\n nodeId,\n path: bundleImagePath(nodeId, contentType),\n bytes,\n }));\n const files = [\n ...images,\n ...snapshots.map(({ nodeId, bytes }) => ({\n nodeId,\n path: bundleSnapshotPath(nodeId),\n bytes,\n })),\n ];\n const index: RawGraphBundleIndex = {\n manifest,\n screenshots: images.map(({ nodeId, path }) => ({ nodeId, path })),\n snapshots: snapshots.map(({ nodeId }) => ({\n nodeId,\n path: bundleSnapshotPath(nodeId),\n })),\n };\n\n // Fixed attributes keep retries of the same artifact byte-identical.\n const attrs = { mtime: new Date(0), uid: 0, gid: 0 };\n const tar = pack();\n\n // Cancellation destroys queued entries too; handle their errors through the\n // pack's pipeline instead of leaving unhandled stream error events.\n const onEntryError = (error: Error) => tar.destroy(error);\n tar\n .entry({ name: RAW_GRAPH_BUNDLE_INDEX, ...attrs }, JSON.stringify(index))\n .on('error', onEntryError);\n\n for (const { path, bytes } of files) {\n tar\n .entry({ name: path, ...attrs }, Buffer.from(bytes))\n .on('error', onEntryError);\n }\n tar.finalize();\n\n const gzip = createGzip();\n pipeline(tar, gzip).catch((error: unknown) => gzip.destroy(error as Error));\n return Readable.from(\n (async function* () {\n let size = 0;\n for await (const chunk of gzip) {\n size += (chunk as Buffer).byteLength;\n if (size > MAX_COMPRESSED_BYTES) {\n throw new TooBigError();\n }\n yield chunk as Buffer;\n }\n })(),\n );\n}\n\n/** The bundle `uploadRawGraph` streams, whole, for saving or inspecting it. */\nexport async function createRawGraphBundle(\n manifest: RawGraphManifest,\n screenshots: readonly RawGraphScreenshot[],\n snapshots: readonly RawGraphAriaSnapshot[] = [],\n): Promise<Uint8Array> {\n const chunks: Buffer[] = [];\n for await (const chunk of rawGraphBundleStream(\n manifest,\n screenshots,\n snapshots,\n )) {\n chunks.push(chunk as Buffer);\n }\n return Buffer.concat(chunks);\n}\n\nfunction isLoopback(url: URL): boolean {\n return (\n url.hostname === 'localhost' ||\n url.hostname === '[::1]' ||\n /^127(\\.\\d{1,3}){3}$/.test(url.hostname)\n );\n}\n\nfunction checkedApiUrl(options: UploadOptions, path: string): URL {\n if (!/^[A-Za-z0-9_-]{1,64}$/.test(options.appId)) {\n throw new Error(\n `\"${options.appId}\" isn't a valid Ralph app id. Check RALPH_APP_ID.`,\n );\n }\n const url = new URL(path, options.apiUrl);\n if (\n !(\n url.protocol === 'https:' ||\n (url.protocol === 'http:' && isLoopback(url))\n )\n ) {\n throw new Error(\n `RALPH_API_URL must start with https:// (http:// only works for localhost), not ${options.apiUrl}.`,\n );\n }\n if (!options.uploadKey) {\n throw new Error(\n `To upload to Ralph, set RALPH_UPLOAD_KEY. ${CREATE_KEY_HINT}`,\n );\n }\n return url;\n}\n\n/** Sends one request, turning every way it can fail into a message that says what to fix. */\nasync function request(\n url: URL,\n init: RequestInit & { duplex?: 'half' },\n options: UploadOptions,\n timeoutMs: number,\n): Promise<Record<string, unknown>> {\n let response: Response;\n try {\n response = await fetch(url, {\n ...init,\n method: 'POST',\n headers: {\n ...init.headers,\n Authorization: `Bearer ${options.uploadKey}`,\n },\n redirect: 'error',\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (error) {\n if (\n error instanceof TooBigError ||\n (error instanceof Error && error.cause instanceof TooBigError)\n ) {\n throw new Error(TOO_BIG, { cause: error });\n }\n throw new Error(describeRequestFailure(error, url, timeoutMs), {\n cause: error,\n });\n }\n\n if (!response.ok) {\n throw new Error(\n describeHttpFailure(\n response.status,\n await responseDetail(response),\n url,\n options.appId,\n ),\n );\n }\n\n try {\n return (await response.json()) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nconst unreadableReply = (url: URL) =>\n new Error(\n \"Upload to Ralph failed. Ralph's reply couldn't be read. Update @ralphralphai/playwright to the latest version and run again.\" +\n customServerHint(url),\n );\n\nconst PREPARE_TIMEOUT_MS = 30_000;\n\n/**\n * Reserves an artifact for the run. Its `resultUrl` works from now on, so the\n * run can be followed while it uploads and while Ralph processes it.\n */\nexport async function prepareArtifactUpload(\n options: UploadOptions,\n): Promise<PreparedUpload> {\n return reserve(options, '/api/upload/artifact/prepare', {\n externalAppId: options.appId,\n });\n}\n\n/**\n * Records that `buildId` went live under `config`, and reserves the artifact\n * its run uploads into. The run then becomes that build's graph in Ralph,\n * where an artifact from `prepareArtifactUpload` stays a standalone result.\n */\nexport async function registerDeployment(\n options: UploadOptions,\n deployment: {\n buildId: string;\n config: ConfigSnapshot['config'];\n tags?: string[];\n },\n): Promise<PreparedUpload> {\n return reserve(options, '/api/upload/deployment/register', {\n externalAppId: options.appId,\n ...deployment,\n });\n}\n\nasync function reserve(\n options: UploadOptions,\n route: string,\n body: Record<string, unknown>,\n): Promise<PreparedUpload> {\n const url = checkedApiUrl(options, route);\n const reply = await request(\n url,\n {\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n },\n options,\n PREPARE_TIMEOUT_MS,\n );\n if (\n reply['result'] !== 'ok' ||\n typeof reply['artifactId'] !== 'string' ||\n (reply['resultUrl'] !== undefined && typeof reply['resultUrl'] !== 'string')\n ) {\n throw unreadableReply(url);\n }\n return {\n artifactId: reply['artifactId'],\n resultUrl: reply['resultUrl'] as string | undefined,\n };\n}\n\n/**\n * Streams the run's bundle to the artifact `manifest.artifactId` names, which\n * `prepareArtifactUpload` has to have reserved. Ralph processes it after this\n * returns; the result page shows when it is ready.\n */\nexport async function uploadRawGraph(\n manifest: RawGraphManifest,\n screenshots: readonly RawGraphScreenshot[],\n options: UploadOptions,\n snapshots: readonly RawGraphAriaSnapshot[] = [],\n): Promise<UploadReceipt> {\n const url = checkedApiUrl(\n options,\n `/api/upload/web/apps/${options.appId}/artifact/${encodeURIComponent(manifest.artifactId)}`,\n );\n // Environment only, and absent from the public options, because only Ralph\n // developers run a server that accepts local storage.\n const storage = process.env['RALPH_UPLOAD_STORAGE'] || 'gcs';\n if (storage !== 'gcs' && storage !== 'local') {\n throw new Error(\n `RALPH_UPLOAD_STORAGE must be gcs or local, not ${storage}.`,\n );\n }\n if (storage === 'local') {\n url.searchParams.set('storage', 'local');\n }\n\n const timeoutMs = options.timeoutMs ?? 120_000;\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n throw new Error(\n 'uploadTimeoutMs in the Ralph reporter options must be a positive number of milliseconds.',\n );\n }\n\n const receipt = await request(\n url,\n {\n headers: { 'content-type': 'application/gzip' },\n body: Readable.toWeb(\n rawGraphBundleStream(manifest, screenshots, snapshots),\n ) as ReadableStream,\n // Required by fetch for a streamed body.\n duplex: 'half',\n },\n options,\n timeoutMs,\n );\n if (\n receipt['result'] !== 'ok' ||\n receipt['status'] !== 'queued' ||\n receipt['artifactId'] !== manifest.artifactId ||\n (receipt['resultUrl'] !== undefined &&\n typeof receipt['resultUrl'] !== 'string') ||\n typeof receipt['replayed'] !== 'boolean'\n ) {\n throw unreadableReply(url);\n }\n return receipt as UploadReceipt;\n}\n\nconst CREATE_KEY_HINT =\n 'You can create an upload key in your project dashboard on Ralph.';\nconst SUPPORT_HINT = 'If it keeps failing, contact Ralph support.';\n\n/** Only users who set their own server need to hear about it. */\nfunction customServerHint(url: URL): string {\n return url.origin === new URL(DEFAULT_API_URL).origin\n ? ''\n : ` Also check RALPH_API_URL (${url.origin}) is right.`;\n}\n\nfunction describeRequestFailure(\n error: unknown,\n url: URL,\n timeoutMs: number,\n): string {\n if (error instanceof Error && error.name === 'TimeoutError') {\n return (\n `Upload to Ralph took longer than ${timeoutMs / 1000}s and was stopped. ` +\n 'Check your network, or raise uploadTimeoutMs in the reporter options.'\n );\n }\n const cause =\n error instanceof Error && error.cause instanceof Error\n ? error.cause.message\n : error instanceof Error\n ? error.message\n : String(error);\n return (\n `Couldn't connect to Ralph (${cause}). ` +\n `Check that this machine can reach ${url.origin}.` +\n customServerHint(url)\n );\n}\n\nfunction describeHttpFailure(\n status: number,\n detail: string | undefined,\n url: URL,\n appId: string,\n): string {\n let message: string;\n if (status === 401) {\n message = `Ralph didn't accept your upload key. Check RALPH_UPLOAD_KEY. ${CREATE_KEY_HINT}`;\n } else if (status === 403) {\n message = `Your upload key can't upload to the app \"${appId}\". Check RALPH_APP_ID is an app in the same project as the key.`;\n } else if (status === 404) {\n message =\n `Ralph couldn't find the app \"${appId}\". Check RALPH_APP_ID.` +\n customServerHint(url);\n } else if (status === 413) {\n message =\n 'This run is too big to upload. Record fewer pages or screen sizes, or split the tests across runs.';\n } else if (status === 429) {\n message = 'Too many uploads right now. Wait a minute and run again.';\n } else if (status >= 500) {\n message = `Something went wrong on Ralph's side. Run again later. ${SUPPORT_HINT}`;\n } else {\n message = `Ralph couldn't accept this upload. Update @ralphralphai/playwright to the latest version and run again. ${SUPPORT_HINT}`;\n }\n return `Upload to Ralph failed. ${message} (HTTP ${status}${detail ? `: ${detail}` : ''})`;\n}\n\n/** The server's reason for rejecting the upload, kept short. */\nasync function responseDetail(response: Response): Promise<string | undefined> {\n let text: string;\n try {\n text = (await response.text()).trim();\n } catch {\n return undefined;\n }\n try {\n const body = JSON.parse(text) as {\n result?: unknown;\n message?: unknown;\n error?: unknown;\n };\n const message = body.result ?? body.message ?? body.error;\n if (typeof message === 'string') {\n text = message;\n }\n } catch {\n // Not JSON; use the text as is.\n }\n text = text.replace(/\\s+/g, ' ');\n return text.length > 200 ? text.slice(0, 200) + '…' : text || undefined;\n}\n"],"mappings":"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"}