@itmetsam/samurai-framework 0.9.0 → 0.12.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 (331) hide show
  1. package/README.md +4 -5
  2. package/dist/assert/assertion-error.d.ts +1 -1
  3. package/dist/assert/expect.d.ts +5 -50
  4. package/dist/assert/expect.js +5 -182
  5. package/dist/assert/locator-assertions.d.ts +22 -0
  6. package/dist/assert/locator-assertions.js +61 -0
  7. package/dist/assert/pending-assertions.d.ts +7 -0
  8. package/dist/assert/pending-assertions.js +40 -0
  9. package/dist/assert/text-match.d.ts +4 -0
  10. package/dist/assert/text-match.js +11 -0
  11. package/dist/assert/value-assertions.d.ts +23 -0
  12. package/dist/assert/value-assertions.js +82 -0
  13. package/dist/browser/browser-context.d.ts +0 -1
  14. package/dist/browser/browser-context.js +13 -5
  15. package/dist/browser/browser-finder.js +5 -9
  16. package/dist/browser/browser.d.ts +3 -31
  17. package/dist/browser/browser.js +36 -219
  18. package/dist/browser/chrome-launch.d.ts +14 -1
  19. package/dist/browser/chrome-launch.js +44 -1
  20. package/dist/browser/dialog-handling.d.ts +32 -0
  21. package/dist/browser/dialog-handling.js +85 -0
  22. package/dist/browser/document-watcher.d.ts +44 -0
  23. package/dist/browser/document-watcher.js +160 -0
  24. package/dist/browser/download-folders.d.ts +0 -1
  25. package/dist/browser/download-folders.js +7 -15
  26. package/dist/browser/download.js +5 -10
  27. package/dist/browser/downloads-dir.js +2 -2
  28. package/dist/browser/emit.d.ts +7 -0
  29. package/dist/browser/emit.js +16 -0
  30. package/dist/browser/emulation.d.ts +1 -1
  31. package/dist/browser/emulation.js +54 -18
  32. package/dist/browser/file-chooser.js +4 -11
  33. package/dist/browser/history.d.ts +6 -0
  34. package/dist/browser/history.js +89 -0
  35. package/dist/browser/index.d.ts +2 -0
  36. package/dist/browser/index.js +2 -0
  37. package/dist/browser/init-script.d.ts +2 -0
  38. package/dist/browser/init-script.js +16 -0
  39. package/dist/browser/launch-firefox.d.ts +20 -0
  40. package/dist/browser/launch-firefox.js +66 -0
  41. package/dist/browser/launch-flags.d.ts +18 -0
  42. package/dist/browser/launch-flags.js +100 -0
  43. package/dist/browser/lazy.d.ts +21 -0
  44. package/dist/browser/lazy.js +44 -0
  45. package/dist/browser/page-events.d.ts +44 -0
  46. package/dist/browser/page-events.js +58 -0
  47. package/dist/browser/page-logs.js +2 -9
  48. package/dist/browser/page-navigation.d.ts +58 -0
  49. package/dist/browser/page-navigation.js +108 -0
  50. package/dist/browser/page-network.d.ts +69 -0
  51. package/dist/browser/page-network.js +163 -0
  52. package/dist/browser/page-settings.d.ts +35 -0
  53. package/dist/browser/page-settings.js +51 -0
  54. package/dist/browser/page-transfers.d.ts +36 -0
  55. package/dist/browser/page-transfers.js +50 -0
  56. package/dist/browser/page.d.ts +39 -154
  57. package/dist/browser/page.js +134 -758
  58. package/dist/browser/pdf.d.ts +1 -1
  59. package/dist/browser/session.d.ts +16 -0
  60. package/dist/browser/session.js +32 -0
  61. package/dist/browser/viewport.d.ts +19 -0
  62. package/dist/browser/viewport.js +72 -0
  63. package/dist/browser/warm-up.d.ts +1 -1
  64. package/dist/browser/warm-up.js +2 -1
  65. package/dist/browsers/index.d.ts +1 -1
  66. package/dist/browsers/index.js +1 -1
  67. package/dist/browsers/pins.d.ts +0 -2
  68. package/dist/browsers/pins.js +0 -2
  69. package/dist/browsers/store.d.ts +0 -2
  70. package/dist/browsers/store.js +1 -2
  71. package/dist/cli/args.d.ts +19 -7
  72. package/dist/cli/args.js +18 -9
  73. package/dist/cli/browsers.d.ts +1 -2
  74. package/dist/cli/browsers.js +2 -2
  75. package/dist/cli/console-reporter.d.ts +4 -0
  76. package/dist/cli/console-reporter.js +15 -7
  77. package/dist/cli/flow-reporter.js +2 -1
  78. package/dist/cli/flow.d.ts +1 -2
  79. package/dist/cli/flow.js +25 -22
  80. package/dist/cli/init.js +3 -8
  81. package/dist/cli/io.d.ts +10 -0
  82. package/dist/cli/io.js +1 -0
  83. package/dist/cli/main.d.ts +3 -10
  84. package/dist/cli/main.js +11 -137
  85. package/dist/cli/paint.d.ts +2 -0
  86. package/dist/cli/paint.js +4 -0
  87. package/dist/cli/record.d.ts +4 -0
  88. package/dist/cli/record.js +93 -0
  89. package/dist/cli/usage.d.ts +1 -0
  90. package/dist/cli/usage.js +58 -0
  91. package/dist/cli/version.d.ts +2 -0
  92. package/dist/cli/version.js +7 -0
  93. package/dist/config/browsers.d.ts +11 -0
  94. package/dist/config/browsers.js +32 -0
  95. package/dist/config/config.js +8 -1
  96. package/dist/config/mask.d.ts +1 -1
  97. package/dist/config/mask.js +7 -2
  98. package/dist/config/run-settings.d.ts +1 -2
  99. package/dist/config/run-settings.js +1 -6
  100. package/dist/config/timeouts.d.ts +2 -0
  101. package/dist/config/timeouts.js +13 -0
  102. package/dist/config/workers.d.ts +14 -0
  103. package/dist/config/workers.js +29 -0
  104. package/dist/flows/run/branch-context.d.ts +14 -0
  105. package/dist/flows/run/branch-context.js +35 -0
  106. package/dist/flows/run/handlers/condition.d.ts +1 -1
  107. package/dist/flows/run/handlers/end.d.ts +1 -1
  108. package/dist/flows/run/handlers/group.d.ts +1 -1
  109. package/dist/flows/run/handlers/index.d.ts +2 -2
  110. package/dist/flows/run/handlers/parallel.d.ts +1 -1
  111. package/dist/flows/run/handlers/start.d.ts +1 -1
  112. package/dist/flows/run/handlers/support.d.ts +3 -24
  113. package/dist/flows/run/handlers/support.js +1 -1
  114. package/dist/flows/run/handlers/test.d.ts +1 -1
  115. package/dist/flows/run/handlers/types.d.ts +27 -0
  116. package/dist/flows/run/handlers/types.js +1 -0
  117. package/dist/flows/run/index.d.ts +1 -1
  118. package/dist/flows/run/prepare-flow.d.ts +14 -0
  119. package/dist/flows/run/prepare-flow.js +44 -0
  120. package/dist/flows/run/project.d.ts +33 -2
  121. package/dist/flows/run/project.js +139 -49
  122. package/dist/flows/run/run.d.ts +8 -4
  123. package/dist/flows/run/run.js +30 -194
  124. package/dist/flows/run/walker.d.ts +56 -0
  125. package/dist/flows/run/walker.js +225 -0
  126. package/dist/flows/steps/catalog.d.ts +1 -1
  127. package/dist/flows/steps/handler.d.ts +1 -1
  128. package/dist/flows/steps/set.d.ts +1 -1
  129. package/dist/index.js +1 -1
  130. package/dist/locator/actionability.d.ts +33 -0
  131. package/dist/locator/actionability.js +104 -0
  132. package/dist/locator/chain-resolver.d.ts +20 -0
  133. package/dist/locator/chain-resolver.js +102 -0
  134. package/dist/locator/document-swap.d.ts +1 -0
  135. package/dist/locator/document-swap.js +11 -0
  136. package/dist/locator/element-state.d.ts +3 -1
  137. package/dist/locator/element-state.js +11 -3
  138. package/dist/locator/file-actions.d.ts +35 -0
  139. package/dist/locator/file-actions.js +55 -0
  140. package/dist/{script/helpers.js → locator/helper-realm.js} +2 -2
  141. package/dist/locator/input-actions.d.ts +18 -0
  142. package/dist/locator/input-actions.js +80 -0
  143. package/dist/locator/label-locator.d.ts +1 -1
  144. package/dist/locator/label-locator.js +5 -12
  145. package/dist/locator/locator.d.ts +14 -37
  146. package/dist/locator/locator.js +68 -424
  147. package/dist/locator/page-scripts.d.ts +16 -0
  148. package/dist/locator/page-scripts.js +54 -0
  149. package/dist/locator/page-search.d.ts +10 -0
  150. package/dist/locator/page-search.js +19 -0
  151. package/dist/locator/selector-errors.d.ts +0 -4
  152. package/dist/locator/selector-errors.js +0 -7
  153. package/dist/locator/selector.d.ts +14 -13
  154. package/dist/locator/selector.js +10 -16
  155. package/dist/locator/text-locator.d.ts +1 -1
  156. package/dist/locator/text-locator.js +5 -9
  157. package/dist/logger/index.d.ts +6 -0
  158. package/dist/logger/index.js +14 -7
  159. package/dist/network/bidi-wire.d.ts +10 -0
  160. package/dist/network/bidi-wire.js +38 -0
  161. package/dist/network/intercept.d.ts +30 -0
  162. package/dist/network/intercept.js +130 -0
  163. package/dist/network/network-tracker.d.ts +10 -47
  164. package/dist/network/network-tracker.js +34 -68
  165. package/dist/network/network-types.d.ts +33 -0
  166. package/dist/network/network-types.js +1 -0
  167. package/dist/network/network-wait.d.ts +7 -2
  168. package/dist/network/network-wait.js +14 -4
  169. package/dist/network/response.d.ts +1 -1
  170. package/dist/network/response.js +1 -1
  171. package/dist/network/route.d.ts +92 -0
  172. package/dist/network/route.js +142 -0
  173. package/dist/network/router.d.ts +6 -91
  174. package/dist/network/router.js +25 -258
  175. package/dist/recorder/expectation.d.ts +5 -0
  176. package/dist/recorder/expectation.js +20 -0
  177. package/dist/recorder/files-recorder.d.ts +30 -0
  178. package/dist/recorder/files-recorder.js +88 -0
  179. package/dist/recorder/normalise.d.ts +17 -6
  180. package/dist/recorder/normalise.js +96 -91
  181. package/dist/recorder/options.d.ts +56 -0
  182. package/dist/recorder/options.js +1 -0
  183. package/dist/recorder/page-messages.d.ts +35 -0
  184. package/dist/recorder/page-messages.js +26 -0
  185. package/dist/recorder/ranking.js +1 -0
  186. package/dist/recorder/realms.d.ts +6 -0
  187. package/dist/recorder/realms.js +13 -0
  188. package/dist/recorder/recorder.d.ts +10 -74
  189. package/dist/recorder/recorder.js +39 -199
  190. package/dist/recorder/session-browser.d.ts +22 -0
  191. package/dist/recorder/session-browser.js +21 -0
  192. package/dist/recorder/session-secrets.d.ts +8 -0
  193. package/dist/recorder/session-secrets.js +45 -0
  194. package/dist/recorder/session.d.ts +7 -13
  195. package/dist/recorder/session.js +22 -117
  196. package/dist/recorder/spec-source.d.ts +39 -0
  197. package/dist/recorder/spec-source.js +84 -0
  198. package/dist/recorder/toolbar/action-guard.d.ts +21 -0
  199. package/dist/recorder/toolbar/action-guard.js +43 -0
  200. package/dist/recorder/toolbar/actions/assert.d.ts +4 -0
  201. package/dist/recorder/toolbar/actions/assert.js +52 -0
  202. package/dist/recorder/toolbar/actions/insert.d.ts +6 -0
  203. package/dist/recorder/toolbar/actions/insert.js +93 -0
  204. package/dist/recorder/toolbar/actions/pick.d.ts +8 -0
  205. package/dist/recorder/toolbar/actions/pick.js +26 -0
  206. package/dist/recorder/toolbar/actions/reply.d.ts +7 -0
  207. package/dist/recorder/toolbar/actions/reply.js +10 -0
  208. package/dist/recorder/toolbar/controller-fixture.d.ts +47 -0
  209. package/dist/recorder/toolbar/controller-fixture.js +129 -0
  210. package/dist/recorder/toolbar/controller.d.ts +12 -78
  211. package/dist/recorder/toolbar/controller.js +35 -247
  212. package/dist/recorder/toolbar/inspect.d.ts +1 -3
  213. package/dist/recorder/toolbar/inspect.js +2 -2
  214. package/dist/recorder/toolbar/page-scope.d.ts +19 -0
  215. package/dist/recorder/toolbar/page-scope.js +43 -0
  216. package/dist/recorder/toolbar/protocol.d.ts +2 -1
  217. package/dist/recorder/toolbar/source/attach.d.ts +2 -0
  218. package/dist/recorder/toolbar/source/attach.js +41 -0
  219. package/dist/recorder/toolbar/source/dom.d.ts +2 -0
  220. package/dist/recorder/toolbar/source/dom.js +47 -0
  221. package/dist/recorder/toolbar/source/drag.d.ts +2 -0
  222. package/dist/recorder/toolbar/source/drag.js +32 -0
  223. package/dist/recorder/toolbar/source/events-page.d.ts +2 -0
  224. package/dist/recorder/toolbar/source/events-page.js +67 -0
  225. package/dist/recorder/toolbar/source/events-toolbar.d.ts +2 -0
  226. package/dist/recorder/toolbar/source/events-toolbar.js +35 -0
  227. package/dist/recorder/toolbar/source/fields.d.ts +2 -0
  228. package/dist/recorder/toolbar/source/fields.js +44 -0
  229. package/dist/recorder/toolbar/source/handlers.d.ts +2 -0
  230. package/dist/recorder/toolbar/source/handlers.js +134 -0
  231. package/dist/recorder/toolbar/source/names.d.ts +2 -0
  232. package/dist/recorder/toolbar/source/names.js +12 -0
  233. package/dist/recorder/toolbar/source/picking.d.ts +2 -0
  234. package/dist/recorder/toolbar/source/picking.js +72 -0
  235. package/dist/recorder/toolbar/source/placement.d.ts +2 -0
  236. package/dist/recorder/toolbar/source/placement.js +45 -0
  237. package/dist/recorder/toolbar/source/recorder-side.d.ts +2 -0
  238. package/dist/recorder/toolbar/source/recorder-side.js +54 -0
  239. package/dist/recorder/toolbar/source/setup.d.ts +2 -0
  240. package/dist/recorder/toolbar/source/setup.js +50 -0
  241. package/dist/recorder/toolbar/source/views-panels.d.ts +2 -0
  242. package/dist/recorder/toolbar/source/views-panels.js +150 -0
  243. package/dist/recorder/toolbar/source/views-rail.d.ts +2 -0
  244. package/dist/recorder/toolbar/source/views-rail.js +40 -0
  245. package/dist/recorder/toolbar/source/views-render.d.ts +2 -0
  246. package/dist/recorder/toolbar/source/views-render.js +48 -0
  247. package/dist/recorder/toolbar/source.js +30 -856
  248. package/dist/recorder/toolbar/types.d.ts +70 -0
  249. package/dist/recorder/toolbar/types.js +1 -0
  250. package/dist/recorder/toolbar-bridge.d.ts +43 -0
  251. package/dist/recorder/toolbar-bridge.js +106 -0
  252. package/dist/runner/browsers.d.ts +10 -0
  253. package/dist/runner/browsers.js +12 -0
  254. package/dist/runner/groups.d.ts +5 -1
  255. package/dist/runner/groups.js +9 -2
  256. package/dist/runner/options.d.ts +31 -0
  257. package/dist/runner/options.js +1 -0
  258. package/dist/runner/pooled-run.d.ts +43 -0
  259. package/dist/runner/pooled-run.js +75 -0
  260. package/dist/runner/ports.d.ts +2 -0
  261. package/dist/runner/ports.js +2 -0
  262. package/dist/runner/registry.d.ts +1 -1
  263. package/dist/runner/registry.js +1 -1
  264. package/dist/runner/reporter.d.ts +40 -11
  265. package/dist/runner/reporter.js +69 -16
  266. package/dist/runner/run-test.d.ts +16 -0
  267. package/dist/runner/run-test.js +178 -0
  268. package/dist/runner/run.d.ts +5 -2
  269. package/dist/runner/run.js +28 -28
  270. package/dist/runner/select.d.ts +24 -0
  271. package/dist/runner/select.js +77 -0
  272. package/dist/runner/test-dir.d.ts +13 -0
  273. package/dist/runner/test-dir.js +27 -0
  274. package/dist/runner/test-runner.d.ts +27 -50
  275. package/dist/runner/test-runner.js +102 -264
  276. package/dist/runner/worker-pool.d.ts +67 -0
  277. package/dist/runner/worker-pool.js +275 -0
  278. package/dist/runner/worker-process.d.ts +6 -0
  279. package/dist/runner/worker-process.js +54 -0
  280. package/dist/runner/worker-protocol.d.ts +67 -0
  281. package/dist/runner/worker-protocol.js +1 -0
  282. package/dist/runner/worker-session.d.ts +7 -0
  283. package/dist/runner/worker-session.js +60 -0
  284. package/dist/runner/worker.d.ts +26 -0
  285. package/dist/runner/worker.js +121 -0
  286. package/dist/runner/workers-option.d.ts +15 -0
  287. package/dist/runner/workers-option.js +22 -0
  288. package/dist/script/call-function.d.ts +1 -9
  289. package/dist/script/call-function.js +2 -19
  290. package/dist/script/element-handle.d.ts +1 -2
  291. package/dist/script/element-handle.js +1 -2
  292. package/dist/script/serialize.d.ts +3 -12
  293. package/dist/script/serialize.js +53 -62
  294. package/dist/steps/parse/call.d.ts +3 -0
  295. package/dist/steps/parse/call.js +81 -0
  296. package/dist/steps/parse/expectation.d.ts +5 -0
  297. package/dist/steps/parse/expectation.js +71 -0
  298. package/dist/steps/parse/file-entry.d.ts +4 -0
  299. package/dist/steps/parse/file-entry.js +74 -0
  300. package/dist/steps/parse/literals.d.ts +4 -0
  301. package/dist/steps/parse/literals.js +28 -0
  302. package/dist/steps/parse/step.d.ts +4 -0
  303. package/dist/steps/parse/step.js +81 -0
  304. package/dist/steps/parse.js +2 -323
  305. package/dist/transport/bidi-connection.d.ts +4 -2
  306. package/dist/transport/bidi-connection.js +9 -43
  307. package/dist/transport/log-redaction.d.ts +13 -0
  308. package/dist/transport/log-redaction.js +51 -0
  309. package/dist/transport/subscriptions.js +18 -13
  310. package/dist/transport/unsupported.d.ts +7 -0
  311. package/dist/transport/unsupported.js +13 -0
  312. package/dist/types/bidi-modules/browser.d.ts +1 -12
  313. package/dist/types/bidi-modules/browsing-context.d.ts +8 -18
  314. package/dist/types/bidi-modules/emulation.d.ts +29 -21
  315. package/dist/types/bidi-modules/input.d.ts +1 -9
  316. package/dist/types/bidi-modules/network.d.ts +29 -24
  317. package/dist/types/bidi-modules/script.d.ts +0 -9
  318. package/dist/types/bidi-modules/session.d.ts +0 -8
  319. package/dist/types/bidi-modules/storage.d.ts +0 -5
  320. package/dist/types/bidi.d.ts +7 -37
  321. package/dist/types/config.d.ts +17 -3
  322. package/dist/types/test.d.ts +15 -2
  323. package/dist/wait/wait-until.d.ts +1 -5
  324. package/dist/wait/wait-until.js +7 -16
  325. package/package.json +3 -2
  326. package/dist/config/default.d.ts +0 -2
  327. package/dist/config/default.js +0 -3
  328. package/dist/types/bidi-modules/web-extensions.d.ts +0 -9
  329. package/dist/types/bidi-protocols/command.d.ts +0 -7
  330. package/dist/types/bidi-protocols/event.d.ts +0 -17
  331. /package/dist/{script/helpers.d.ts → locator/helper-realm.d.ts} +0 -0
