Files
reverse-engineering-anything/src/browser/PlaywrightScenarioEvents.sharedContext.test.ts
T

128 lines
5.1 KiB
TypeScript

import { EventEmitter } from "node:events";
import type { Page } from "playwright-core";
import { expect, it } from "vitest";
import { browserScenarioSchema } from "../domain/browserScenario.js";
import { resolveScenarioSecrets } from "../../tests/fixtures/browserScenarioSecrets.js";
import { PlaywrightScenarioEvents } from "./PlaywrightScenarioEvents.js";
it("omits a request without a frame in a shared context", () => {
const context = new EventEmitter();
const page = new EventEmitter() 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: ["network"] },
});
const secrets = resolveScenarioSecrets(scenario, {});
const events = new PlaywrightScenarioEvents({
page,
context: context as never,
ownsContext: false,
enabled: new Set(["network"]),
secrets,
});
const unknownRequest = {
frame: () => {
throw new Error("Frame for this navigation request is unavailable");
},
url: () => "https://unrelated.example.test/private",
};
context.emit("request", unknownRequest);
expect(events.result().items).toEqual([]);
const limitations = events.limitations().join("\n");
expect(limitations).toContain("Shared-context network capture");
expect(limitations).toContain("excludes requests");
expect(limitations).toContain("without a known root or descendant");
});
it("detaches root and popup listeners when a scenario finishes", async () => {
const context = new EventEmitter();
const page = new EventEmitter() as unknown as Page;
const popup = Object.assign(new EventEmitter(), {
url: () => "https://app.example.test/popup",
});
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: ["console", "popups"] },
});
const secrets = resolveScenarioSecrets(scenario, {});
const events = new PlaywrightScenarioEvents({
page,
context: context as never,
ownsContext: false,
enabled: new Set(["console", "popups"]),
secrets,
});
(page as unknown as EventEmitter).emit("popup", popup);
expect((page as unknown as EventEmitter).listenerCount("console")).toBe(1);
expect(popup.listenerCount("console")).toBe(1);
// Popup closure is recorded, then its page-owned listeners are released.
expect(popup.listenerCount("close")).toBe(2);
await events.finish();
expect((page as unknown as EventEmitter).listenerCount("console")).toBe(0);
expect((page as unknown as EventEmitter).listenerCount("popup")).toBe(0);
expect(popup.listenerCount("console")).toBe(0);
expect(popup.listenerCount("popup")).toBe(0);
expect(popup.listenerCount("close")).toBe(0);
});
it("detaches worker and websocket listeners when a scenario finishes", async () => {
const context = new EventEmitter();
const page = new EventEmitter() as unknown as Page;
const socket = Object.assign(new EventEmitter(), {
url: () => "wss://app.example.test/socket",
});
const worker = Object.assign(new EventEmitter(), {
url: () => "https://app.example.test/worker.js",
});
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: ["websockets", "workers"] },
});
const secrets = resolveScenarioSecrets(scenario, {});
const events = new PlaywrightScenarioEvents({
page,
context: context as never,
ownsContext: false,
enabled: new Set(["websockets", "workers"]),
secrets,
});
(page as unknown as EventEmitter).emit("websocket", socket);
(page as unknown as EventEmitter).emit("worker", worker);
expect(socket.listenerCount("framesent")).toBe(1);
expect(worker.listenerCount("close")).toBe(1);
await events.finish();
expect(socket.listenerCount("framesent")).toBe(0);
expect(socket.listenerCount("framereceived")).toBe(0);
expect(socket.listenerCount("close")).toBe(0);
expect(worker.listenerCount("close")).toBe(0);
});
it("detaches owned-context popup discovery when a scenario finishes", async () => {
const context = new EventEmitter();
const page = new EventEmitter() 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: ["console"] },
});
const secrets = resolveScenarioSecrets(scenario, {});
const events = new PlaywrightScenarioEvents({
page,
context: context as never,
ownsContext: true,
enabled: new Set(["console"]),
secrets,
});
expect(context.listenerCount("page")).toBe(1);
await events.finish();
expect(context.listenerCount("page")).toBe(0);
});