Skip to content

Commit 32cff87

Browse files
committed
Added on.('network' listener)
1 parent 1ebc7e1 commit 32cff87

File tree

4 files changed

+769
-13
lines changed

4 files changed

+769
-13
lines changed
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
import { test, expect } from "@playwright/test";
2+
import { V3 } from "../v3";
3+
import { v3TestConfig } from "./v3.config";
4+
5+
test.describe("Page Network Events", () => {
6+
let v3: V3;
7+
8+
test.beforeEach(async () => {
9+
v3 = new V3(v3TestConfig);
10+
await v3.init();
11+
});
12+
13+
test.afterEach(async () => {
14+
await v3?.close?.().catch(() => {});
15+
});
16+
17+
test("should capture network request events", async () => {
18+
const page = v3.context.pages()[0];
19+
const requests: any[] = [];
20+
21+
page.on("network", (message) => {
22+
if (message.type() === "request") {
23+
requests.push(message);
24+
}
25+
});
26+
27+
await page.goto("https://example.com");
28+
29+
expect(requests.length).toBeGreaterThan(0);
30+
const mainRequest = requests.find((r) => r.url().includes("example.com"));
31+
expect(mainRequest).toBeDefined();
32+
expect(mainRequest?.method()).toBe("GET");
33+
});
34+
35+
test("should capture network response events", async () => {
36+
const page = v3.context.pages()[0];
37+
const responses: any[] = [];
38+
39+
page.on("network", (message) => {
40+
if (message.type() === "response") {
41+
responses.push(message);
42+
}
43+
});
44+
45+
await page.goto("https://example.com");
46+
47+
expect(responses.length).toBeGreaterThan(0);
48+
const mainResponse = responses.find((r) => r.url().includes("example.com"));
49+
expect(mainResponse).toBeDefined();
50+
expect(mainResponse?.status()).toBe(200);
51+
expect(mainResponse?.statusText()).toBeDefined();
52+
});
53+
54+
test("should provide resource type information", async () => {
55+
const page = v3.context.pages()[0];
56+
const messages: any[] = [];
57+
58+
page.on("network", (message) => {
59+
messages.push(message);
60+
});
61+
62+
await page.goto("https://example.com");
63+
64+
const documentRequest = messages.find(
65+
(m) => m.resourceType() === "Document",
66+
);
67+
expect(documentRequest).toBeDefined();
68+
});
69+
70+
test("should support once() for single event", async () => {
71+
const page = v3.context.pages()[0];
72+
let callCount = 0;
73+
74+
page.once("network", (message) => {
75+
callCount++;
76+
expect(message).toBeDefined();
77+
});
78+
79+
await page.goto("https://example.com");
80+
81+
// Even though multiple network events occur, once() should only fire once
82+
expect(callCount).toBe(1);
83+
});
84+
85+
test("should support removing listeners with off()", async () => {
86+
const page = v3.context.pages()[0];
87+
let callCount = 0;
88+
89+
const listener = (message: any) => {
90+
callCount++;
91+
};
92+
93+
page.on("network", listener);
94+
await page.goto("https://example.com");
95+
96+
const firstCallCount = callCount;
97+
expect(firstCallCount).toBeGreaterThan(0);
98+
99+
page.off("network", listener);
100+
callCount = 0;
101+
102+
await page.goto("https://example.com");
103+
expect(callCount).toBe(0);
104+
});
105+
106+
test("should provide request headers", async () => {
107+
const page = v3.context.pages()[0];
108+
let foundHeaders = false;
109+
110+
page.on("network", (message) => {
111+
if (message.type() === "request") {
112+
const headers = message.requestHeaders();
113+
if (headers && Object.keys(headers).length > 0) {
114+
foundHeaders = true;
115+
}
116+
}
117+
});
118+
119+
await page.goto("https://example.com");
120+
expect(foundHeaders).toBe(true);
121+
});
122+
123+
test("should provide response headers", async () => {
124+
const page = v3.context.pages()[0];
125+
let foundHeaders = false;
126+
127+
page.on("network", (message) => {
128+
if (message.type() === "response") {
129+
const headers = message.responseHeaders();
130+
if (headers && Object.keys(headers).length > 0) {
131+
foundHeaders = true;
132+
}
133+
}
134+
});
135+
136+
await page.goto("https://example.com");
137+
expect(foundHeaders).toBe(true);
138+
});
139+
140+
test("should provide MIME type for responses", async () => {
141+
const page = v3.context.pages()[0];
142+
let foundMimeType = false;
143+
144+
page.on("network", (message) => {
145+
if (message.type() === "response") {
146+
const mimeType = message.mimeType();
147+
if (mimeType && mimeType.includes("text/html")) {
148+
foundMimeType = true;
149+
}
150+
}
151+
});
152+
153+
await page.goto("https://example.com");
154+
expect(foundMimeType).toBe(true);
155+
});
156+
157+
test("should track frame and loader IDs", async () => {
158+
const page = v3.context.pages()[0];
159+
let hasFrameId = false;
160+
let hasLoaderId = false;
161+
162+
page.on("network", (message) => {
163+
if (message.frameId()) hasFrameId = true;
164+
if (message.loaderId()) hasLoaderId = true;
165+
});
166+
167+
await page.goto("https://example.com");
168+
expect(hasFrameId).toBe(true);
169+
expect(hasLoaderId).toBe(true);
170+
});
171+
172+
test("should provide unique request IDs", async () => {
173+
const page = v3.context.pages()[0];
174+
const requestIds = new Set<string>();
175+
176+
page.on("network", (message) => {
177+
requestIds.add(message.requestId());
178+
});
179+
180+
await page.goto("https://example.com");
181+
expect(requestIds.size).toBeGreaterThan(0);
182+
});
183+
184+
test("should support toString() method", async () => {
185+
const page = v3.context.pages()[0];
186+
let foundToString = false;
187+
188+
page.on("network", (message) => {
189+
const str = message.toString();
190+
expect(typeof str).toBe("string");
191+
expect(str.length).toBeGreaterThan(0);
192+
if (str.includes("Request") || str.includes("Response")) {
193+
foundToString = true;
194+
}
195+
});
196+
197+
await page.goto("https://example.com");
198+
expect(foundToString).toBe(true);
199+
});
200+
201+
test("should provide page reference", async () => {
202+
const page = v3.context.pages()[0];
203+
let hasPageRef = false;
204+
205+
page.on("network", (message) => {
206+
const messagePage = message.page();
207+
if (messagePage === page) {
208+
hasPageRef = true;
209+
}
210+
});
211+
212+
await page.goto("https://example.com");
213+
expect(hasPageRef).toBe(true);
214+
});
215+
216+
test("should capture both requests and responses for same URL", async () => {
217+
const page = v3.context.pages()[0];
218+
const events: { type: string; url: string }[] = [];
219+
220+
page.on("network", (message) => {
221+
events.push({
222+
type: message.type(),
223+
url: message.url(),
224+
});
225+
});
226+
227+
await page.goto("https://example.com");
228+
229+
const exampleEvents = events.filter((e) => e.url.includes("example.com"));
230+
const hasRequest = exampleEvents.some((e) => e.type === "request");
231+
const hasResponse = exampleEvents.some((e) => e.type === "response");
232+
233+
expect(hasRequest).toBe(true);
234+
expect(hasResponse).toBe(true);
235+
});
236+
237+
test("should work across multiple pages", async () => {
238+
const page1 = v3.context.pages()[0];
239+
const page2 = await v3.context.newPage();
240+
241+
const page1Events: string[] = [];
242+
const page2Events: string[] = [];
243+
244+
page1.on("network", (message) => {
245+
page1Events.push(message.url());
246+
});
247+
248+
page2.on("network", (message) => {
249+
page2Events.push(message.url());
250+
});
251+
252+
await page1.goto("https://example.com");
253+
await page2.goto("https://httpbin.org/html");
254+
255+
expect(page1Events.some((url) => url.includes("example.com"))).toBe(true);
256+
expect(page1Events.some((url) => url.includes("httpbin.org"))).toBe(false);
257+
258+
expect(page2Events.some((url) => url.includes("httpbin.org"))).toBe(true);
259+
expect(page2Events.some((url) => url.includes("example.com"))).toBe(false);
260+
261+
await page2.close();
262+
});
263+
264+
test("should support multiple simultaneous listeners", async () => {
265+
const page = v3.context.pages()[0];
266+
let listener1Called = false;
267+
let listener2Called = false;
268+
let listener3Called = false;
269+
270+
page.on("network", () => {
271+
listener1Called = true;
272+
});
273+
274+
page.on("network", () => {
275+
listener2Called = true;
276+
});
277+
278+
page.on("network", () => {
279+
listener3Called = true;
280+
});
281+
282+
await page.goto("https://example.com");
283+
284+
expect(listener1Called).toBe(true);
285+
expect(listener2Called).toBe(true);
286+
expect(listener3Called).toBe(true);
287+
});
288+
289+
test("should handle errors in listeners gracefully", async () => {
290+
const page = v3.context.pages()[0];
291+
let goodListenerCalled = false;
292+
293+
page.on("network", () => {
294+
throw new Error("Listener error");
295+
});
296+
297+
page.on("network", () => {
298+
goodListenerCalled = true;
299+
});
300+
301+
await page.goto("https://example.com");
302+
303+
// The second listener should still be called even if first throws
304+
expect(goodListenerCalled).toBe(true);
305+
});
306+
307+
test("should filter by resource type", async () => {
308+
const page = v3.context.pages()[0];
309+
const documentRequests: any[] = [];
310+
const imageRequests: any[] = [];
311+
312+
page.on("network", (message) => {
313+
if (message.resourceType() === "Document") {
314+
documentRequests.push(message);
315+
} else if (message.resourceType() === "Image") {
316+
imageRequests.push(message);
317+
}
318+
});
319+
320+
await page.goto("https://example.com");
321+
322+
expect(documentRequests.length).toBeGreaterThan(0);
323+
});
324+
325+
test("should provide POST data for POST requests", async () => {
326+
const page = v3.context.pages()[0];
327+
let foundPostData = false;
328+
329+
page.on("network", (message) => {
330+
if (message.type() === "request" && message.method() === "POST") {
331+
const postData = message.postData();
332+
if (postData) {
333+
foundPostData = true;
334+
}
335+
}
336+
});
337+
338+
await page.goto("https://httpbin.org/forms/post");
339+
await page.evaluate(() => {
340+
const form = document.querySelector("form");
341+
if (form) {
342+
const input = form.querySelector(
343+
'input[name="custname"]',
344+
) as HTMLInputElement;
345+
if (input) input.value = "test";
346+
form.submit();
347+
}
348+
});
349+
350+
await page.waitForLoadState("load").catch(() => {});
351+
352+
// POST data may or may not be captured depending on timing and form behavior
353+
// Soft expectation - POST data capture is timing-dependent
354+
expect.soft(foundPostData).toBe(true);
355+
});
356+
});

packages/core/lib/v3/types/public/page.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,8 @@ export type AnyPage = PlaywrightPage | PuppeteerPage | PatchrightPage | Page;
99
export { ConsoleMessage } from "../../understudy/consoleMessage";
1010
export type { ConsoleListener } from "../../understudy/consoleMessage";
1111

12+
export { NetworkMessage } from "../../understudy/networkMessage";
13+
export type { NetworkListener } from "../../understudy/networkMessage";
14+
1215
export type LoadState = "load" | "domcontentloaded" | "networkidle";
1316
export { Response } from "../../understudy/response";

0 commit comments

Comments
 (0)