diff --git a/src/browser/PlaywrightScenarioEvents.test.ts b/src/browser/PlaywrightScenarioEvents.test.ts index 1db332e09..4c96e8d3d 100644 --- a/src/browser/PlaywrightScenarioEvents.test.ts +++ b/src/browser/PlaywrightScenarioEvents.test.ts @@ -6,6 +6,42 @@ import { BrowserScenarioSecrets } from "./BrowserScenarioSecrets.js"; import { PlaywrightScenarioEvents } from "./PlaywrightScenarioEvents.js"; describe("PlaywrightScenarioEvents", () => { + it("observes requested popup page errors independently of popup lifecycle events", () => { + const listeners = new Map void>(); + const popupListeners = new Map void>(); + const page = { + on: (name: string, listener: (value: unknown) => void) => { + listeners.set(name, listener); + }, + } as unknown as Page; + const popup = { + url: () => "https://app.example.test/popup", + on: (name: string, listener: (value: unknown) => void) => { + popupListeners.set(name, listener); + }, + } as unknown as Page; + const scenario = browserScenarioSchema.parse({ + browser: { mode: "launch", executable_path: "/opt/chromium" }, + start_url: { url: "https://app.example.test/" }, + actions: [ + { step_id: "wait", action: "wait_for_timeout", duration_ms: 1 }, + ], + capture: { events: ["page-errors"] }, + }); + const secrets = BrowserScenarioSecrets.resolve(scenario, {}); + if (secrets === undefined) throw new Error("Expected resolved secrets"); + const events = new PlaywrightScenarioEvents({ + page, + enabled: new Set(["page-errors"]), + secrets, + }); + listeners.get("popup")?.(popup); + popupListeners.get("pageerror")?.(new Error("popup application failure")); + expect(events.result().items).toMatchObject([ + { kind: "page-error", message: "popup application failure" }, + ]); + }); + it("bounds oversized page errors before validating captured events", () => { const listeners = new Map void>(); const page = { diff --git a/src/browser/PlaywrightScenarioEvents.ts b/src/browser/PlaywrightScenarioEvents.ts index db7466ab9..2b355c586 100644 --- a/src/browser/PlaywrightScenarioEvents.ts +++ b/src/browser/PlaywrightScenarioEvents.ts @@ -116,8 +116,10 @@ export class PlaywrightScenarioEvents { } if (this.enabled.has("workers")) page.on("worker", (worker) => this.worker(worker)); - if (this.enabled.has("popups")) - page.on("popup", (popup) => this.popup(popup)); + // Every selectable event can also be emitted by a child popup page, and a + // popup is only reachable through this one subscription, so any selection at + // all must observe popups. Lifecycle events stay gated on `popups` below. + if (this.enabled.size > 0) page.on("popup", (popup) => this.popup(popup)); if (this.enabled.has("downloads")) page.on("download", (download) => this.download(download)); } @@ -243,14 +245,16 @@ export class PlaywrightScenarioEvents { url: page.url() === "" ? null : this.safeUrl(page.url()), name: null, }; - this.push({ kind: "popup-opened", ...opened }); - page.on("close", () => - this.push({ - kind: "popup-closed", - url: page.url() === "" ? null : this.safeUrl(page.url()), - name: null, - }), - ); + if (this.enabled.has("popups")) { + this.push({ kind: "popup-opened", ...opened }); + page.on("close", () => + this.push({ + kind: "popup-closed", + url: page.url() === "" ? null : this.safeUrl(page.url()), + name: null, + }), + ); + } this.observePage(page); }