@@ -1,18 +1,13 @@
1
1
  import EventEmitter from "node:events";
2
2
  import logger from "../logger/index.js";
3
- import { WaitTimeoutError } from "../wait/wait-until.js";
3
+ import { headersFromBidi, toRequest } from "./bidi-wire.js";
4
+ import { NetworkIdleTimeoutError } from "./network-wait.js";
5
+ import {} from "./network-types.js";
6
+ export { NetworkIdleTimeoutError } from "./network-wait.js";
4
7
  /** The error text of a request that the page left before it finished, when the browser says none */
5
8
  const ABORTED_BY_NAVIGATION = "aborted by navigation";
6
9
  /** How many URLs a timeout message names */
7
10
  const URLS_IN_MESSAGE = 5;
8
- /** waitForIdle ran out of time with requests still in flight; `last` holds (up to five of) their URLs */
9
- export class NetworkIdleTimeoutError extends WaitTimeoutError {
10
- constructor(timeout, inflight, urls) {
11
- super(timeout, urls);
12
- this.name = "NetworkIdleTimeoutError";
13
- this.message = `waitForNetworkIdle(): still ${inflight} request(s) in flight after ${timeout}ms: ${urls.join(", ")}`;
14
- }
15
- }
16
11
  /**
17
12
  * Follows the network activity of one page (its frames included): which requests are in flight,
18
13
  * and `request` / `response` / `requestfailed` events. In-flight is keyed by `request id:redirect count`,
@@ -25,28 +20,26 @@ export class NetworkTracker {
25
20
  this.ignore = ignore;
26
21
  this.connector = connector;
27
22
  this.events = new EventEmitter();
28
- this.active = new Map();
29
- /** The context each request in flight belongs to, by the same key */
30
- this.contexts = new Map();
31
- /** Requests that may still be the source of a redirect, by hop key */
23
+ /**
24
+ * Every hop that is in flight or may still be the source of a redirect, by hop key. A redirect response ends a
25
+ * hop's flight (`live` false) but keeps it, for the next hop's `redirectedFrom`.
26
+ */
32
27
  this.hops = new Map();
