whopper 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/analyzer/apply.d.ts.map +1 -1
  2. package/dist/analyzer/apply.js +11 -3
  3. package/dist/analyzer/apply.js.map +1 -1
  4. package/dist/analyzer/apply.test.js +69 -0
  5. package/dist/analyzer/apply.test.js.map +1 -1
  6. package/dist/analyzer/match.d.ts +1 -0
  7. package/dist/analyzer/match.d.ts.map +1 -1
  8. package/dist/analyzer/match.js +1 -0
  9. package/dist/analyzer/match.js.map +1 -1
  10. package/dist/browser/active_scan.d.ts +5 -0
  11. package/dist/browser/active_scan.d.ts.map +1 -0
  12. package/dist/browser/active_scan.js +73 -0
  13. package/dist/browser/active_scan.js.map +1 -0
  14. package/dist/browser/active_scan.test.d.ts +2 -0
  15. package/dist/browser/active_scan.test.d.ts.map +1 -0
  16. package/dist/browser/active_scan.test.js +207 -0
  17. package/dist/browser/active_scan.test.js.map +1 -0
  18. package/dist/browser/index.d.ts.map +1 -1
  19. package/dist/browser/index.js +148 -36
  20. package/dist/browser/index.js.map +1 -1
  21. package/dist/browser/index.test.js +141 -3
  22. package/dist/browser/index.test.js.map +1 -1
  23. package/dist/browser/types.d.ts +1 -0
  24. package/dist/browser/types.d.ts.map +1 -1
  25. package/dist/commands/active_scan_runner.d.ts +5 -0
  26. package/dist/commands/active_scan_runner.d.ts.map +1 -0
  27. package/dist/commands/active_scan_runner.js +28 -0
  28. package/dist/commands/active_scan_runner.js.map +1 -0
  29. package/dist/commands/active_scan_runner.test.d.ts +2 -0
  30. package/dist/commands/active_scan_runner.test.d.ts.map +1 -0
  31. package/dist/commands/active_scan_runner.test.js +80 -0
  32. package/dist/commands/active_scan_runner.test.js.map +1 -0
  33. package/dist/commands/detect.d.ts.map +1 -1
  34. package/dist/commands/detect.js +7 -0
  35. package/dist/commands/detect.js.map +1 -1
  36. package/dist/commands/detect.test.js +12 -0
  37. package/dist/commands/detect.test.js.map +1 -1
  38. package/dist/signatures/_types.d.ts +7 -0
  39. package/dist/signatures/_types.d.ts.map +1 -1
  40. package/dist/signatures/signatures.test.js +24 -2
  41. package/dist/signatures/signatures.test.js.map +1 -1
  42. package/dist/signatures/technologies/lodash.d.ts.map +1 -1
  43. package/dist/signatures/technologies/lodash.js +1 -0
  44. package/dist/signatures/technologies/lodash.js.map +1 -1
  45. package/dist/signatures/technologies/lodash.test.d.ts +2 -0
  46. package/dist/signatures/technologies/lodash.test.d.ts.map +1 -0
  47. package/dist/signatures/technologies/lodash.test.js +104 -0
  48. package/dist/signatures/technologies/lodash.test.js.map +1 -0
  49. package/dist/signatures/technologies/magento.d.ts.map +1 -1
  50. package/dist/signatures/technologies/magento.js +6 -0
  51. package/dist/signatures/technologies/magento.js.map +1 -1
  52. package/dist/signatures/technologies/magento.test.d.ts +2 -0
  53. package/dist/signatures/technologies/magento.test.d.ts.map +1 -0
  54. package/dist/signatures/technologies/magento.test.js +28 -0
  55. package/dist/signatures/technologies/magento.test.js.map +1 -0
  56. package/dist/signatures/technologies/underscore_js.d.ts.map +1 -1
  57. package/dist/signatures/technologies/underscore_js.js +2 -0
  58. package/dist/signatures/technologies/underscore_js.js.map +1 -1
  59. package/dist/signatures/technologies/underscore_js.test.d.ts +2 -0
  60. package/dist/signatures/technologies/underscore_js.test.d.ts.map +1 -0
  61. package/dist/signatures/technologies/underscore_js.test.js +94 -0
  62. package/dist/signatures/technologies/underscore_js.test.js.map +1 -0
  63. package/package.json +1 -1
