fix(browser): capture selected events in popup pages (#600)

Selected page events from popups are retained even when popup lifecycle events are unselected; popup opened/closed events remain selection-controlled.
This commit is contained in:
Rudy Mizrahi Celekli 2026-10-05 13:51:10 -04:00 committed by GitHub
parent 2919def828
commit a3bf4a0944
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 50 additions and 10 deletions

View File

@ -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<string, (value: unknown) => void>();
const popupListeners = new Map<string, (value: unknown) => 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<string, (value: Error) => void>();
const page = {

View File

@ -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);
}