28
+ /** Rejects a pending `waitForIdle`; each wait adds its own */
33
29
  this.waiters = new Set();
34
30
  this.disposed = false;
35
31
  this.onBeforeRequestSent = (params) => {
36
32
  if (!this.owns(params.context))
37
33
  return;
38
34
  const key = NetworkTracker.key(params.request, params.redirectCount);
39
- const redirectCount = Number(params.redirectCount);
35
+ const { redirectCount } = params;
40
36
  const previousKey = `${params.request.request}:${redirectCount - 1}`;
41
- const redirectedFrom = redirectCount > 0 ? (this.hops.get(previousKey) ?? null) : null;
37
+ const redirectedFrom = redirectCount > 0 ? (this.hops.get(previousKey)?.request ?? null) : null;
42
38
  this.hops.delete(previousKey);
43
39
  const request = toRequest(params.request, params.navigation, params.isBlocked, redirectedFrom);
44
40
  if (this.ignore(request))
45
41
  return;
46
- this.hops.set(key, request);
47
- this.active.set(key, request.url);
48
- if (params.context)
49
- this.contexts.set(key, params.context);
42
+ this.hops.set(key, { request, context: params.context, live: true });
50
43
  this.emit("request", request);
51
44
  this.events.emit("change");
52
45
  };
