import assert from "node:assert/strict"; import test from "node:test"; import { createBrowserRuntimeFactory } from "./runtime.ts"; import { BrowserOperationError } from "./contracts.ts"; import type { BrowserOperation, BrowserRuntimeOptions, BrowserTransport, BrowserTransportRequest, BrowserTransportResultFor, PrivateArtifactMetadata, PrivateArtifactStore, } from "./contracts.ts"; const tab = (id: string, url = "https://example.test/form") => ({ tabId: id as never, kind: "page" as const, selected: true, owned: true as const, url, origin: new URL(url).origin, title: "Fixture", }); function artifacts(): { store: PrivateArtifactStore; committed: PrivateArtifactMetadata[] } { const committed: PrivateArtifactMetadata[] = []; const store: PrivateArtifactStore = { async open(owner, kind) { let bytes = 0; let done = false; return { owner, kind, async write(chunk) { if (done) throw new Error("closed"); bytes += chunk.byteLength; }, async commit() { done = true; const metadata: PrivateArtifactMetadata = { artifactId: `${kind}-1`, kind, contentType: kind === "screenshot" ? "image/png" : "application/json", path: `/private/${kind}-1`, bytes, owner, permissions: "0600", localOnly: true, modelForwarded: false, }; committed.push(metadata); return metadata; }, async abort() { done = true; }, }; }, async close() {}, }; return { store, committed }; } function transportFixture() { const calls: BrowserTransportRequest[] = []; let transportClosed = 0; const transport: BrowserTransport = { async dispatch(request: BrowserTransportRequest, signal: AbortSignal): Promise> { calls.push(request as BrowserTransportRequest); const op = request.operation; if (op.op === "observe") return { op: "observe", result: { observationId: "o1", url: "https://example.test/form", title: "Fixture", candidates: [] } } as unknown as BrowserTransportResultFor; if (op.op === "read") return { op: "read", result: { tab: tab("tab-1"), documentId: "doc-1", url: "https://example.test/form", title: "Fixture", visibleText: "Hello", visibleTextTruncated: false } } as unknown as BrowserTransportResultFor; if (op.op === "listTabs") return { op: "listTabs", result: [tab("tab-1"), tab("tab-2", "https://other.test/")] } as unknown as BrowserTransportResultFor; if (op.op === "navigate") return { op: "navigate", result: { status: "committed", mutationState: "verified", tab: tab("tab-1", op.request.url), documentId: "doc-2" } } as unknown as BrowserTransportResultFor; if (op.op === "narrowOriginPolicy") return { op: "narrowOriginPolicy", result: { allowedOrigins: op.request.allowedOrigins } } as unknown as BrowserTransportResultFor; if (op.op === "captureEvidence") return { op: "captureEvidence", result: { status: "complete", console: [{ level: "error", message: "password=secret https://example.test/?token=secret", count: 1, truncated: false }], failedRequests: [], screenshot: { kind: "screenshot", contentType: "image/png", encoding: "base64", dataBase64: "aGk=", byteLength: 2, truncated: false }, } } as unknown as BrowserTransportResultFor; if (op.op === "close") return { op: "close", result: { closed: true } } as unknown as BrowserTransportResultFor; if (op.op === "assert") return { op: "assert", result: { status: "satisfied", readOnly: true, tab: tab("tab-1"), documentId: "doc-1" } } as unknown as BrowserTransportResultFor; if (op.op === "reload") return { op: "reload", result: { status: "committed", mutationState: "verified", tab: tab("tab-1"), documentId: "doc-2" } } as unknown as BrowserTransportResultFor; if (op.op === "selectTab") return { op: "selectTab", result: { selected: true, tab: tab(op.request.tabId) } } as unknown as BrowserTransportResultFor; if (op.op === "waitPopup") return { op: "waitPopup", result: { status: "timed_out", autoSwitched: false } } as unknown as BrowserTransportResultFor; if (op.op === "closeTab") return { op: "closeTab", result: { closed: true, tabId: op.request.tabId, mutationState: "verified" } } as unknown as BrowserTransportResultFor; throw new Error("unexpected operation"); }, async close() { transportClosed++; }, }; return { transport, calls, transportClosed: () => transportClosed }; } function options(artifactsStore: PrivateArtifactStore, transport: BrowserTransport): BrowserRuntimeOptions { return { sessionId: "session-1", cdpUrl: "http://127.0.0.1:9222", allowedOrigins: "*", artifacts: artifactsStore, transportFactory: { create() { return transport; } } }; } test("factory creates one persistent transport and serializes same-session calls", async () => { const store = artifacts(); const fixture = transportFixture(); const runtime = createBrowserRuntimeFactory(options(store.store, fixture.transport).transportFactory).create(options(store.store, fixture.transport)); await Promise.all([runtime.observe(new AbortController().signal), runtime.read({}, new AbortController().signal)]); assert.equal(fixture.calls.filter((call) => call.operation.op === "observe" || call.operation.op === "read").length, 2); assert.deepEqual(Object.keys(fixture.calls[0] ?? {}).sort(), ["operation"]); assert.equal((fixture.calls[0]?.operation as any).op, "observe"); assert.equal((fixture.calls[0] as any)?.op, undefined); assert.equal(fixture.calls[1]?.operation.op, "read"); await runtime.close(); await runtime.close(); assert.equal(fixture.transportClosed(), 1); }); test("aborted operations still make an independent bounded evidence attempt", async () => { const store = artifacts(); const fixture = transportFixture(); const runtime = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create(options(store.store, fixture.transport)); const controller = new AbortController(); controller.abort(new Error("operation deadline exceeded")); await assert.rejects(runtime.observe(controller.signal), (error: any) => { assert.equal(error.details.code, "aborted"); assert.deepEqual(error.failureEvidence, { status: "complete" }); return true; }); assert.equal(fixture.calls.filter((call) => call.operation.op === "captureEvidence").length, 1); await runtime.close(); }); test("already-aborted parent does not create transport during failure evidence", async () => { const store = artifacts(); const parent = new AbortController(); parent.abort(new Error("browser session closed")); let created = 0; const runtime = createBrowserRuntimeFactory({ create() { created++; return transportFixture().transport; } }).create({ ...options(store.store, transportFixture().transport) }, parent.signal); await assert.rejects(runtime.observe(new AbortController().signal), (error: any) => { assert.equal(error.details.code, "browser_closed"); assert.deepEqual(error.failureEvidence, { status: "unavailable", reason: "browser session is unavailable" }); return true; }); assert.equal(created, 0); await runtime.close(); }); test("destroyed transports report unavailable evidence without replacing the failure", async () => { const store = artifacts(); const transport: BrowserTransport = { async dispatch(request: BrowserTransportRequest): Promise> { if (request.operation.op === "captureEvidence") throw new Error("browser bridge is closed"); throw new Error("browser worker request timed out; worker was torn down"); }, async close() {}, }; const runtime = createBrowserRuntimeFactory({ create() { return transport; } }).create(options(store.store, transport)); const controller = new AbortController(); controller.abort(new Error("operation deadline exceeded")); await assert.rejects(runtime.observe(controller.signal), (error: any) => { assert.equal(error.details.code, "aborted"); assert.deepEqual(error.failureEvidence, { status: "unavailable", reason: "browser session is unavailable" }); return true; }); await runtime.close(); }); test("factory forwards pinned Python selection to the transport", async () => { const store = artifacts(); const fixture = transportFixture(); let received: unknown; const runtime = createBrowserRuntimeFactory({ create(transportOptions) { received = transportOptions; return fixture.transport; } }).create({ ...options(store.store, fixture.transport), python: "/tmp/pinned-python" }); await runtime.observe(new AbortController().signal); assert.equal((received as any).python, "/tmp/pinned-python"); await runtime.close(); }); test("narrowing is local, lazy, and propagates only its effective context", async () => { const store = artifacts(); const fixture = transportFixture(); let created = 0; const runtime = createBrowserRuntimeFactory({ create() { created++; return fixture.transport; } }).create(options(store.store, fixture.transport)); const context = await runtime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal); assert.deepEqual(context, { allowedOrigins: ["https://example.test"] }); assert.equal(created, 0); assert.equal(fixture.calls.length, 0); await runtime.read({}, new AbortController().signal, context); await runtime.read({}, new AbortController().signal); assert.equal(created, 1); assert.deepEqual(fixture.calls[0]?.context, { allowedOrigins: ["https://example.test"] }); assert.equal(fixture.calls[1]?.context, undefined); assert.equal(fixture.calls.some((call) => call.operation.op === "narrowOriginPolicy"), false); await runtime.close(); }); test("narrowing validates invalid and broadened policies without dispatch", async () => { const store = artifacts(); const fixture = transportFixture(); let created = 0; const runtime = createBrowserRuntimeFactory({ create() { created++; return fixture.transport; } }).create({ ...options(store.store, fixture.transport), allowedOrigins: ["https://example.test"], }); await assert.rejects(runtime.narrowOriginPolicy({ allowedOrigins: [] }, new AbortController().signal), /must not be empty/); await assert.rejects(runtime.narrowOriginPolicy({ allowedOrigins: "*" }, new AbortController().signal), (error: unknown) => { assert.ok(error instanceof BrowserOperationError); assert.equal(error.details.code, "policy_denied"); assert.equal(error.details.mutationState, "not_attempted"); return true; }); assert.equal(created, 0); assert.equal(fixture.calls.length, 0); await runtime.close(); }); test("narrowing preserves closed and aborted lifecycle errors", async () => { const store = artifacts(); const fixture = transportFixture(); const runtime = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create(options(store.store, fixture.transport)); await runtime.close(); await assert.rejects(runtime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal), (error: unknown) => { assert.ok(error instanceof BrowserOperationError); assert.equal(error.details.code, "browser_closed"); return true; }); const parent = new AbortController(); parent.abort(new Error("browser session closed")); const abortedRuntime = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create(options(store.store, fixture.transport), parent.signal); await assert.rejects(abortedRuntime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal), (error: unknown) => { assert.ok(error instanceof BrowserOperationError); assert.equal(error.details.code, "browser_closed"); return true; }); const operation = new AbortController(); operation.abort(); const liveRuntime = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create(options(store.store, fixture.transport)); await assert.rejects(liveRuntime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, operation.signal), (error: unknown) => { assert.ok(error instanceof Error); assert.equal(error.name, "AbortError"); return true; }); await abortedRuntime.close(); await liveRuntime.close(); }); test("narrowing remains serialized behind an in-flight transport operation", async () => { const store = artifacts(); const fixture = transportFixture(); let releaseRead!: () => void; let readStarted!: () => void; const readGate = new Promise((resolve) => { releaseRead = resolve; }); const started = new Promise((resolve) => { readStarted = resolve; }); const transport: BrowserTransport = { async dispatch(request: BrowserTransportRequest, signal: AbortSignal): Promise> { if (request.operation.op === "read") { readStarted(); await readGate; } return fixture.transport.dispatch(request, signal); }, async close() { return fixture.transport.close(); }, }; const runtime = createBrowserRuntimeFactory({ create() { return transport; } }).create(options(store.store, transport)); const read = runtime.read({}, new AbortController().signal); await started; let narrowed = false; const narrow = runtime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal).then(() => { narrowed = true; }); await Promise.resolve(); assert.equal(narrowed, false); releaseRead(); await Promise.all([read, narrow]); assert.equal(narrowed, true); await runtime.close(); }); test("policy is enforced before navigation and tabs are origin filtered", async () => { const store = artifacts(); const fixture = transportFixture(); const runtime = createBrowserRuntimeFactory(options(store.store, fixture.transport).transportFactory).create(options(store.store, fixture.transport)); const context = await runtime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal); await assert.rejects(runtime.navigate({ url: "https://other.test/out" }, new AbortController().signal, context), (error: any) => error.details.code === "policy_denied"); const restricted = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create({ ...options(store.store, fixture.transport), allowedOrigins: ["https://example.test"] }); await assert.rejects(restricted.navigate({ url: "https://example.test/out" }, new AbortController().signal, { allowedOrigins: "*" }), (error: any) => { assert.equal(error.details.code, "policy_denied"); assert.equal(error.details.mutationState, "not_attempted"); assert.equal(error.details.retry, "never"); return true; }); const tabs = await runtime.listTabs(new AbortController().signal, context); assert.deepEqual(tabs.map((item) => item.origin), ["https://example.test"]); await runtime.close(); await restricted.close(); }); test("requested evidence commits local metadata and redacts untrusted console text", async () => { const store = artifacts(); const fixture = transportFixture(); const runtime = createBrowserRuntimeFactory(options(store.store, fixture.transport).transportFactory).create(options(store.store, fixture.transport)); const result = await runtime.captureEvidence({ runId: "run-1", reason: "requested" }, new AbortController().signal); assert.equal(result.screenshot?.bytes, 2); assert.equal(result.screenshot?.modelForwarded, false); assert.equal(result.console[0]?.message.includes("secret"), false); assert.equal(store.committed.length, 1); await runtime.close(); }); test("transport failures become safe errors and are not mutation retried", async () => { const store = artifacts(); let actions = 0; const transport: BrowserTransport = { async dispatch(request: BrowserTransportRequest): Promise> { actions++; if (request.operation.op === "captureEvidence") return { op: "captureEvidence", result: { status: "unavailable", console: [], failedRequests: [] } } as unknown as BrowserTransportResultFor; throw new Error("private page secret and mutation may have run"); }, async close() {}, }; const runtime = createBrowserRuntimeFactory({ create() { return transport; } }).create(options(store.store, transport)); await assert.rejects(runtime.act({ observationId: "o", candidateId: "c", action: "click", postcondition: { kind: "text", value: "done" } }, new AbortController().signal), (error: any) => { assert.equal(error.details.code, "worker_failure"); assert.equal(error.details.mutationState, "unknown"); assert.equal(String(error).includes("private"), false); return true; }); assert.equal(actions, 2, "one action dispatch plus one best-effort evidence request; no mutation retry"); await runtime.close(); });