import { type ActorCompletionReason, type ActorPersonaRef, type ActorStatus, type ActorTrace } from "./actor-contract.js"; import { type PreparedOutputDirectory } from "./selected-output-paths.js"; export interface ScriptedLocatorLike { first(): ScriptedLocatorLike; fill(value: string, options?: { timeout?: number; }): Promise; click(options?: { timeout?: number; }): Promise; count(): Promise; waitFor(options?: { state?: "visible"; timeout?: number; }): Promise; isVisible(options?: { timeout?: number; }): Promise; } export interface ScriptedPageLike { goto(url: string, options?: { waitUntil?: "domcontentloaded"; timeout?: number; }): Promise; locator(selector: string): ScriptedLocatorLike; waitForTimeout(ms: number): Promise; waitForFunction(fn: string, arg: unknown, options?: { timeout?: number; }): Promise; screenshot(options: { path: string; fullPage: boolean; }): Promise; url(): string; evaluate(pageFunction: string): Promise; } export interface ScriptedBrowserLike { newContext(options: { deviceScaleFactor: number; isMobile: boolean; viewport: { width: number; height: number; }; }): Promise<{ newPage(): Promise; }>; close(): Promise; } export interface ScriptedBrowserLaunchArgs { browserCommand: string; timeoutMs: number; } export type ScriptedBrowserEvidenceUrlPolicy = { kind: "loopback"; } | { kind: "provisioned-subject"; evidenceOrigin: string; }; /** Production default: lazy playwright-core import + chromium.launch, exactly as the driver * always did. Kept in ONE place so the optional peer is touched by exactly one code path. */ export declare function launchPlaywrightChromium(args: ScriptedBrowserLaunchArgs): Promise; export interface BrowserSurface { id: "desktop" | "mobile"; label: string; viewport: { width: number; height: number; deviceScaleFactor: number; isMobile: boolean; }; } export interface BrowserSurfaceCapture { capturedAt: string; durationMs: number; httpStatus?: number; ok: boolean; reason: string; /** * Surface-level screenshot the producer wrote (the last step's screenshot). * Omitted for a blocked capture whose evidence is the failure itself, so the * stream never claims a screenshot embed/ui reference that does not exist. * See src/artifact-reference.ts. */ screenshotPath?: string; steps: BrowserPersonaStepCapture[]; surface: BrowserSurface; tracePath: string; } export type BrowserPersonaAction = "goto" | "click" | "fill" | "assertText" | "waitForText" | "waitForSelector"; export interface BrowserPersonaAssertionCapture { id: string; reason: string; status: "passed" | "blocked"; } export interface BrowserPersonaStepCapture { action: string; assertions?: BrowserPersonaAssertionCapture[]; completedAt: string; durationMs: number; id: string; label: string; reason: string; /** * Path to the step screenshot the producer actually wrote. Omitted for blocked * steps where the failure itself is the recorded evidence and no screenshot was * written — the bundle must not reference an artifact that does not exist (see * src/artifact-reference.ts). A step that ran and attempted a screenshot keeps * this even when its assertions failed, so a broken producer still fails verify. */ screenshotPath?: string; status: "passed" | "blocked"; url: string; } export interface BrowserPersonaStepExpectation { selectorVisible?: string; stateChanged?: boolean; text?: string; urlIncludes?: string; } export interface BrowserPersonaStepManifest { action: BrowserPersonaAction; expectation?: BrowserPersonaStepExpectation; id: string; label: string; path?: string; selector?: string; value?: string; } export interface BrowserPersonaJourney { goal: string; scenarioId: string; scenarioTitle: string; source: string; sourceDigest: string; startPath: string; steps: BrowserPersonaStepManifest[]; } export declare const browserSurfaces: BrowserSurface[]; export declare function captureBrowserSurface(args: { absoluteArtifactRoot: PreparedOutputDirectory; appUrl: string; browserCommand: string; browserJourney: BrowserPersonaJourney; surface: BrowserSurface; timeoutMs: number; }): Promise; export declare function captureBrowserSurfaceFixture(args: { absoluteArtifactRoot: PreparedOutputDirectory; appUrl: string; browserCommand: string; browserJourney: BrowserPersonaJourney; surface: BrowserSurface; timeoutMs: number; }): Promise; export declare function captureBrowserSurfaceWithPlaywright(args: { absoluteArtifactRoot: PreparedOutputDirectory; appUrl: string; browserCommand: string; browserJourney: BrowserPersonaJourney; surface: BrowserSurface; timeoutMs: number; }): Promise; export declare function executeBrowserPersonaStep(args: { absoluteArtifactRoot: PreparedOutputDirectory; appUrl: string; browserJourney: BrowserPersonaJourney; page: ScriptedPageLike; step: BrowserPersonaStepManifest; surface: BrowserSurface; timeoutMs: number; urlPolicy?: ScriptedBrowserEvidenceUrlPolicy; }): Promise; export declare function evaluateBrowserStepExpectations(args: { afterState: { bodyDigest: string; url: string; }; beforeState: { bodyDigest: string; url: string; }; page: ScriptedPageLike; step: BrowserPersonaStepManifest; timeoutMs: number; }): Promise; export declare function buildBlockedBrowserPersonaSteps(args: { browserJourney: BrowserPersonaJourney; currentUrl: string; reason: string; surface: BrowserSurface; timestamp: string; urlPolicy?: ScriptedBrowserEvidenceUrlPolicy; }): BrowserPersonaStepCapture[]; export declare function screenshotPathForBrowserStep(surface: BrowserSurface, step: BrowserPersonaStepManifest | undefined): string; /** * Surface-level screenshot path = the last step that actually wrote one. Returns * undefined when no step wrote a screenshot (a fully blocked capture whose evidence * is the failure itself), so the producer never synthesizes a path to a file it did * not write. See src/artifact-reference.ts. */ export declare function surfaceScreenshotPath(steps: BrowserPersonaStepCapture[]): string | undefined; export declare function resolveBrowserStepUrl(appUrl: string, value: string | undefined): string; export declare function resolveBrowserStepUrlForPolicy(appUrl: string, value: string | undefined, urlPolicy?: ScriptedBrowserEvidenceUrlPolicy): string; export declare function buildBrowserTrace(args: { appUrl: string; browserCommand: string; browserJourney: BrowserPersonaJourney; capturedAt: string; durationMs: number; httpStatus?: number; ok: boolean; reason: string; screenshotPath?: string; steps: BrowserPersonaStepCapture[]; surface: BrowserSurface; }): Record; export declare function sanitizeLoopbackUrl(value: string): string; export declare function sanitizeBrowserEvidenceUrl(value: string, urlPolicy?: ScriptedBrowserEvidenceUrlPolicy): string; export declare function captureScreenshotWithBrowser(args: { args: string[]; browserCommand: string; screenshotPath: string; timeoutMs: number; }): Promise; export declare function probeAppUrl(appUrl: string, timeoutMs: number): Promise<{ ok: boolean; reason: string; status?: number; }>; export declare function resolveBrowserCommand(): Promise; export declare function normalizeLocalAppUrl(value: string): string | null; export declare function parseBrowserPersonaJourneyFromScenario(args: { raw: unknown; relativePath: string; sourceDigest: string; }): { failure?: string; journey?: BrowserPersonaJourney; }; export declare function parseBrowserPersonaStep(rawStep: unknown, index: number): { failure?: string; step?: BrowserPersonaStepManifest; }; export declare function builtinBrowserPersonaJourney(): BrowserPersonaJourney; export declare const SCRIPTED_BROWSER_PROVIDER = "browser-persona"; export interface ScriptedBrowserSessionOptions { /** Pre-normalized loopback URL, or a harness-minted provisioned subject URL. */ appUrl: string; /** Stable redacted URL label persisted in public-safe evidence when appUrl is private. */ evidenceAppUrl?: string; /** Defaults to loopback. Provisioned subjects drive a private URL but persist redacted labels. */ urlPolicy?: ScriptedBrowserEvidenceUrlPolicy; /** Parsed + validated by the backend (scenario.ref is consumed there, fail-closed). */ journey: BrowserPersonaJourney; /** ONE session per surface lane. */ surface: BrowserSurface; /** id = actors[0].persona ?? "scripted-journey"; promptDigest = journey.sourceDigest prefix * (the step manifest IS the "prompt" — no model prompt exists on this lane). */ persona: ActorPersonaRef; /** Journey wall-clock budget (default upstream: 60_000, today's run --app-url default). */ timeoutMs: number; /** Absolute; the session writes screenshots/ and traces/.json beneath it. */ artifactRoot: string; /** Default resolveBrowserCommand(); recorded as "injected-browser" when launchBrowser is injected. */ browserCommand?: string; /** DI seam; production default is launchPlaywrightChromium. */ launchBrowser?: (args: ScriptedBrowserLaunchArgs) => Promise; now?: () => number; } export interface ScriptedBrowserSessionResult { status: ActorStatus; completionReason: ActorCompletionReason; reason: string; /** Native evidence incl. tracePath (humanish.browser-persona-trace.v1, written to disk). */ capture: BrowserSurfaceCapture; /** humanish.actor-trace.v1 projection. */ trace: ActorTrace; } /** * Run the scripted journey for ONE surface and return native capture + ActorTrace projection. * * Completion semantics (the contract the projection tests pin): * - every step executed, every assertion passed, HTTP probe ok -> passed / goal_satisfied * (the scenario's expect blocks ARE the success predicate; a pass claims "the app still * affords this exact journey", nothing about user behavior); * - a step's expectation evaluated false, a step target missing/unactionable, or an * unreachable subject -> failed / step_failed (the harness executed faithfully; the SUBJECT * did not satisfy the script — distinct from actor_error/harness_error); * - journey exceeded timeoutMs -> timed_out / timed_out; * - browser launch/import crash -> failed / harness_error; * - gave_up / blocked_approval are UNREACHABLE from this actor (no persona patience, no * approvals exist on a deterministic replay) — asserted in tests. */ export declare function runScriptedBrowserSession(options: ScriptedBrowserSessionOptions): Promise; /** Internal lab seam: the run root is already prepared and must stay bound to that identity. */ export declare function runScriptedBrowserSessionInPreparedRoot(options: ScriptedBrowserSessionOptions, preparedArtifactRoot: PreparedOutputDirectory): Promise;