@@ -54,19 +47,21 @@ export class NetworkTracker {
54
47
  if (!this.owns(params.context))
55
48
  return;
56
49
  const key = NetworkTracker.key(params.request, params.redirectCount);
57
- const request = this.hops.get(key);
58
- if (!this.active.delete(key) || !request)
50
+ const hop = this.hops.get(key);
51
+ if (!hop?.live)
59
52
  return;
60
- this.contexts.delete(key);
53
+ const { request } = hop;
61
54
  const { response } = params;
62
55
  // A redirect response keeps its request around: the next hop links back to it
63
56
  if (response.status < 300 || response.status >= 400)
64
57
  this.hops.delete(key);
58
+ else
59
+ hop.live = false;
65
60
  this.emit("response", {
66
61
  url: response.url,
67
62
  status: response.status,
68
63
  statusText: response.statusText,
69
- headers: toHeaders(response.headers),
64
+ headers: headersFromBidi(response.headers),
70
65
  fromCache: response.fromCache,
71
66
  request,
72
67
  });
@@ -76,12 +71,11 @@ export class NetworkTracker {
76
71
  if (!this.owns(params.context))
77
72
  return;
78
73
  const key = NetworkTracker.key(params.request, params.redirectCount);
79
- const request = this.hops.get(key);
80
- if (!this.active.delete(key) || !request)
74
+ const hop = this.hops.get(key);
75
+ if (!hop?.live)
81
76
  return;
82
- this.contexts.delete(key);
83
77
  this.hops.delete(key);
84
- this.emit("requestfailed", Object.assign(request, { errorText: params.errorText }));
78
+ this.emit("requestfailed", Object.assign(hop.request, { errorText: params.errorText }));
85
79
  this.events.emit("change");
86
80
  };
87
81
  /**
@@ -92,14 +86,11 @@ export class NetworkTracker {
92
86
  if (!this.owns(params.context))
93
87
  return;
94
88
  let changed = false;
95
- for (const [key, requestContext] of [...this.contexts]) {
96
- const request = this.hops.get(key);
97
- if (!request || request.navigation === params.navigation)
89
+ for (const [key, { request, context, live }] of [...this.hops]) {
90
+ if (!live || request.navigation === params.navigation)
98
91
  continue;
99
- if (!this.tree.isWithin(requestContext, params.context))
92
+ if (!this.tree.isWithin(context, params.context))
100
93
  continue;
101
- this.contexts.delete(key);
102
- this.active.delete(key);
103
94
  this.hops.delete(key);
104
95
  this.emit("requestfailed", Object.assign(request, { errorText: ABORTED_BY_NAVIGATION }));
105
96
  changed = true;
@@ -131,11 +122,14 @@ export class NetworkTracker {
131
122
  }
132
123
  /** Requests currently in flight */
133
124
  get inflight() {
134
- return this.active.size;
125
+ return this.liveHops().length;
135
126
  }
136
127
  /** URLs of the requests currently in flight, oldest first */
137
128
  get inflightUrls() {
138
- return [...this.active.values()];
129
+ return this.liveHops().map(({ request }) => request.url);
130
+ }
131
+ liveHops() {
132
+ return [...this.hops.values()].filter(({ live }) => live);
139
133
  }
140
134
  /** Waits that have not finished yet */
141
135
  get waiting() {
@@ -154,9 +148,7 @@ export class NetworkTracker {
154
148
  waitForIdle({ idleTime, timeout, }) {
155
149
  return new Promise((resolve, reject) => {
156
150
  let idleTimer;
157
- const waiter = {
158
- reject: (reason) => finish(() => reject(reason)),
159
- };
151
+ const waiter = (reason) => finish(() => reject(reason));
160
152
  const finish = (settle) => {
161
153
  clearTimeout(idleTimer);
162
154
  clearTimeout(deadline);
@@ -166,10 +158,10 @@ export class NetworkTracker {
166
158
  };
167
159
  const check = () => {
168
160
  clearTimeout(idleTimer);
169
- if (this.active.size === 0)
161
+ if (this.inflight === 0)
170
162
  idleTimer = setTimeout(() => finish(resolve), idleTime);
171
163
  };
172
- const deadline = setTimeout(() => finish(() => reject(new NetworkIdleTimeoutError(timeout, this.active.size, this.inflightUrls.slice(0, URLS_IN_MESSAGE)))), timeout);
164
+ const deadline = setTimeout(() => finish(() => reject(new NetworkIdleTimeoutError(timeout, this.inflight, this.inflightUrls.slice(0, URLS_IN_MESSAGE)))), timeout);
173
165
  this.waiters.add(waiter);
174
166
  this.events.on("change", check);
175
167
  check();
@@ -181,10 +173,8 @@ export class NetworkTracker {
181
173
  return;
182
174
  this.disposed = true;
183
175
  this.removeListeners();
184
- this.active.clear();
185
- this.contexts.clear();
186
176
  this.hops.clear();
187
- [...this.waiters].forEach((waiter) => waiter.reject(new Error("page closed")));
177
+ [...this.waiters].forEach((reject) => reject(new Error("page closed")));
188
178
  await this.subscription?.unsubscribe();
189
179
  }
190
180
  removeListeners() {
@@ -197,7 +187,7 @@ export class NetworkTracker {
197
187
  return context !== null && this.tree.isWithin(context, this.contextId);
198
188
  }
199
189
  static key(data, redirectCount) {
200
- return `${data.request}:${Number(redirectCount)}`;
190
+ return `${data.request}:${redirectCount}`;
201
191
  }
202
192
  /** A listener that throws must not stop the tracker or the other listeners */
203
193
  emit(event, ...args) {
@@ -211,27 +201,3 @@ export class NetworkTracker {
211
201
  }
212
202
  }
213
203
  }
214
- export function toRequest(data, navigation, isBlocked, redirectedFrom) {
215
- return {
216
- id: data.request,
217
- url: data.url,
218
- method: data.method,
219
- headers: toHeaders(data.headers),
220
- resourceType: data.destination || data.initiatorType || "other",
221
- navigation,
222
- redirectedFrom,
223
- isBlocked,
224
- };
225
- }
226
- /** Header list to a lower-cased record; repeated names are joined, base64 values decoded */
227
- export function toHeaders(headers) {
228
- const result = {};
229
- for (const { name, value } of headers) {
230
- const text = value.type === "base64"
231
- ? Buffer.from(value.value, "base64").toString("utf8")
232
- : value.value;
233
- const key = name.toLowerCase();
234
- result[key] = key in result ? `${result[key]}, ${text}` : text;
235
- }
236
- return result;
237
- }
@@ -0,0 +1,33 @@
1
+ export interface NetworkRequest {
2
+ /** The browser's id; the same for every hop of a redirect chain */
3
+ id: string;
4
+ url: string;
5
+ method: string;
6
+ /** Header names lower-cased; repeated headers joined with ", " */
7
+ headers: Record<string, string>;
8
+ /** "document", "script", "image", …; falls back to the initiator type, then "other" */
9
+ resourceType: string;
10
+ navigation: string | null;
11
+ /** The request this one is the redirect of, or null */
12
+ redirectedFrom: NetworkRequest | null;
13
+ /** True while a network intercept holds it */
14
+ isBlocked: boolean;
15
+ }
16
+ export interface NetworkResponse {
17
+ url: string;
18
+ status: number;
19
+ statusText: string;
20
+ headers: Record<string, string>;
21
+ fromCache: boolean;
22
+ request: NetworkRequest;
23
+ }
24
+ export type FailedRequest = NetworkRequest & {
25
+ errorText: string;
26
+ };
27
+ /** What `page.on` and the tracker emit: the event name and the arguments its listeners get */
28
+ export interface EventMap {
29
+ request: [NetworkRequest];
30
+ response: [NetworkResponse];
31
+ requestfailed: [FailedRequest];
32
+ }
33
+ export type NetworkEventName = keyof EventMap;
@@ -0,0 +1 @@
1
+ export {};
@@ -2,15 +2,20 @@ import { WaitTimeoutError } from "../wait/wait-until.js";
2
2
  import { type UrlPattern } from "./url-match.js";
3
3
  /** What `page.waitForRequest` / `waitForResponse` match: a URL pattern, or a function */
4
4
  export type NetworkMatch<T> = UrlPattern | ((item: T) => boolean);
5
- /** No matching request or response arrived in time */
6
- export declare class NetworkWaitTimeoutError extends WaitTimeoutError<undefined> {
5
+ /** No matching request or response arrived in time; each subclass names itself */
6
+ declare class NetworkWaitTimeoutError extends WaitTimeoutError<undefined> {
7
7
  constructor(operation: "waitForRequest" | "waitForResponse", match: NetworkMatch<never>, timeout: number);
8
8
  }
9
9
  export declare class RequestTimeoutError extends NetworkWaitTimeoutError {
10
10
  }
11
11
  export declare class ResponseTimeoutError extends NetworkWaitTimeoutError {
12
12
  }
13
+ /** waitForIdle ran out of time with requests still in flight; `last` holds (up to five of) their URLs */
14
+ export declare class NetworkIdleTimeoutError extends WaitTimeoutError<string[]> {
15
+ constructor(timeout: number, inflight: number, urls: string[]);
16
+ }
13
17
  /** The predicate for a match: a function as it is, a URL pattern tested on the item's url */
14
18
  export declare function toPredicate<T extends {
15
19
  url: string;
16
20
  }>(match: NetworkMatch<T>): (item: T) => boolean;
21
+ export {};
@@ -1,10 +1,10 @@
1
1
  import { WaitTimeoutError } from "../wait/wait-until.js";
2
2
  import { matchUrl } from "./url-match.js";
3
- /** No matching request or response arrived in time */
4
- export class NetworkWaitTimeoutError extends WaitTimeoutError {
3
+ /** No matching request or response arrived in time; each subclass names itself */
4
+ class NetworkWaitTimeoutError extends WaitTimeoutError {
5
5
  constructor(operation, match, timeout) {
6
6
  super(timeout, undefined);
7
- this.name = operation === "waitForRequest" ? "RequestTimeoutError" : "ResponseTimeoutError";
7
+ this.name = new.target.name;
8
8
  const kind = operation === "waitForRequest" ? "request" : "response";
9
9
  this.message = `${operation}(): no ${kind} matching ${describeMatch(match)} within ${timeout}ms`;
10
10
  }
@@ -13,6 +13,14 @@ export class RequestTimeoutError extends NetworkWaitTimeoutError {
13
13
  }
14
14
  export class ResponseTimeoutError extends NetworkWaitTimeoutError {
15
15
  }
16
+ /** waitForIdle ran out of time with requests still in flight; `last` holds (up to five of) their URLs */
17
+ export class NetworkIdleTimeoutError extends WaitTimeoutError {
18
+ constructor(timeout, inflight, urls) {
19
+ super(timeout, urls);
20
+ this.name = "NetworkIdleTimeoutError";
21
+ this.message = `waitForNetworkIdle(): still ${inflight} request(s) in flight after ${timeout}ms: ${urls.join(", ")}`;
22
+ }
23
+ }
16
24
  function describeMatch(match) {
17
25
  if (typeof match === "function")
18
26
  return "the given function";
@@ -20,5 +28,7 @@ function describeMatch(match) {
20
28
  }
21
29
  /** The predicate for a match: a function as it is, a URL pattern tested on the item's url */
22
30
  export function toPredicate(match) {
23
- return typeof match === "function" ? match : (item) => matchUrl(match, item.url);
31
+ return typeof match === "function"
32
+ ? match
33
+ : (item) => matchUrl(match, item.url);
24
34
  }
@@ -1,4 +1,4 @@
1
- import type { NetworkRequest, NetworkResponse } from "./network-tracker.js";
1
+ import type { NetworkRequest, NetworkResponse } from "./network-types.js";
2
2
  /** A response of the page, with access to its body */
3
3
  export declare class Response {
4
4
  private loadBody;
@@ -36,7 +36,7 @@ export class Response {
36
36
  return JSON.parse(text);
37
37
  }
38
38
  catch (err) {
39
- throw new Error(`Response body of ${this.url} is not JSON: ${err.message}`);
39
+ throw new Error(`json(): the response body of ${this.url} is not JSON: ${err.message}`);
40
40
  }
41
41
  }
42
42
  }
@@ -0,0 +1,92 @@
1
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
2
+ import type { NetworkRequest } from "./network-types.js";
3
+ import type { UrlPattern } from "./url-match.js";
4
+ export type RouteHandler = (route: Route) => void | Promise<void>;
5
+ export interface FulfillOptions {
6
+ /** @default 200 */
7
+ status?: number;
8
+ headers?: Record<string, string>;
9
+ /** Sets `content-type` unless `headers` has one; `json` implies "application/json" */
10
+ contentType?: string;
11
+ body?: string | Buffer;
12
+ /** Sent as JSON text; cannot be combined with `body` */
13
+ json?: unknown;
14
+ }
15
+ export interface ContinueOptions {
16
+ /**
17
+ * Sends the request to another URL. Firefox applies this to navigations only: the page's `fetch`, XHR and
18
+ * image requests fail when their URL is changed, so mock those with `fulfill` instead
19
+ */
20
+ url?: string;
21
+ method?: string;
22
+ /** Replaces all request headers */
23
+ headers?: Record<string, string>;
24
+ postData?: string | Buffer;
25
+ }
26
+ export interface RouteEntry {
27
+ pattern: UrlPattern;
28
+ handler: RouteHandler;
29
+ }
30
+ /** Answers `network.*` commands for one blocked request */
31
+ export interface RouteDeps {
32
+ send: BiDiConnector["send"];
33
+ /** Passes the request to the next matching route, or lets it continue */
34
+ next: () => Promise<void>;
35
+ /** Called once the route is answered, whichever way */
36
+ settled: () => void;
37
+ /**
38
+ * Whether a replaced body needs a `content-length` of its own. Firefox keeps the old one and cuts the new body
39
+ * off; Chrome sets the right one itself and hangs the request when it is given one.
40
+ */
41
+ sendsContentLength: boolean;
42
+ }
43
+ /**
44
+ * A request the browser holds until it is answered. Answer it exactly once with `fulfill`, `continue`,
45
+ * `abort` or `fallback`; a second call throws.
46
+ */
47
+ export declare class Route {
48
+ private readonly info;
49
+ private readonly deps;
50
+ private answered;
51
+ constructor(info: NetworkRequest, deps: RouteDeps);
52
+ request(): NetworkRequest;
53
+ /** Answers the request without sending it: status 200 and no body unless told otherwise */
54
+ fulfill(options?: FulfillOptions): Promise<void>;
55
+ /**
56
+ * Lets the request go on, optionally changed. `headers` replaces all request headers. A new `postData` brings its
57
+ * own `content-length` (the request's old one would cut the new body off or leave the server waiting).
58
+ */
59
+ continue(options?: ContinueOptions): Promise<void>;
60
+ /** Makes the request fail (the browser reports a network error to the page) */
61
+ abort(): Promise<void>;
62
+ /** Hands the request to the next route that matches it, or lets it continue when there is none */
63
+ fallback(): Promise<void>;
64
+ private claim;
65
+ private answer;
66
+ }
67
+ /** What a `PendingRoute` needs of the router: how to talk to the browser and where to go on */
68
+ export type PendingRouteControl = Omit<RouteDeps, "settled"> & {
69
+ /** Called once the request is answered, whichever way */
70
+ onSettled: (pending: PendingRoute) => void;
71
+ };
72
+ /**
73
+ * A request the browser holds, with what the router needs to see to it: the `Route` handed to the handler, the
74
+ * route entry that holds it, and the timer that lets it continue when the handler is too slow.
75
+ * Not part of the public API: handlers only ever see the `Route`.
76
+ */
77
+ export declare class PendingRoute {
78
+ private readonly info;
79
+ readonly entry?: RouteEntry | undefined;
80
+ readonly route: Route;
81
+ private answered;
82
+ private timer;
83
+ /** `entry` is the route that holds the request; none for one nobody asked for */
84
+ constructor(info: NetworkRequest, control: PendingRouteControl, entry?: RouteEntry | undefined);
85
+ get isAnswered(): boolean;
86
+ /** Calls `onLate` when nothing answered after `ms`; the timer never keeps the process alive */
87
+ startTimer(ms: number, onLate: () => void): void;
88
+ /** Lets the request continue unchanged; failures are logged, not thrown */
89
+ release(): Promise<void>;
90
+ /** Fails the request; failures are logged, not thrown */
91
+ failQuietly(): Promise<void>;
92
+ }
@@ -0,0 +1,142 @@
1
+ import { STATUS_CODES } from "node:http";
2
+ import logger from "../logger/index.js";
3
+ import { headersToBidi, toBytes } from "./bidi-wire.js";
4
+ const hasHeader = (headers, name) => Object.keys(headers).some((key) => key.toLowerCase() === name);
5
+ /**
6
+ * A request the browser holds until it is answered. Answer it exactly once with `fulfill`, `continue`,
7
+ * `abort` or `fallback`; a second call throws.
8
+ */
9
+ export class Route {
10
+ constructor(info, deps) {
11
+ this.info = info;
12
+ this.deps = deps;
13
+ this.answered = false;
14
+ }
15
+ request() {
16
+ return this.info;
17
+ }
18
+ /** Answers the request without sending it: status 200 and no body unless told otherwise */
19
+ async fulfill(options = {}) {
20
+ if (options.json !== undefined && options.body !== undefined) {
21
+ throw new TypeError("fulfill(): use either json or body, not both");
22
+ }
23
+ const text = options.json !== undefined ? JSON.stringify(options.json) : undefined;
24
+ const body = options.body ?? text;
25
+ const headers = { ...options.headers };
26
+ const contentType = options.contentType ??
27
+ (text !== undefined ? "application/json" : undefined);
28
+ if (contentType && !hasHeader(headers, "content-type"))
29
+ headers["content-type"] = contentType;
30
+ if (!hasHeader(headers, "content-length")) {
31
+ headers["content-length"] = String(body === undefined ? 0 : Buffer.byteLength(body));
32
+ }
33
+ const status = options.status ?? 200;
34
+ await this.answer("network.provideResponse", {
35
+ request: this.info.id,
36
+ statusCode: status,
37
+ reasonPhrase: STATUS_CODES[status] ?? "",
38
+ headers: headersToBidi(headers),
39
+ ...(body !== undefined && { body: toBytes(body) }),
40
+ });
41
+ }
42
+ /**
43
+ * Lets the request go on, optionally changed. `headers` replaces all request headers. A new `postData` brings its
44
+ * own `content-length` (the request's old one would cut the new body off or leave the server waiting).
45
+ */
46
+ async continue(options = {}) {
47
+ let headers = options.headers;
48
+ if (options.postData !== undefined) {
49
+ const withoutLength = Object.fromEntries(Object.entries(headers ?? this.info.headers).filter(([name]) => name.toLowerCase() !== "content-length"));
50
+ headers = this.deps.sendsContentLength
51
+ ? {
52
+ ...withoutLength,
53
+ "content-length": String(Buffer.byteLength(options.postData)),
54
+ }
55
+ : withoutLength;
56
+ }
57
+ await this.answer("network.continueRequest", {
58
+ request: this.info.id,
59
+ ...(options.url !== undefined && { url: options.url }),
60
+ ...(options.method !== undefined && { method: options.method }),
61
+ ...(headers !== undefined && { headers: headersToBidi(headers) }),
62
+ ...(options.postData !== undefined && {
63
+ body: toBytes(options.postData),
64
+ }),
65
+ });
66
+ }
67
+ /** Makes the request fail (the browser reports a network error to the page) */
68
+ async abort() {
69
+ await this.answer("network.failRequest", { request: this.info.id });
70
+ }
71
+ /** Hands the request to the next route that matches it, or lets it continue when there is none */
72
+ async fallback() {
73
+ this.claim();
74
+ await this.deps.next();
75
+ }
76
+ claim() {
77
+ if (this.answered)
78
+ throw new Error("route already handled");
79
+ this.answered = true;
80
+ this.deps.settled();
81
+ }
82
+ async answer(method, params) {
83
+ this.claim();
84
+ await this.deps.send(method, params);
85
+ }
86
+ }
87
+ /**
88
+ * A request the browser holds, with what the router needs to see to it: the `Route` handed to the handler, the
89
+ * route entry that holds it, and the timer that lets it continue when the handler is too slow.
90
+ * Not part of the public API: handlers only ever see the `Route`.
91
+ */
92
+ export class PendingRoute {
93
+ /** `entry` is the route that holds the request; none for one nobody asked for */
94
+ constructor(info, control, entry) {
95
+ this.info = info;
96
+ this.entry = entry;
97
+ this.answered = false;
98
+ this.route = new Route(info, {
99
+ send: control.send,
100
+ next: control.next,
101
+ sendsContentLength: control.sendsContentLength,
102
+ settled: () => {
103
+ this.answered = true;
104
+ clearTimeout(this.timer);
105
+ control.onSettled(this);
106
+ },
107
+ });
108
+ }
109
+ get isAnswered() {
110
+ return this.answered;
111
+ }
112
+ /** Calls `onLate` when nothing answered after `ms`; the timer never keeps the process alive */
113
+ startTimer(ms, onLate) {
114
+ this.timer = setTimeout(() => {
115
+ if (!this.answered)
116
+ onLate();
117
+ }, ms);
118
+ this.timer.unref?.();
119
+ }
120
+ /** Lets the request continue unchanged; failures are logged, not thrown */
121
+ async release() {
122
+ if (this.answered)
123
+ return;
124
+ try {
125
+ await this.route.continue();
126
+ }
127
+ catch (err) {
128
+ logger.debug("Could not let %s continue", this.info.url, { err });
129
+ }
130
+ }
131
+ /** Fails the request; failures are logged, not thrown */
132
+ async failQuietly() {
133
+ if (this.answered)
134
+ return;
135
+ try {
136
+ await this.route.abort();
137
+ }
138
+ catch (err) {
139
+ logger.debug("Could not fail %s", this.info.url, { err });
140
+ }
141
+ }
142
+ }