diff --git a/src/browser/BrowserScenarioSessionPort.ts b/src/browser/BrowserScenarioSessionPort.ts index ea2fa0838..92299c049 100644 --- a/src/browser/BrowserScenarioSessionPort.ts +++ b/src/browser/BrowserScenarioSessionPort.ts @@ -35,11 +35,8 @@ export interface BrowserScenarioSessionPort { redactError(error: unknown): string; } -export interface BrowserScenarioSessionFactory { - open( - scenario: BrowserScenario, - options?: { - readonly signal?: AbortSignal; - }, - ): Promise; -} +/** Acquire one scenario session whose lifecycle the caller owns. */ +export type BrowserScenarioSessionOpener = ( + scenario: BrowserScenario, + options: { readonly signal?: AbortSignal }, +) => Promise; diff --git a/src/browser/PlaywrightBrowserScenarioProvider.test.ts b/src/browser/PlaywrightBrowserScenarioProvider.test.ts index fd3cd8869..b66cecb20 100644 --- a/src/browser/PlaywrightBrowserScenarioProvider.test.ts +++ b/src/browser/PlaywrightBrowserScenarioProvider.test.ts @@ -221,9 +221,9 @@ describe("PlaywrightBrowserScenarioProvider", () => { it("makes reported event gaps ineligible for equality even in launch mode", async () => { const session = new FakeSession("launch"); session.eventGaps = ["Popup frames before discovery are unavailable"]; - const provider = new PlaywrightBrowserScenarioProvider({ - open: () => Promise.resolve(session), - }); + const provider = new PlaywrightBrowserScenarioProvider(() => + Promise.resolve(session), + ); const result = await provider.captureScenario( scenario({ events: ["frames"] }), ); @@ -237,9 +237,9 @@ describe("PlaywrightBrowserScenarioProvider", () => { }); it("returns an initial state and every step beyond the former action ceiling", async () => { const session = new FakeSession("launch"); - const provider = new PlaywrightBrowserScenarioProvider({ - open: () => Promise.resolve(session), - }); + const provider = new PlaywrightBrowserScenarioProvider(() => + Promise.resolve(session), + ); const result = await provider.captureScenario(scenario({ actions: 129 })); if (!result.ok) throw result.error; expect(result.value.scenario.action_count).toBe(129); @@ -249,9 +249,9 @@ describe("PlaywrightBrowserScenarioProvider", () => { it("makes missing and truncated captures ineligible for equality", async () => { const session = new FakeSession("launch", { incomplete: true }); - const provider = new PlaywrightBrowserScenarioProvider({ - open: () => Promise.resolve(session), - }); + const provider = new PlaywrightBrowserScenarioProvider(() => + Promise.resolve(session), + ); const result = await provider.captureScenario( scenario({ captures: ["screenshot", "dom"] }), ); @@ -268,9 +268,9 @@ describe("PlaywrightBrowserScenarioProvider", () => { const session = new FakeSession("launch", { action: new Error("fixture secret action failure"), }); - const provider = new PlaywrightBrowserScenarioProvider({ - open: () => Promise.resolve(session), - }); + const provider = new PlaywrightBrowserScenarioProvider(() => + Promise.resolve(session), + ); const result = await provider.captureScenario(scenario({ actions: 2 })); if (!result.ok) throw result.error; expect(result.value.steps.map(({ status }) => status)).toEqual([ @@ -288,9 +288,9 @@ describe("PlaywrightBrowserScenarioProvider", () => { it("disconnects external CDP sessions and marks pre-attach events missing", async () => { const session = new FakeSession("connect"); - const provider = new PlaywrightBrowserScenarioProvider({ - open: () => Promise.resolve(session), - }); + const provider = new PlaywrightBrowserScenarioProvider(() => + Promise.resolve(session), + ); const result = await provider.captureScenario( scenario({ mode: "connect" }), ); @@ -314,9 +314,9 @@ describe("PlaywrightBrowserScenarioProvider cleanup", () => { action: new Error("fixture secret action failure"), close: new Error("browser close failed"), }); - const provider = new PlaywrightBrowserScenarioProvider({ - open: () => Promise.resolve(session), - }); + const provider = new PlaywrightBrowserScenarioProvider(() => + Promise.resolve(session), + ); const result = await provider.captureScenario(scenario()); expect(result.ok).toBe(false); @@ -378,9 +378,9 @@ describe("PlaywrightBrowserScenarioProvider operation failures", () => { url: sanitizeBrowserUrl("https://app.example.test/"), }, ]; - const provider = new PlaywrightBrowserScenarioProvider({ - open: () => Promise.resolve(session), - }); + const provider = new PlaywrightBrowserScenarioProvider(() => + Promise.resolve(session), + ); const result = await provider.captureScenario(scenario()); expect(result.ok).toBe(false); @@ -462,9 +462,9 @@ describe("PlaywrightBrowserScenarioProvider cancellation and timeout", () => { const session = new FakeSession("launch", { capture: { call: 1, error }, }); - const provider = new PlaywrightBrowserScenarioProvider({ - open: () => Promise.resolve(session), - }); + const provider = new PlaywrightBrowserScenarioProvider(() => + Promise.resolve(session), + ); const result = await provider.captureScenario(scenario()); expect(result.ok).toBe(false); @@ -489,11 +489,9 @@ describe("PlaywrightBrowserScenarioProvider cancellation and timeout", () => { describe("PlaywrightBrowserScenarioProvider request cancellation", () => { it("does not open a session for an already-cancelled request", async () => { let opens = 0; - const provider = new PlaywrightBrowserScenarioProvider({ - open: () => { - opens += 1; - return Promise.resolve(new FakeSession("launch")); - }, + const provider = new PlaywrightBrowserScenarioProvider(() => { + opens += 1; + return Promise.resolve(new FakeSession("launch")); }); const controller = new AbortController(); controller.abort(); @@ -508,9 +506,9 @@ describe("PlaywrightBrowserScenarioProvider request cancellation", () => { const controller = new AbortController(); const session = new FakeSession("launch"); session.onPerform = () => controller.abort(); - const provider = new PlaywrightBrowserScenarioProvider({ - open: () => Promise.resolve(session), - }); + const provider = new PlaywrightBrowserScenarioProvider(() => + Promise.resolve(session), + ); const result = await provider.captureScenario(scenario(), { signal: controller.signal, }); diff --git a/src/browser/PlaywrightBrowserScenarioProvider.ts b/src/browser/PlaywrightBrowserScenarioProvider.ts index b62d3504f..8a8c75b7c 100644 --- a/src/browser/PlaywrightBrowserScenarioProvider.ts +++ b/src/browser/PlaywrightBrowserScenarioProvider.ts @@ -25,7 +25,7 @@ import { BrowserObservationError } from "../domain/browserObservationError.js"; import { ProviderAdapterError } from "../domain/providerAdapterError.js"; import { err, ok, type Result } from "../domain/result.js"; import type { - BrowserScenarioSessionFactory, + BrowserScenarioSessionOpener, BrowserScenarioSessionPort, } from "./BrowserScenarioSessionPort.js"; import { @@ -226,14 +226,14 @@ const globalCompleteness = ( }; const runScenario = async ( - factory: BrowserScenarioSessionFactory, + openSession: BrowserScenarioSessionOpener, scenario: BrowserScenario, options: ExecutionOptions, ): Promise => { if (options.signal?.aborted === true) throw new BrowserObservationError(OPERATION, "cancelled"); const startedAt = Date.now(); - const session = await factory.open( + const session = await openSession( scenario, options.signal === undefined ? {} : { signal: options.signal }, ); @@ -337,7 +337,7 @@ const runScenario = async ( /** Controlled Playwright/CDP scenario driver with exact process ownership. */ export class PlaywrightBrowserScenarioProvider implements BrowserScenarioCapturePort { - constructor(private readonly factory: BrowserScenarioSessionFactory) {} + constructor(private readonly openSession: BrowserScenarioSessionOpener) {} identity(): ProviderIdentity { return PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY; @@ -348,7 +348,7 @@ export class PlaywrightBrowserScenarioProvider implements BrowserScenarioCapture options: ExecutionOptions = {}, ): Promise> { try { - return ok(await runScenario(this.factory, scenario, options)); + return ok(await runScenario(this.openSession, scenario, options)); } catch (cause: unknown) { if (cause instanceof AnalysisError) return err(cause); return err( diff --git a/src/browser/PlaywrightScenarioBrowser.ts b/src/browser/PlaywrightScenarioBrowser.ts index 4cfdf2653..10942cbe2 100644 --- a/src/browser/PlaywrightScenarioBrowser.ts +++ b/src/browser/PlaywrightScenarioBrowser.ts @@ -25,7 +25,7 @@ export interface OpenedScenarioBrowser { } /** Provider actions retained until the owned browser and profile are released. */ -export interface PlaywrightScenarioCleanupResources { +interface PlaywrightScenarioCleanupResources { readonly closeBrowser: () => Promise; readonly removeProfile: (() => Promise) | undefined; } diff --git a/src/browser/PlaywrightScenarioSession.ts b/src/browser/PlaywrightScenarioSession.ts index 1c31ef344..26aa65fcd 100644 --- a/src/browser/PlaywrightScenarioSession.ts +++ b/src/browser/PlaywrightScenarioSession.ts @@ -3,10 +3,7 @@ import { type BrowserContext, type Page } from "playwright-core"; import type { BrowserScenario } from "../domain/browserScenario.js"; import type { BrowserScenarioAction } from "../domain/browserScenarioValues.js"; import { BrowserObservationError } from "../domain/browserObservationError.js"; -import type { - BrowserScenarioSessionFactory, - BrowserScenarioSessionPort, -} from "./BrowserScenarioSessionPort.js"; +import type { BrowserScenarioSessionPort } from "./BrowserScenarioSessionPort.js"; import { BrowserScenarioSecrets } from "./BrowserScenarioSecrets.js"; import { failBrowserScenarioOperation, @@ -311,19 +308,3 @@ export class PlaywrightScenarioSession implements BrowserScenarioSessionPort { ); } } - -/** Production Playwright/CDP session factory. */ -export class PlaywrightScenarioSessionFactory implements BrowserScenarioSessionFactory { - constructor( - private readonly environment: Readonly>, - ) {} - - open( - scenario: BrowserScenario, - options: { - readonly signal?: AbortSignal; - } = {}, - ): Promise { - return PlaywrightScenarioSession.open(scenario, this.environment, options); - } -} diff --git a/src/composition/browserScenario.ts b/src/composition/browserScenario.ts index 14d78c209..8a4aa5bc9 100644 --- a/src/composition/browserScenario.ts +++ b/src/composition/browserScenario.ts @@ -1,11 +1,11 @@ import type { BrowserScenarioCapturePort } from "../application/BrowserScenarioCapturePort.js"; import { PlaywrightBrowserScenarioProvider } from "../browser/PlaywrightBrowserScenarioProvider.js"; -import { PlaywrightScenarioSessionFactory } from "../browser/PlaywrightScenarioSession.js"; +import { PlaywrightScenarioSession } from "../browser/PlaywrightScenarioSession.js"; /** Construct a fresh provider without opening a target or acquiring an engine. */ export const createBrowserScenarioProvider = ( environment: Readonly>, ): BrowserScenarioCapturePort => - new PlaywrightBrowserScenarioProvider( - new PlaywrightScenarioSessionFactory(environment), + new PlaywrightBrowserScenarioProvider((scenario, options) => + PlaywrightScenarioSession.open(scenario, environment, options), );