/** * Capture Agent — WebPlaywrightLocal RuntimeAdapter * * Thin adapter delegating to the existing Browser class from src/browser.ts. * This is the first (and for now only) RuntimeAdapter implementation. */ import type { Browser } from './browser.js'; import type { AKTree, OutscaleConfig, VideoPageSignals } from './types.js'; import type { RuntimeAdapter, ClickOptions, WaitCondition, RecordingOptions, RecordingResult, SemanticTarget, ProgressSnapshot, VisualStabilityResult } from './execution-types.js'; import { type ResolveOptions } from './semantic-resolver.js'; export declare function isRecognizedHintCookieKey(key: string, kind: 'locale' | 'theme'): boolean; export declare class WebPlaywrightLocal implements RuntimeAdapter { private browser; private recordingDir?; private readonly sessionStartedAt; private recording; private clipCursor; /** * Flags / hooks that surface mid-recording navigations in CLI logs. When a * navigation (NAVIGATE opcode, SET_LOCALE storage reload) is initiated by * the runner, `expectedNavigationCount` is bumped before the call and * decremented when the framenavigated handler observes it — so any nav that * fires WITHOUT a pending count is logged as UNEXPECTED (likely page-side * `location.reload()`, session redirect, or HMR). `detach()` removes the * listeners when recording ends. */ private recordingNavWatcher; constructor(browser: Browser, recordingDir?: string | undefined); navigate(url: string): Promise; getCurrentUrl(): Promise; getPageTitle(): Promise; /** AUT-240 (Layer A): live text of the first match, for text_contains. */ getTextContent(selector: string): Promise; detectAppVersion(): Promise; getAKTree(): Promise; getPageSignals(): Promise; click(selector: string, options?: ClickOptions): Promise; /** * Click an element using semantic target resolution. * Tries CSS selector first, falls back to Playwright semantic locators. */ clickByTarget(opts: ResolveOptions & { onClick?: (timestampMs: number) => void; }): Promise; /** * Type into an element using semantic target resolution. */ typeByTarget(opts: ResolveOptions, text: string, clearFirst?: boolean, typeOpts?: { onKeystroke?: (timestampMs: number) => void; }): Promise; /** * Wait for an element using semantic target resolution. */ waitForTarget(opts: ResolveOptions, timeoutMs?: number): Promise; /** * Scroll an element into view using semantic target resolution. */ scrollIntoViewByTarget(opts: ResolveOptions): Promise; type(selector: string, text: string, clearFirst?: boolean, opts?: { onKeystroke?: (timestampMs: number) => void; }): Promise; pressKey(key: string): Promise; scroll(direction: 'up' | 'down' | 'left' | 'right', amount?: number): Promise; scrollIntoView(selector: string): Promise; waitFor(condition: WaitCondition): Promise; /** AUT-240 (Layer C): cheap page-activity snapshot for the progress watchdog. */ getProgressSnapshot(): Promise; /** AUT-240 (Layer B): wait for the page to be visually stable before capture. */ waitForVisuallyStable(options?: { maxWaitMs?: number; }): Promise; dismissOverlays(): Promise<{ dismissed: boolean; method: string | null; }>; takeScreenshot(): Promise; takeElementScreenshot(selector: string, outscale?: OutscaleConfig): Promise; takeCleanScreenshot(): Promise; beginRecording(options: RecordingOptions): Promise; getElementBoundingBox(selector: string): Promise<{ x: number; y: number; width: number; height: number; } | null>; endRecording(): Promise; setLocale(locale: string): Promise; setColorScheme(scheme: 'light' | 'dark'): Promise; reloadPage(): Promise; writeStorageHint(params: { storage: 'localStorage' | 'sessionStorage' | 'cookie'; key: string; value: string; kind: 'locale' | 'theme'; }): Promise; hover(selector: string): Promise; hoverByTarget(opts: ResolveOptions): Promise; selectOption(selector: string, option: { label?: string; value?: string; index?: number; }): Promise; selectOptionByTarget(opts: ResolveOptions, option: { label?: string; value?: string; index?: number; }): Promise; checkByTarget(opts: ResolveOptions, checked: boolean, actionOpts?: { onClick?: (timestampMs: number) => void; }): Promise; doubleClickByTarget(opts: ResolveOptions, actionOpts?: { onClick?: (timestampMs: number) => void; }): Promise; check(selector: string, checked: boolean, actionOpts?: { onClick?: (timestampMs: number) => void; }): Promise; doubleClick(selector: string, actionOpts?: { onClick?: (timestampMs: number) => void; }): Promise; drag(opts: { selector?: string; target?: SemanticTarget; selectorAlternates?: string[]; toSelector?: string; toTarget?: SemanticTarget; toSelectorAlternates?: string[]; offset?: { dx: number; dy: number; }; locale?: string; }): Promise; cloneElement(opts: { sourceSelector: string; containerSelector: string; count: number; removeSource?: boolean; }): Promise<{ clonedCount: number; }>; setAttribute(opts: { selector: string; attribute: string; value: string; }): Promise; setTextContent(opts: { selector: string; text: string; }): Promise; removeElement(opts: { selector: string; }): Promise<{ removedCount: number; }>; setInputValue(opts: { selector: string; value: string; }): Promise; clickHidden(opts: { selector: string; }): Promise; extractFavicon(): Promise<{ buffer: Buffer; mimeType: string; } | null>; close(): Promise; private typeIntoLocator; private seedClipCursor; private moveClipCursorToViewportCenter; /** * Animate the cursor to a point inside `locator` and return that point so * the caller can click at the SAME coordinates (via * `locator.click({ position })`). Returning the target avoids the "snap" * bug where Playwright's default click recentered the mouse after the * human-like Bezier landed somewhere off-center. * * Returns null when the cursor overlay is disabled, when the element has * no bounding box, or when the cursor is already inside the element (no * move needed, caller should let Playwright click at its default center). */ private moveClipCursorToLocator; /** * Convert an absolute viewport point to a position relative to `locator`'s * bounding box, suitable for `locator.click({ position })`. Returns null * if the point is outside the box (defensive fallback — callers should * then let Playwright click at the default center). */ private relativeClickPosition; private moveClipCursorToPoint; private emitClipClickPulse; /** * Drain the browser-side `__akClickAt` buffer for timestamps newer than * `sinceMs`, replay them through `onClick`, and reset the buffer so the * next click action starts fresh. This is what makes mouse SFX line up * exactly with the visual click in the recorded video — the mousedown * listener inside the cursor overlay timestamps each click at the same * instant the browser dispatches it, which is also the frame the CDP * screencast captures. * * Falls back to `sinceMs` (Node wall-clock at action dispatch) when the * buffer is empty (e.g. `useKeyboard` Enter-press path, or transient * page.evaluate failure). */ private reportClickSfxTimestamps; } export declare function describeResolveOptions(opts: ResolveOptions): string;