@@ -3,6 +3,15 @@ import { chromium } from "playwright";
3
3
  import { logger } from "../logger/index.js";
4
4
  import { extractJsVariables, getHostFromUrl, isFirstPartyHost, isSameHost, sleep, } from "./utils.js";
5
5
  const MAX_REDIRECT_HOPS = 20;
6
+ // Quiet period (ms) used for the custom network-idle decision.
7
+ // Playwright's built-in `networkidle` has a fixed 500ms threshold, which
8
+ // is too short: it fires before script-driven secondary requests (e.g. a
9
+ // CSS file loaded from within an async script) have a chance to start,
10
+ // causing technology-detection evidence to be dropped. We use a longer
11
+ // threshold evaluated by our own tracking.
12
+ const NETWORK_IDLE_THRESHOLD_MS = 2000;
13
+ // Polling interval (ms) for the idle-decision loop.
14
+ const NETWORK_IDLE_POLL_INTERVAL_MS = 200;
6
15
  function colorizeStatusCode(statusCode) {
7
16
  const code = String(statusCode);
8
17
  if (statusCode >= 100 && statusCode < 200) {
@@ -23,7 +32,7 @@ function colorizeStatusCode(statusCode) {
23
32
  return chalk.gray(code);
24
33
  }
25
34
  export async function openPage(url, timeoutMs, javascriptVariableNames, options = {}) {
26
- const { userAgent, locale, extraHTTPHeaders, blockCrossDomainRedirect = false, } = options;
35
+ const { userAgent, locale, extraHTTPHeaders, blockCrossDomainRedirect = false, networkIdleThresholdMs = NETWORK_IDLE_THRESHOLD_MS, } = options;
27
36
  const pageHost = getHostFromUrl(url);
28
37
  if (!pageHost) {
29
38
  throw new Error(`Invalid URL: ${url}`);
@@ -68,8 +77,7 @@ export async function openPage(url, timeoutMs, javascriptVariableNames, options
68
77
  await route.continue();
69
78
  return;
70
79
  }
71
- if (blockCrossDomainRedirect &&
72
- !isSameHost(pageHost, targetHost)) {
80
+ if (blockCrossDomainRedirect && !isSameHost(pageHost, targetHost)) {
73
81
  logger.warn(`Blocked cross-domain redirect: ${targetUrl}`);
74
82
  blockedByRedirectPolicy = true;
75
83
  urls.push({ url: targetUrl, error: "Blocked cross-domain redirect" });
@@ -104,7 +112,9 @@ export async function openPage(url, timeoutMs, javascriptVariableNames, options
104
112
  // URLs captured here are added to preInspectedUrls so that the
105
113
  // page.on("response") listener can skip them and avoid duplicates.
106
114
  const firstResponse = response;
107
- for (let hop = 0; hop < MAX_REDIRECT_HOPS && response.status() >= 300 && response.status() < 400; hop++) {
115
+ for (let hop = 0; hop < MAX_REDIRECT_HOPS &&
116
+ response.status() >= 300 &&
117
+ response.status() < 400; hop++) {
108
118
  // Capture intermediate 3xx responses so their headers (e.g. server,
109
119
  // x-powered-by) are available for analysis even though the browser
110
120
  // never sees these responses directly.
@@ -161,44 +171,146 @@ export async function openPage(url, timeoutMs, javascriptVariableNames, options
161
171
  });
162
172
  const urls = [];
163
173
  const responses = [];
164
- page.on("response", async (response) => {
165
- const responseUrl = response.url();
166
- const statusCode = response.status();
167
- // Skip responses already captured by the pre-inspection loop to
168
- // avoid duplicates.
169
- if (preInspectedUrls.has(responseUrl) && statusCode >= 300 && statusCode < 400) {
170
- logger.debug(`Skipping already captured response [${colorizeStatusCode(statusCode)}] ${responseUrl}`);
171
- return;
172
- }
173
- const responseHost = getHostFromUrl(responseUrl) ?? "";
174
- logger.debug(`Received response [${colorizeStatusCode(statusCode)}] ${responseUrl}`);
175
- const request = response.request();
176
- if (request.isNavigationRequest() && request.frame() === page.mainFrame()) {
177
- urls.push({ url: responseUrl, status: statusCode });
178
- }
179
- const res = {
180
- url: responseUrl,
181
- host: responseHost,
182
- isFirstParty: responseHost
183
- ? isFirstPartyHost(pageHost, responseHost)
184
- : false,
185
- status: statusCode,
186
- headers: response.headers(),
187
- };
188
- const body = await response.text().catch(() => null);
189
- if (body) {
190
- res.body = body;
191
- }
192
- responses.push(res);
193
- });
174
+ // Timestamp of the most recent network activity (request sent or
175
+ // response received). Used by the custom idle-decision loop below.
176
+ let lastNetworkActivityAt = Date.now();
177
+ // Number of requests currently in flight. We include this in the idle
178
+ // condition so that a request whose response takes longer than the
179
+ // quiet-period threshold cannot cause the loop to exit before the
180
+ // response is captured.
181
+ let inFlightRequestCount = 0;
182
+ // Pending response-handler promises. The response handler awaits
183
+ // `response.text()`, which can still be in progress when the idle loop
184
+ // decides to break; awaiting this set after the loop prevents those
185
+ // in-progress body reads from being dropped.
186
+ const pendingResponseWork = new Set();
187
+ const onRequest = () => {
188
+ inFlightRequestCount++;
189
+ lastNetworkActivityAt = Date.now();
190
+ };
191
+ const onRequestFinished = () => {
192
+ inFlightRequestCount = Math.max(0, inFlightRequestCount - 1);
193
+ lastNetworkActivityAt = Date.now();
194
+ };
195
+ const onRequestFailed = () => {
196
+ inFlightRequestCount = Math.max(0, inFlightRequestCount - 1);
197
+ lastNetworkActivityAt = Date.now();
198
+ };
199
+ const onResponse = (response) => {
200
+ lastNetworkActivityAt = Date.now();
201
+ const work = (async () => {
202
+ const responseUrl = response.url();
203
+ const statusCode = response.status();
204
+ // Skip responses already captured by the pre-inspection loop to
205
+ // avoid duplicates.
206
+ if (preInspectedUrls.has(responseUrl) &&
207
+ statusCode >= 300 &&
208
+ statusCode < 400) {
209
+ logger.debug(`Skipping already captured response [${colorizeStatusCode(statusCode)}] ${responseUrl}`);
210
+ return;
211
+ }
212
+ const responseHost = getHostFromUrl(responseUrl) ?? "";
213
+ logger.debug(`Received response [${colorizeStatusCode(statusCode)}] ${responseUrl}`);
214
+ const request = response.request();
215
+ if (request.isNavigationRequest() &&
216
+ request.frame() === page.mainFrame()) {
217
+ urls.push({ url: responseUrl, status: statusCode });
218
+ }
219
+ const res = {
220
+ url: responseUrl,
221
+ host: responseHost,
222
+ isFirstParty: responseHost
223
+ ? isFirstPartyHost(pageHost, responseHost)
224
+ : false,
225
+ status: statusCode,
226
+ headers: response.headers(),
227
+ };
228
+ const body = await response.text().catch(() => null);
229
+ if (body) {
230
+ res.body = body;
231
+ }
232
+ responses.push(res);
233
+ })();
234
+ pendingResponseWork.add(work);
235
+ work.finally(() => {
236
+ pendingResponseWork.delete(work);
237
+ });
238
+ };
239
+ page.on("request", onRequest);
240
+ page.on("requestfinished", onRequestFinished);
241
+ page.on("requestfailed", onRequestFailed);
242
+ page.on("response", onResponse);
194
243
  let timeoutOccurred = false;
195
- const goto = page.goto(url, { waitUntil: "networkidle" });
244
+ const navigationStartedAt = Date.now();
245
+ // Playwright's built-in `networkidle` is flaky (500ms fixed threshold),
246
+ // so resolve goto at the `load` event (onload fired) and wait for
247
+ // secondary requests triggered by deferred scripts using our own
248
+ // idle-decision loop below.
249
+ const goto = page.goto(url, { waitUntil: "load" });
196
250
  const result = await Promise.race([
197
251
  goto.then(() => "loaded").catch((e) => e.message),
198
252
  sleep(timeoutMs).then(() => "timeout"),
199
253
  ]);
254
+ // After onload, wait until all in-flight requests have finished and a
255
+ // quiet period of `networkIdleThresholdMs` has elapsed (or the overall
256
+ // timeout is reached) so that secondary requests triggered by deferred
257
+ // scripts are captured. Including in-flight count in the condition
258
+ // prevents the loop from exiting while a slow response
259
+ // (> networkIdleThresholdMs) is still pending.
260
+ //
261
+ // Tracks whether the loop exited because the network actually went
262
+ // idle, or because the overall timeout budget was exhausted. In the
263
+ // latter case we must return `timeoutOccurred: true` so callers can
264
+ // distinguish a full capture from a partial one.
265
+ let idleWaitTimedOut = false;
200
266
  if (result === "loaded") {
201
- logger.info("Page loaded successfully");
267
+ while (true) {
268
+ const now = Date.now();
269
+ const elapsed = now - navigationStartedAt;
270
+ if (elapsed >= timeoutMs) {
271
+ idleWaitTimedOut = true;
272
+ break;
273
+ }
274
+ const idleFor = now - lastNetworkActivityAt;
275
+ if (inFlightRequestCount === 0 && idleFor >= networkIdleThresholdMs) {
276
+ break;
277
+ }
278
+ const remainingBudget = timeoutMs - elapsed;
279
+ await sleep(Math.min(NETWORK_IDLE_POLL_INTERVAL_MS, remainingBudget));
280
+ }
281
+ // If any response handlers are still running at loop exit (e.g.
282
+ // waiting on `response.text()`), wait for them to finish so their
283
+ // captures are not dropped. However, do not wait beyond the overall
284
+ // timeout: race against the remaining budget so that when the budget
285
+ // is exhausted we drop any still-pending in-flight captures rather
286
+ // than letting the process block indefinitely on slow body reads.
287
+ if (pendingResponseWork.size > 0) {
288
+ const remainingBudget = Math.max(0, timeoutMs - (Date.now() - navigationStartedAt));
289
+ if (remainingBudget > 0) {
290
+ await Promise.race([
291
+ Promise.allSettled(Array.from(pendingResponseWork)),
292
+ sleep(remainingBudget),
293
+ ]);
294
+ }
295
+ }
296
+ }
297
+ // Detach network listeners so that the `responses` snapshot is frozen
298
+ // at this point. Subsequent activity from cookie/JS extraction (or any
299
+ // delayed in-flight responses) must not mutate the captured set, since
300
+ // those entries would not be awaited and could race with the return.
301
+ page.off("request", onRequest);
302
+ page.off("requestfinished", onRequestFinished);
303
+ page.off("requestfailed", onRequestFailed);
304
+ page.off("response", onResponse);
305
+ logger.info(`${responses.length} responses captured`);
306
+ if (result === "loaded") {
307
+ if (idleWaitTimedOut) {
308
+ timeoutOccurred = true;
309
+ logger.warn(`Timeout of ${timeoutMs}ms exceeded while waiting for network idle after load on ${url}`);
310
+ }
311
+ else {
312
+ logger.info("Page loaded successfully");
313
+ }
202
314
  }
203
315
  else if (result === "timeout") {
204
316
  timeoutOccurred = true;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { openPage } from "./index.js";
4
4
  vi.mock("playwright", () => {
5
5
  const mockPage = {
6
6
  on: vi.fn(),
7
+ off: vi.fn(),
7
8
  route: vi.fn(),
8
9
  mainFrame: vi.fn(),
9
10
  goto: vi.fn(),
@@ -55,6 +56,7 @@ describe("openPage", () => {
55
56
  // Get references to mocked objects
56
57
  mockPage = {
57
58
  on: vi.fn(),
59
+ off: vi.fn(),
58
60
  route: vi.fn(),
59
61
  mainFrame: vi.fn(),
60
62
  goto: vi.fn(() => Promise.resolve()),
@@ -90,7 +92,9 @@ describe("openPage", () => {
90
92
  });
91
93
  });
92
94
  it("should pass custom userAgent to browser context", async () => {
93
- await openPage("https://example.com", 10000, [], { userAgent: "MyCustomAgent/1.0" });
95
+ await openPage("https://example.com", 10000, [], {
96
+ userAgent: "MyCustomAgent/1.0",
97
+ });
94
98
  expect(mockBrowser.newContext).toHaveBeenCalledWith({
95
99
  ignoreHTTPSErrors: true,
96
100
  userAgent: "MyCustomAgent/1.0",
@@ -115,7 +119,7 @@ describe("openPage", () => {
115
119
  it("should navigate to the specified URL", async () => {
116
120
  await openPage("https://example.com", 10000, []);
117
121
  expect(mockPage.goto).toHaveBeenCalledWith("https://example.com", {
118
- waitUntil: "networkidle",
122
+ waitUntil: "load",
119
123
  });
120
124
  });
121
125
  it("should log success message on successful load", async () => {
@@ -176,7 +180,9 @@ describe("openPage", () => {
176
180
  mockPage.route.mockImplementation(async (_pattern, handler) => {
177
181
  routeHandler = handler;
178
182
  });
179
- await openPage("https://example.com", 10000, [], { blockCrossDomainRedirect: false });
183
+ await openPage("https://example.com", 10000, [], {
184
+ blockCrossDomainRedirect: false,
185
+ });
180
186
  const mockResponse = { status: () => 200, headers: () => ({}) };
181
187
  const continueMock = vi.fn(() => Promise.resolve());
182
188
  const abortMock = vi.fn(() => Promise.resolve());
@@ -1004,6 +1010,138 @@ describe("openPage", () => {
1004
1010
  expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("Timeout"));
1005
1011
  });
1006
1012
  });
1013
+ describe("network idle tracking", () => {
1014
+ it("should register request, requestfinished, and requestfailed listeners", async () => {
1015
+ await openPage("https://example.com", 10000, []);
1016
+ const registeredEvents = mockPage.on.mock.calls.map((call) => call[0]);
1017
+ expect(registeredEvents).toContain("request");
1018
+ expect(registeredEvents).toContain("requestfinished");
1019
+ expect(registeredEvents).toContain("requestfailed");
1020
+ expect(registeredEvents).toContain("response");
1021
+ });
1022
+ it("should set timeoutOccurred when idle wait exhausts timeout budget", async () => {
1023
+ mockPage.goto.mockResolvedValue(undefined);
1024
+ // Use setTimeout(0) so sleep resolves via a macrotask (ensuring
1025
+ // goto's microtask chain wins the race with "loaded"), while still
1026
+ // being fast enough for the idle loop to spin through quickly.
1027
+ vi.mocked(sleep).mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 0)));
1028
+ // Very short timeout: the idle loop will exhaust the budget before
1029
+ // the NETWORK_IDLE_THRESHOLD_MS (2000ms) quiet period is reached.
1030
+ const result = await openPage("https://example.com", 10, []);
1031
+ expect(result.timeoutOccurred).toBe(true);
1032
+ expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("waiting for network idle after load"));
1033
+ });
1034
+ it("should wait for in-flight requests before declaring idle", async () => {
1035
+ const listeners = {};
1036
+ mockPage.on.mockImplementation((event, callback) => {
1037
+ listeners[event] = callback;
1038
+ });
1039
+ // During goto, fire a request event but NOT requestfinished,
1040
+ // so the request stays in-flight.
1041
+ mockPage.goto.mockImplementation(async () => {
1042
+ listeners["request"]?.();
1043
+ });
1044
+ vi.mocked(sleep).mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 0)));
1045
+ // With an in-flight request (count=1), the idle condition
1046
+ // (inFlightRequestCount === 0) is never met, so the loop can
1047
+ // only exit via timeout budget exhaustion.
1048
+ const result = await openPage("https://example.com", 10, []);
1049
+ expect(result.timeoutOccurred).toBe(true);
1050
+ });
1051
+ it("should exit idle loop after requestfinished fires", async () => {
1052
+ const listeners = {};
1053
+ mockPage.on.mockImplementation((event, callback) => {
1054
+ listeners[event] = callback;
1055
+ });
1056
+ mockPage.goto.mockImplementation(async () => {
1057
+ listeners["request"]?.();
1058
+ listeners["requestfinished"]?.();
1059
+ });
1060
+ vi.mocked(sleep).mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 100)));
1061
+ const result = await openPage("https://example.com", 10000, []);
1062
+ expect(result.timeoutOccurred).toBe(false);
1063
+ expect(logger.info).toHaveBeenCalledWith("Page loaded successfully");
1064
+ });
1065
+ it("should exit idle loop after requestfailed fires", async () => {
1066
+ const listeners = {};
1067
+ mockPage.on.mockImplementation((event, callback) => {
1068
+ listeners[event] = callback;
1069
+ });
1070
+ mockPage.goto.mockImplementation(async () => {
1071
+ listeners["request"]?.();
1072
+ listeners["requestfailed"]?.();
1073
+ });
1074
+ vi.mocked(sleep).mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 100)));
1075
+ const result = await openPage("https://example.com", 10000, []);
1076
+ expect(result.timeoutOccurred).toBe(false);
1077
+ expect(logger.info).toHaveBeenCalledWith("Page loaded successfully");
1078
+ });
1079
+ it("should await pending response handlers within remaining timeout budget", async () => {
1080
+ const listeners = {};
1081
+ mockPage.on.mockImplementation((event, callback) => {
1082
+ listeners[event] = callback;
1083
+ });
1084
+ let resolveText;
1085
+ const textPromise = new Promise((resolve) => {
1086
+ resolveText = resolve;
1087
+ });
1088
+ // During goto, fire a response whose text() resolves quickly.
1089
+ mockPage.goto.mockImplementation(async () => {
1090
+ listeners["response"]?.({
1091
+ url: () => "https://example.com/quick.js",
1092
+ status: () => 200,
1093
+ headers: () => ({}),
1094
+ text: () => textPromise,
1095
+ request: () => ({
1096
+ isNavigationRequest: () => false,
1097
+ frame: () => null,
1098
+ }),
1099
+ });
1100
+ });
1101
+ vi.mocked(sleep).mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 0)));
1102
+ // Generous timeout; resolve text() after the idle loop has exited.
1103
+ const openPagePromise = openPage("https://example.com", 5000, []);
1104
+ setTimeout(() => resolveText("quick content"), 20);
1105
+ const result = await openPagePromise;
1106
+ // text() completes within the remaining budget, so the response
1107
+ // should be captured.
1108
+ const quickResponse = result.responses.find((r) => r.url === "https://example.com/quick.js");
1109
+ expect(quickResponse).toBeDefined();
1110
+ expect(quickResponse.body).toBe("quick content");
1111
+ });
1112
+ it("should not wait for pending response handlers beyond overall timeout", async () => {
1113
+ const listeners = {};
1114
+ mockPage.on.mockImplementation((event, callback) => {
1115
+ listeners[event] = callback;
1116
+ });
1117
+ // Fire a response whose text() never resolves.
1118
+ const neverResolves = new Promise(() => { });
1119
+ mockPage.goto.mockImplementation(async () => {
1120
+ listeners["response"]?.({
1121
+ url: () => "https://example.com/hang.js",
1122
+ status: () => 200,
1123
+ headers: () => ({}),
1124
+ text: () => neverResolves,
1125
+ request: () => ({
1126
+ isNavigationRequest: () => false,
1127
+ frame: () => null,
1128
+ }),
1129
+ });
1130
+ });
1131
+ vi.mocked(sleep).mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 0)));
1132
+ // Even with a short timeout, openPage must return once the budget
1133
+ // is exhausted rather than waiting on the hanging text() call.
1134
+ const start = Date.now();
1135
+ const result = await openPage("https://example.com", 50, []);
1136
+ const elapsed = Date.now() - start;
1137
+ // Must not wait significantly beyond the overall timeout
1138
+ // (with generous slack for CI jitter).
1139
+ expect(elapsed).toBeLessThan(2000);
1140
+ // The hanging response is dropped (body never captured).
1141
+ const hangResponse = result.responses.find((r) => r.url === "https://example.com/hang.js");
1142
+ expect(hangResponse).toBeUndefined();
1143
+ });
1144
+ });
1007
1145
  describe("error handling", () => {
1008
1146
  it("should log error when page load fails", async () => {
1009
1147
  mockPage.goto.mockRejectedValue(new Error("Connection refused"));