@itmetsam/samurai-framework 0.10.0 → 0.12.1

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 (326) hide show
  1. package/README.md +3 -4
  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 +2 -1
  19. package/dist/browser/dialog-handling.d.ts +32 -0
  20. package/dist/browser/dialog-handling.js +85 -0
  21. package/dist/browser/document-watcher.d.ts +44 -0
  22. package/dist/browser/document-watcher.js +160 -0
  23. package/dist/browser/download-folders.d.ts +0 -1
  24. package/dist/browser/download-folders.js +7 -15
  25. package/dist/browser/download.js +5 -10
  26. package/dist/browser/downloads-dir.js +2 -2
  27. package/dist/browser/emit.d.ts +7 -0
  28. package/dist/browser/emit.js +16 -0
  29. package/dist/browser/emulation.d.ts +1 -1
  30. package/dist/browser/emulation.js +54 -18
  31. package/dist/browser/file-chooser.js +4 -11
  32. package/dist/browser/history.d.ts +6 -0
  33. package/dist/browser/history.js +89 -0
  34. package/dist/browser/index.d.ts +2 -0
  35. package/dist/browser/index.js +2 -0
  36. package/dist/browser/init-script.d.ts +2 -0
  37. package/dist/browser/init-script.js +16 -0
  38. package/dist/browser/launch-firefox.d.ts +20 -0
  39. package/dist/browser/launch-firefox.js +66 -0
  40. package/dist/browser/launch-flags.d.ts +18 -0
  41. package/dist/browser/launch-flags.js +100 -0
  42. package/dist/browser/lazy.d.ts +21 -0
  43. package/dist/browser/lazy.js +44 -0
  44. package/dist/browser/page-events.d.ts +44 -0
  45. package/dist/browser/page-events.js +58 -0
  46. package/dist/browser/page-logs.js +2 -9
  47. package/dist/browser/page-navigation.d.ts +58 -0
  48. package/dist/browser/page-navigation.js +108 -0
  49. package/dist/browser/page-network.d.ts +69 -0
  50. package/dist/browser/page-network.js +163 -0
  51. package/dist/browser/page-settings.d.ts +35 -0
  52. package/dist/browser/page-settings.js +51 -0
  53. package/dist/browser/page-transfers.d.ts +36 -0
  54. package/dist/browser/page-transfers.js +50 -0
  55. package/dist/browser/page.d.ts +39 -154
  56. package/dist/browser/page.js +134 -758
  57. package/dist/browser/pdf.d.ts +1 -1
  58. package/dist/browser/session.d.ts +16 -0
  59. package/dist/browser/session.js +32 -0
  60. package/dist/browser/viewport.d.ts +19 -0
  61. package/dist/browser/viewport.js +72 -0
  62. package/dist/browser/warm-up.d.ts +1 -1
  63. package/dist/browser/warm-up.js +2 -1
  64. package/dist/browsers/index.d.ts +1 -1
  65. package/dist/browsers/index.js +1 -1
  66. package/dist/browsers/pins.d.ts +0 -2
  67. package/dist/browsers/pins.js +0 -2
  68. package/dist/browsers/store.d.ts +0 -2
  69. package/dist/browsers/store.js +1 -2
  70. package/dist/cli/args.d.ts +13 -6
  71. package/dist/cli/args.js +13 -10
  72. package/dist/cli/browsers.d.ts +1 -2
  73. package/dist/cli/browsers.js +2 -2
  74. package/dist/cli/console-reporter.js +3 -2
  75. package/dist/cli/flow-reporter.js +2 -1
  76. package/dist/cli/flow.d.ts +1 -2
  77. package/dist/cli/flow.js +25 -22
  78. package/dist/cli/init.js +2 -7
  79. package/dist/cli/io.d.ts +10 -0
  80. package/dist/cli/io.js +1 -0
  81. package/dist/cli/main.d.ts +3 -10
  82. package/dist/cli/main.js +10 -143
  83. package/dist/cli/paint.d.ts +2 -0
  84. package/dist/cli/paint.js +4 -0
  85. package/dist/cli/record.d.ts +4 -0
  86. package/dist/cli/record.js +93 -0
  87. package/dist/cli/usage.d.ts +1 -0
  88. package/dist/cli/usage.js +58 -0
  89. package/dist/cli/version.d.ts +2 -0
  90. package/dist/cli/version.js +7 -0
  91. package/dist/config/browsers.d.ts +11 -0
  92. package/dist/config/browsers.js +32 -0
  93. package/dist/config/config.js +4 -1
  94. package/dist/config/mask.d.ts +1 -1
  95. package/dist/config/mask.js +7 -2
  96. package/dist/config/run-settings.d.ts +1 -2
  97. package/dist/config/run-settings.js +1 -6
  98. package/dist/config/timeouts.d.ts +2 -0
  99. package/dist/config/timeouts.js +13 -0
  100. package/dist/config/workers.d.ts +14 -0
  101. package/dist/config/workers.js +29 -0
  102. package/dist/flows/run/branch-context.d.ts +14 -0
  103. package/dist/flows/run/branch-context.js +35 -0
  104. package/dist/flows/run/handlers/condition.d.ts +1 -1
  105. package/dist/flows/run/handlers/end.d.ts +1 -1
  106. package/dist/flows/run/handlers/group.d.ts +1 -1
  107. package/dist/flows/run/handlers/index.d.ts +2 -2
  108. package/dist/flows/run/handlers/parallel.d.ts +1 -1
  109. package/dist/flows/run/handlers/start.d.ts +1 -1
  110. package/dist/flows/run/handlers/support.d.ts +3 -27
  111. package/dist/flows/run/handlers/test.d.ts +1 -1
  112. package/dist/flows/run/handlers/types.d.ts +27 -0
  113. package/dist/flows/run/handlers/types.js +1 -0
  114. package/dist/flows/run/index.d.ts +1 -1
  115. package/dist/flows/run/prepare-flow.d.ts +14 -0
  116. package/dist/flows/run/prepare-flow.js +44 -0
  117. package/dist/flows/run/project.d.ts +33 -2
  118. package/dist/flows/run/project.js +139 -57
  119. package/dist/flows/run/run.d.ts +3 -4
  120. package/dist/flows/run/run.js +23 -193
  121. package/dist/flows/run/walker.d.ts +56 -0
  122. package/dist/flows/run/walker.js +225 -0
  123. package/dist/flows/steps/catalog.d.ts +1 -1
  124. package/dist/flows/steps/handler.d.ts +1 -1
  125. package/dist/flows/steps/set.d.ts +1 -1
  126. package/dist/index.js +1 -1
  127. package/dist/locator/actionability.d.ts +33 -0
  128. package/dist/locator/actionability.js +104 -0
  129. package/dist/locator/chain-resolver.d.ts +20 -0
  130. package/dist/locator/chain-resolver.js +102 -0
  131. package/dist/locator/document-swap.d.ts +1 -0
  132. package/dist/locator/document-swap.js +11 -0
  133. package/dist/locator/element-state.d.ts +3 -1
  134. package/dist/locator/element-state.js +11 -3
  135. package/dist/locator/file-actions.d.ts +35 -0
  136. package/dist/locator/file-actions.js +55 -0
  137. package/dist/{script/helpers.js → locator/helper-realm.js} +2 -2
  138. package/dist/locator/input-actions.d.ts +18 -0
  139. package/dist/locator/input-actions.js +80 -0
  140. package/dist/locator/label-locator.d.ts +1 -1
  141. package/dist/locator/label-locator.js +5 -12
  142. package/dist/locator/locator.d.ts +14 -37
  143. package/dist/locator/locator.js +68 -424
  144. package/dist/locator/page-scripts.d.ts +16 -0
  145. package/dist/locator/page-scripts.js +54 -0
  146. package/dist/locator/page-search.d.ts +10 -0
  147. package/dist/locator/page-search.js +19 -0
  148. package/dist/locator/selector-errors.d.ts +0 -4
  149. package/dist/locator/selector-errors.js +0 -7
  150. package/dist/locator/selector.d.ts +14 -13
  151. package/dist/locator/selector.js +10 -16
  152. package/dist/locator/text-locator.d.ts +1 -1
  153. package/dist/locator/text-locator.js +5 -9
  154. package/dist/logger/index.d.ts +6 -0
  155. package/dist/logger/index.js +14 -7
  156. package/dist/network/bidi-wire.d.ts +10 -0
  157. package/dist/network/bidi-wire.js +38 -0
  158. package/dist/network/intercept.d.ts +30 -0
  159. package/dist/network/intercept.js +130 -0
  160. package/dist/network/network-tracker.d.ts +10 -47
  161. package/dist/network/network-tracker.js +34 -68
  162. package/dist/network/network-types.d.ts +33 -0
  163. package/dist/network/network-types.js +1 -0
  164. package/dist/network/network-wait.d.ts +7 -2
  165. package/dist/network/network-wait.js +14 -4
  166. package/dist/network/response.d.ts +1 -1
  167. package/dist/network/response.js +1 -1
  168. package/dist/network/route.d.ts +92 -0
  169. package/dist/network/route.js +142 -0
  170. package/dist/network/router.d.ts +6 -91
  171. package/dist/network/router.js +25 -258
  172. package/dist/recorder/expectation.d.ts +5 -0
  173. package/dist/recorder/expectation.js +20 -0
  174. package/dist/recorder/files-recorder.d.ts +30 -0
  175. package/dist/recorder/files-recorder.js +88 -0
  176. package/dist/recorder/normalise.d.ts +17 -6
  177. package/dist/recorder/normalise.js +96 -91
  178. package/dist/recorder/options.d.ts +56 -0
  179. package/dist/recorder/options.js +1 -0
  180. package/dist/recorder/page-messages.d.ts +35 -0
  181. package/dist/recorder/page-messages.js +26 -0
  182. package/dist/recorder/ranking.js +1 -0
  183. package/dist/recorder/realms.d.ts +6 -0
  184. package/dist/recorder/realms.js +13 -0
  185. package/dist/recorder/recorder.d.ts +10 -74
  186. package/dist/recorder/recorder.js +39 -199
  187. package/dist/recorder/session-browser.d.ts +22 -0
  188. package/dist/recorder/session-browser.js +21 -0
  189. package/dist/recorder/session-secrets.d.ts +8 -0
  190. package/dist/recorder/session-secrets.js +45 -0
  191. package/dist/recorder/session.d.ts +5 -14
  192. package/dist/recorder/session.js +22 -120
  193. package/dist/recorder/spec-source.d.ts +39 -0
  194. package/dist/recorder/spec-source.js +84 -0
  195. package/dist/recorder/toolbar/action-guard.d.ts +21 -0
  196. package/dist/recorder/toolbar/action-guard.js +43 -0
  197. package/dist/recorder/toolbar/actions/assert.d.ts +4 -0
  198. package/dist/recorder/toolbar/actions/assert.js +52 -0
  199. package/dist/recorder/toolbar/actions/insert.d.ts +6 -0
  200. package/dist/recorder/toolbar/actions/insert.js +93 -0
  201. package/dist/recorder/toolbar/actions/pick.d.ts +8 -0
  202. package/dist/recorder/toolbar/actions/pick.js +26 -0
  203. package/dist/recorder/toolbar/actions/reply.d.ts +7 -0
  204. package/dist/recorder/toolbar/actions/reply.js +10 -0
  205. package/dist/recorder/toolbar/controller-fixture.d.ts +47 -0
  206. package/dist/recorder/toolbar/controller-fixture.js +129 -0
  207. package/dist/recorder/toolbar/controller.d.ts +12 -78
  208. package/dist/recorder/toolbar/controller.js +35 -247
  209. package/dist/recorder/toolbar/inspect.d.ts +1 -3
  210. package/dist/recorder/toolbar/inspect.js +2 -2
  211. package/dist/recorder/toolbar/page-scope.d.ts +19 -0
  212. package/dist/recorder/toolbar/page-scope.js +43 -0
  213. package/dist/recorder/toolbar/protocol.d.ts +2 -1
  214. package/dist/recorder/toolbar/source/attach.d.ts +2 -0
  215. package/dist/recorder/toolbar/source/attach.js +41 -0
  216. package/dist/recorder/toolbar/source/dom.d.ts +2 -0
  217. package/dist/recorder/toolbar/source/dom.js +47 -0
  218. package/dist/recorder/toolbar/source/drag.d.ts +2 -0
  219. package/dist/recorder/toolbar/source/drag.js +32 -0
  220. package/dist/recorder/toolbar/source/events-page.d.ts +2 -0
  221. package/dist/recorder/toolbar/source/events-page.js +67 -0
  222. package/dist/recorder/toolbar/source/events-toolbar.d.ts +2 -0
  223. package/dist/recorder/toolbar/source/events-toolbar.js +35 -0
  224. package/dist/recorder/toolbar/source/fields.d.ts +2 -0
  225. package/dist/recorder/toolbar/source/fields.js +44 -0
  226. package/dist/recorder/toolbar/source/handlers.d.ts +2 -0
  227. package/dist/recorder/toolbar/source/handlers.js +134 -0
  228. package/dist/recorder/toolbar/source/names.d.ts +2 -0
  229. package/dist/recorder/toolbar/source/names.js +12 -0
  230. package/dist/recorder/toolbar/source/picking.d.ts +2 -0
  231. package/dist/recorder/toolbar/source/picking.js +72 -0
  232. package/dist/recorder/toolbar/source/placement.d.ts +2 -0
  233. package/dist/recorder/toolbar/source/placement.js +45 -0
  234. package/dist/recorder/toolbar/source/recorder-side.d.ts +2 -0
  235. package/dist/recorder/toolbar/source/recorder-side.js +54 -0
  236. package/dist/recorder/toolbar/source/setup.d.ts +2 -0
  237. package/dist/recorder/toolbar/source/setup.js +50 -0
  238. package/dist/recorder/toolbar/source/views-panels.d.ts +2 -0
  239. package/dist/recorder/toolbar/source/views-panels.js +150 -0
  240. package/dist/recorder/toolbar/source/views-rail.d.ts +2 -0
  241. package/dist/recorder/toolbar/source/views-rail.js +40 -0
  242. package/dist/recorder/toolbar/source/views-render.d.ts +2 -0
  243. package/dist/recorder/toolbar/source/views-render.js +48 -0
  244. package/dist/recorder/toolbar/source.js +30 -856
  245. package/dist/recorder/toolbar/types.d.ts +70 -0
  246. package/dist/recorder/toolbar/types.js +1 -0
  247. package/dist/recorder/toolbar-bridge.d.ts +43 -0
  248. package/dist/recorder/toolbar-bridge.js +106 -0
  249. package/dist/runner/browsers.d.ts +0 -12
  250. package/dist/runner/browsers.js +1 -33
  251. package/dist/runner/options.d.ts +31 -0
  252. package/dist/runner/options.js +1 -0
  253. package/dist/runner/pooled-run.d.ts +43 -0
  254. package/dist/runner/pooled-run.js +75 -0
  255. package/dist/runner/ports.d.ts +2 -0
  256. package/dist/runner/ports.js +2 -0
  257. package/dist/runner/registry.d.ts +1 -1
  258. package/dist/runner/registry.js +1 -1
  259. package/dist/runner/reporter.d.ts +31 -10
  260. package/dist/runner/reporter.js +39 -6
  261. package/dist/runner/run-test.d.ts +16 -0
  262. package/dist/runner/run-test.js +178 -0
  263. package/dist/runner/run.d.ts +5 -3
  264. package/dist/runner/run.js +28 -29
  265. package/dist/runner/select.d.ts +24 -0
  266. package/dist/runner/select.js +77 -0
  267. package/dist/runner/test-dir.d.ts +13 -0
  268. package/dist/runner/test-dir.js +27 -0
  269. package/dist/runner/test-runner.d.ts +25 -56
  270. package/dist/runner/test-runner.js +81 -270
  271. package/dist/runner/worker-pool.d.ts +67 -0
  272. package/dist/runner/worker-pool.js +275 -0
  273. package/dist/runner/worker-process.d.ts +6 -0
  274. package/dist/runner/worker-process.js +54 -0
  275. package/dist/runner/worker-protocol.d.ts +67 -0
  276. package/dist/runner/worker-protocol.js +1 -0
  277. package/dist/runner/worker-session.d.ts +7 -0
  278. package/dist/runner/worker-session.js +60 -0
  279. package/dist/runner/worker.d.ts +26 -0
  280. package/dist/runner/worker.js +121 -0
  281. package/dist/runner/workers-option.d.ts +15 -0
  282. package/dist/runner/workers-option.js +22 -0
  283. package/dist/script/call-function.d.ts +1 -9
  284. package/dist/script/call-function.js +2 -19
  285. package/dist/script/element-handle.d.ts +1 -2
  286. package/dist/script/element-handle.js +1 -2
  287. package/dist/script/serialize.d.ts +3 -12
  288. package/dist/script/serialize.js +53 -62
  289. package/dist/steps/parse/call.d.ts +3 -0
  290. package/dist/steps/parse/call.js +81 -0
  291. package/dist/steps/parse/expectation.d.ts +5 -0
  292. package/dist/steps/parse/expectation.js +71 -0
  293. package/dist/steps/parse/file-entry.d.ts +4 -0
  294. package/dist/steps/parse/file-entry.js +74 -0
  295. package/dist/steps/parse/literals.d.ts +4 -0
  296. package/dist/steps/parse/literals.js +28 -0
  297. package/dist/steps/parse/step.d.ts +4 -0
  298. package/dist/steps/parse/step.js +81 -0
  299. package/dist/steps/parse.js +2 -323
  300. package/dist/transport/bidi-connection.d.ts +4 -15
  301. package/dist/transport/bidi-connection.js +9 -63
  302. package/dist/transport/log-redaction.d.ts +13 -0
  303. package/dist/transport/log-redaction.js +51 -0
  304. package/dist/transport/subscriptions.js +18 -13
  305. package/dist/transport/unsupported.d.ts +7 -0
  306. package/dist/transport/unsupported.js +13 -0
  307. package/dist/types/bidi-modules/browser.d.ts +1 -12
  308. package/dist/types/bidi-modules/browsing-context.d.ts +8 -18
  309. package/dist/types/bidi-modules/emulation.d.ts +29 -21
  310. package/dist/types/bidi-modules/input.d.ts +1 -9
  311. package/dist/types/bidi-modules/network.d.ts +29 -24
  312. package/dist/types/bidi-modules/script.d.ts +0 -9
  313. package/dist/types/bidi-modules/session.d.ts +0 -8
  314. package/dist/types/bidi-modules/storage.d.ts +0 -5
  315. package/dist/types/bidi.d.ts +7 -37
  316. package/dist/types/config.d.ts +12 -0
  317. package/dist/types/test.d.ts +8 -3
  318. package/dist/wait/wait-until.d.ts +1 -5
  319. package/dist/wait/wait-until.js +7 -16
  320. package/package.json +3 -2
  321. package/dist/config/default.d.ts +0 -2
  322. package/dist/config/default.js +0 -3
  323. package/dist/types/bidi-modules/web-extensions.d.ts +0 -9
  324. package/dist/types/bidi-protocols/command.d.ts +0 -7
  325. package/dist/types/bidi-protocols/event.d.ts +0 -17
  326. /package/dist/{script/helpers.d.ts → locator/helper-realm.d.ts} +0 -0
@@ -1,28 +1,25 @@
1
- import assert from "node:assert";
2
1
  import { execSync } from "node:child_process";
3
2
  import { existsSync } from "node:fs";
4
3
  import logger from "../logger/index.js";
5
4
  const supportedBrowsers = ["firefox"];
5
+ const isFindable = (name) => supportedBrowsers.includes(name);
6
6
  const macBrowserPaths = {
7
- chrome: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
8
7
  firefox: "/Applications/Firefox.app/Contents/MacOS/Firefox",
9
8
  };
10
9
  /** @deprecated Launches use the builds SAMURAI downloads (`samurai browsers install`); removed in a later major */
11
10
  export function findBrowser(browserName) {
12
11
  logger.verbose("Checking install location for %s", browserName);
13
- if (!supportedBrowsers.includes(browserName)) {
12
+ if (!isFindable(browserName)) {
14
13
  throw new Error("This browser is not supported. Supported browsers include: " +
15
14
  supportedBrowsers.join(", "));
16
15
  }
17
- let browserLocation = "";
18
- const notFoundMsg = `Cannot find browser ${browserName} on this machine! Is it installed?`;
16
+ let browserLocation;
19
17
  try {
20
18
  browserLocation = tryFindBrowser(browserName);
21
19
  }
22
20
  catch (err) {
23
- throw new Error(notFoundMsg);
21
+ throw Object.assign(new Error(`Cannot find browser ${browserName} on this machine! Is it installed?`), { cause: err });
24
22
  }
25
- assert(browserLocation, notFoundMsg);
26
23
  logger.verbose("Browser found at: %s", browserLocation);
27
24
  return browserLocation;
28
25
  }
@@ -42,7 +39,7 @@ function tryFindBrowser(browserName) {
42
39
  function findBrowserWindows(browserName) {
43
40
  // Function to prevent search the entire file system
44
41
  // We'll only try the most common paths
45
- const possibleDirs = ["Program\ Files", "Program\ Files (x86)"];
42
+ const possibleDirs = ["Program Files", "Program Files (x86)"];
46
43
  const where = (dirPath) => {
47
44
  const command = `where /r ${dirPath} ${browserName}.exe`;
48
45
  try {
@@ -82,7 +79,6 @@ function findBrowserMac(browserName) {
82
79
  return checkPathExists(macBrowserPaths[browserName]);
83
80
  }
84
81
  const linuxBinaryNames = {
85
- chrome: "google-chrome",
86
82
  firefox: "firefox",
87
83
  };
88
84
  function findBrowserLinux(browserName) {
@@ -1,41 +1,15 @@
1
1
  import type { SupportedBrowser } from "../types/browser.js";
2
- import { BiDiConnector } from "../transport/bidi-connection.js";
2
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
3
3
  import Page from "./page.js";
4
- import { SESSION_CAPABILITIES } from "./session-capabilities.js";
5
4
  import { type BrowserProcess } from "./chrome-launch.js";
6
5
  import { BrowserContext } from "./browser-context.js";
7
6
  import type { UserPromptHandler } from "../types/bidi-modules/session.js";
8
7
  import { type EmulationOptions } from "./emulation.js";
9
8
  import { ChromeDownloadFolders } from "./download-folders.js";
9
+ import { type BrowserLaunchOptions } from "./launch-flags.js";
10
10
  import type { BiDiCommands } from "../types/bidi.js";
11
11
  import type { CookieFilter, PartialCookie, PartitionDescriptor } from "../types/bidi-modules/storage.js";
12
- export { SESSION_CAPABILITIES };
13
- /** session.new got no reply in time */
14
- export declare class BrowserStartTimeoutError extends Error {
15
- constructor(label: string, timeout: number);
16
- }
17
- /** Sends session.new with its own, longer timeout; `label` names the browser in the timeout error */
18
- export declare function startSession(connector: Pick<BiDiConnector, "send">, label: string, commandTimeout: number | undefined): Promise<void>;
19
- export declare const browserLaunchFlag: (browserName: SupportedBrowser, launchOptions: Partial<BrowserLaunchOptions>) => string[];
20
- /**
21
- * Stops what a failed launch started. A browser left running holds the profile (the next launch
22
- * fails with "already open") and keeps the test process alive.
23
- */
24
- export declare function abandonLaunch(browserProc: {
25
- kill(): boolean;
26
- }, biDiConnector: {
27
- kill(): void;
28
- } | undefined): void;
29
- /** Env added to a browser's process */
30
- export declare const browserLaunchEnv: (browserName: SupportedBrowser, appDataPath?: string) => Record<string, string>;
31
- export interface BrowserLaunchOptions {
32
- /** Which port to use for the WebDeveloper tools */
33
- port: number;
34
- /** The browser profile folder: user.js goes in it for Firefox, Chrome's user data for Chrome */
35
- profileDir: string;
36
- /** Start the browser in headless mode or in UI mode */
37
- headless: boolean;
38
- }
12
+ export type { BrowserLaunchOptions } from "./launch-flags.js";
39
13
  /** Options of `browser.newContext()` */
40
14
  export interface ContextOptions extends EmulationOptions {
41
15
  acceptInsecureCerts?: boolean;
@@ -95,5 +69,3 @@ export declare class Browser {
95
69
  partitionKey: import("../types/bidi-modules/storage.js").PartitionKey;
96
70
  }>;
97
71
  }
98
- /** Writes the browser's user.js unless it already holds the current prefs; an outdated one is replaced */
99
- export declare function ensureBrowserProfile(browserName: SupportedBrowser, profilePath?: string | undefined): void;
@@ -1,119 +1,19 @@
1
- import { spawn } from "node:child_process";
2
1
  import { setTimeout as sleep } from "node:timers/promises";
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
- import { projectBrowser } from "../browsers/resolve.js";
5
2
  import path from "node:path";
6
- import { BiDiConnector, DEFAULT_COMMAND_TIMEOUT, } from "../transport/bidi-connection.js";
7
- import { BiDiError } from "../transport/bidi-error.js";
3
+ import { projectBrowser } from "../browsers/resolve.js";
8
4
  import Page from "./page.js";
9
- import { SESSION_CAPABILITIES } from "./session-capabilities.js";
10
5
  import { launchChrome } from "./chrome-launch.js";
11
6
  import { BrowserContext } from "./browser-context.js";
12
7
  import { validateEmulation } from "./emulation.js";
13
8
  import { prepareDownloadsDir, removeIfEmpty } from "./downloads-dir.js";
14
9
  import { ChromeDownloadFolders } from "./download-folders.js";
10
+ import { launchFirefox } from "./launch-firefox.js";
11
+ import { defaultLaunchOptions, ensureBrowserProfile, } from "./launch-flags.js";
12
+ import { abandonLaunch } from "./session.js";
15
13
  import logger from "../logger/index.js";
16
- import { browsersDir, projectDir, readConfig } from "../config/config.js";
17
- export { SESSION_CAPABILITIES };
18
- /** session.new waits at least this long (ms): the first start after an install can take a while (macOS checks the new app) */
19
- const SESSION_START_TIMEOUT = 120_000;
20
- /** session.new got no reply in time */
21
- export class BrowserStartTimeoutError extends Error {
22
- constructor(label, timeout) {
23
- super(`${label} did not finish starting within ${Math.round(timeout / 1000)}s. The first start after an install can be slow; run again.`);
24
- this.name = "BrowserStartTimeoutError";
25
- }
26
- }
27
- /** Sends session.new with its own, longer timeout; `label` names the browser in the timeout error */
28
- export async function startSession(connector, label, commandTimeout) {
29
- const timeout = Math.max(commandTimeout ?? DEFAULT_COMMAND_TIMEOUT, SESSION_START_TIMEOUT);
30
- try {
31
- await connector.send("session.new", { capabilities: SESSION_CAPABILITIES }, { timeout });
32
- }
33
- catch (err) {
34
- if (err instanceof BiDiError && err.code === "timeout")
35
- throw new BrowserStartTimeoutError(label, timeout);
36
- throw err;
37
- }
38
- }
14
+ import { projectDir, readConfig } from "../config/config.js";
39
15
  /** Time (ms) to wait for Firefox to answer browser.close before killing the process */
40
16
  const BROWSER_CLOSE_GRACE = 2000;
41
- const browserProfilePath = (browserName) => path.join(browsersDir(), "profiles", browserName, "user.js");
42
- const baseBrowserLaunchFlags = {
43
- // Chrome is started by its chromedriver: see chrome-launch.ts
44
- chrome: [],
45
- firefox: ["--no-sandbox", "--no-remote"],
46
- };
47
- /** Read when a browser launches, since the profile folder depends on the active project */
48
- const defaultLaunchOptions = (browserName) => ({
49
- port: 9222,
50
- profileDir: path.dirname(browserProfilePath(browserName)),
51
- headless: true,
52
- });
53
- export const browserLaunchFlag = (browserName, launchOptions) => {
54
- const defLaunchOpts = defaultLaunchOptions(browserName);
55
- const port = launchOptions.port || defLaunchOpts.port;
56
- const profileDir = launchOptions.profileDir || defLaunchOpts.profileDir;
57
- const headless = launchOptions.headless ?? defLaunchOpts.headless;
58
- const baseLaunchFlags = [...baseBrowserLaunchFlags[browserName]];
59
- if (headless) {
60
- baseLaunchFlags.push("--headless");
61
- }
62
- baseLaunchFlags.push(`--remote-debugging-port=${port}`);
63
- switch (browserName) {
64
- case "chrome":
65
- break;
66
- case "firefox":
67
- baseLaunchFlags.push("--profile", profileDir);
68
- // The home page (about:home) is privileged: BiDi refuses commands such as setViewport on it
69
- baseLaunchFlags.push("about:blank");
70
- break;
71
- }
72
- logger.verbose("Launch flags for browser %s: ", browserName, {
73
- baseLaunchFlags,
74
- });
75
- return baseLaunchFlags;
76
- };
77
- /**
78
- * Stops what a failed launch started. A browser left running holds the profile (the next launch
79
- * fails with "already open") and keeps the test process alive.
80
- */
81
- export function abandonLaunch(browserProc, biDiConnector) {
82
- biDiConnector?.kill();
83
- browserProc.kill();
84
- }
85
- // On macOS a spawned browser is attributed to the terminal, which is denied access to
86
- // ~/Library/Application Support/Firefox (profiles.ini). Point Firefox at a local app data dir instead.
87
- const browserAppDataPath = (browserName) => path.join(browsersDir(), "app-data", browserName);
88
- /** Env added to a browser's process */
89
- export const browserLaunchEnv = (browserName, appDataPath = browserAppDataPath(browserName)) => {
90
- mkdirSync(appDataPath, { recursive: true });
91
- logger.debug("Using app data dir for %s: %s", browserName, appDataPath);
92
- switch (browserName) {
93
- case "chrome":
94
- return {};
95
- case "firefox":
96
- // The project profile may have been opened by a newer Firefox (the system one, before 0.6);
97
- // without this Firefox refuses to start on it
98
- return { MOZ_APP_DATA: appDataPath, MOZ_ALLOW_DOWNGRADE: "1" };
99
- }
100
- };
101
- const browserProfiles = {
102
- // Chrome needs no prefs file; its profile folder is created at launch
103
- chrome: "",
104
- firefox: `
105
- user_pref("devtools.debugger.remote-enabled", true);
106
- user_pref("devtools.debugger.prompt-connection", false);
107
- user_pref("devtools.chrome.enabled", true);
108
- // The home page (about:home) is privileged: BiDi refuses commands such as setViewport on it
109
- user_pref("browser.startup.page", 0);
110
- user_pref("browser.startup.homepage", "about:blank");
111
- user_pref("browser.newtabpage.enabled", false);
112
- `,
113
- };
114
- // Firefox logs "WebDriver BiDi listening on ws://<host>:<port>" without the endpoint path
115
- const FIREFOX_WS_PATH = "/session";
116
- const FIREFOX_WS_REGEX = /ws:\/\/(\d){1,3}.(\d){1,3}.(\d){1,3}.(\d){1,3}:(\d){4}/g;
117
17
  export class Browser {
118
18
  constructor({ browserProc, biDiConnector, downloadsDir, downloadFolders, }) {
119
19
  this.userContexts = new Map();
@@ -143,86 +43,41 @@ export class Browser {
143
43
  signal?.throwIfAborted();
144
44
  logger.verbose("Trying to launch browser %s", browserName);
145
45
  const resolved = await projectBrowser(browserName);
146
- const { executablePath: browserLocation, label } = resolved;
147
- logger.debug("Found browser install location at: %s", browserLocation);
148
- ensureBrowserProfile(browserName);
149
- if (browserName === "chrome") {
150
- const commandTimeout = await readCommandTimeout();
151
- const { proc, connector } = await launchChrome({
152
- executablePath: browserLocation,
46
+ logger.debug("Found browser install location at: %s", resolved.executablePath);
47
+ ensureBrowserProfile(browserName, launchOptions.profileDir &&
48
+ path.join(launchOptions.profileDir, "user.js"));
49
+ const commandTimeout = await readCommandTimeout();
50
+ // Both launchers hand back an open BiDi session: Firefox after session.new, Chrome with the one
51
+ // its chromedriver created
52
+ const { proc, connector } = browserName === "chrome"
53
+ ? await launchChrome({
54
+ executablePath: resolved.executablePath,
153
55
  driverPath: resolved.driverPath,
154
- profileDir: launchOptions.profileDir ?? defaultLaunchOptions("chrome").profileDir,
56
+ profileDir: launchOptions.profileDir ??
57
+ defaultLaunchOptions("chrome").profileDir,
155
58
  headless: launchOptions.headless ?? defaultLaunchOptions("chrome").headless,
156
59
  commandTimeout,
157
60
  signal,
61
+ })
62
+ : await launchFirefox({
63
+ executablePath: resolved.executablePath,
64
+ label: resolved.label,
65
+ launchOptions,
66
+ commandTimeout,
67
+ signal,
158
68
  });
159
- try {
160
- // The chromedriver session is the BiDi session: there is no session.new to send
161
- return await Browser.attach(proc, connector, browserName);
162
- }
163
- catch (err) {
164
- abandonLaunch(proc, connector);
165
- throw err;
166
- }
69
+ try {
70
+ return await Browser.attach(proc, connector, browserName);
71
+ }
72
+ catch (err) {
73
+ abandonLaunch(proc, connector);
74
+ throw err;
167
75
  }
168
- logger.verbose("Trying to spawn %s process", browserName);
169
- const browserProc = spawn(browserLocation, browserLaunchFlag(browserName, launchOptions), { env: { ...process.env, ...browserLaunchEnv(browserName) } });
170
- return new Promise((resolve, reject) => {
171
- const onUnexpectedClose = (code) => {
172
- reject(new Error(`Browser process exited unexpectedly with code ${code}`));
173
- };
174
- browserProc.on("close", onUnexpectedClose);
175
- const onAbort = () => {
176
- logger.verbose("Launch of %s aborted, killing process", browserName);
177
- browserProc.kill();
178
- reject(signal?.reason);
179
- };
180
- signal?.addEventListener("abort", onAbort, { once: true });
181
- browserProc.stdout.on("data", (data) => {
182
- logger.debug(`${browserName} stdout: ${data}`);
183
- if (String(data).match(/!!!\scould\snot\sstart\sserver\son\sport\s(\d){4}/g)) {
184
- browserProc.kill();
185
- reject(`PORT ${launchOptions.port} is already in use!`);
186
- }
187
- });
188
- browserProc.stderr.on("data", async (data) => {
189
- logger.debug(`${browserName} stderr: ${data}`);
190
- const urlMatch = String(data).match(FIREFOX_WS_REGEX);
191
- logger.debug("Browser announced its endpoint", { urlMatch });
192
- if (!Array.isArray(urlMatch) || urlMatch.length === 0) {
193
- return;
194
- }
195
- // Killing the process mid-handshake rejects pending BiDi calls; surface that via reject
196
- // instead of an unhandled rejection from this async listener
197
- let biDiConnector;
198
- try {
199
- let url = urlMatch[0] + FIREFOX_WS_PATH;
200
- logger.verbose("Websocket URL match: %s", url);
201
- const commandTimeout = await readCommandTimeout();
202
- biDiConnector = await BiDiConnector.connect(url, { commandTimeout });
203
- logger.verbose("Starting new session");
204
- const status = await biDiConnector.send("session.status", {});
205
- logger.verbose("Session status", status);
206
- await startSession(biDiConnector, label, commandTimeout);
207
- logger.verbose("Session started successfully");
208
- const launched = await Browser.attach(browserProc, biDiConnector, browserName);
209
- browserProc.off("close", onUnexpectedClose);
210
- signal?.removeEventListener("abort", onAbort);
211
- resolve(launched);
212
- }
213
- catch (err) {
214
- browserProc.off("close", onUnexpectedClose);
215
- signal?.removeEventListener("abort", onAbort);
216
- abandonLaunch(browserProc, biDiConnector);
217
- reject(err);
218
- }
219
- });
220
- });
221
76
  }
222
77
  /** Sets up a browser whose BiDi session is open: downloads folder, the first page and what it tracks */
223
78
  static async attach(browserProc, biDiConnector, browserName) {
224
79
  biDiConnector.browserName = browserName;
225
- const downloadsDir = await prepareDownloadsDir(biDiConnector, path.resolve(projectDir(), (await readOptionalConfig("downloadsDir")) ?? "result/downloads"));
80
+ const downloadsDir = await prepareDownloadsDir(biDiConnector, path.resolve(projectDir(), (await readDownloadsDir()) ?? "result/downloads"));
226
81
  // Chrome replaces a file that has the name of a download; this keeps every download
227
82
  const downloadFolders = browserName === "chrome" && downloadsDir
228
83
  ? new ChromeDownloadFolders(biDiConnector, downloadsDir)
@@ -247,13 +102,7 @@ export class Browser {
247
102
  else {
248
103
  page = await browser.newPage({ type: "tab" });
249
104
  }
250
- await page.applyDefaultViewport();
251
- // Requests made before tracking starts are invisible to it, and a dialog nobody watches would hang the page
252
- await page.startNetworkTracking();
253
- await page.startDialogHandling();
254
- await page.startLogging();
255
- await page.startDownloadTracking();
256
- await page.startFileChooserTracking();
105
+ await page.startTracking();
257
106
  return { browser, page };
258
107
  }
259
108
  /** Closes the browser via BiDi browser.close, then makes sure the process has exited */
@@ -296,12 +145,7 @@ export class Browser {
296
145
  logger.debug("Created browsing context", { createBrowserContext });
297
146
  const page = new Page(this.biDiConnector, createBrowserContext.context);
298
147
  this.contextFor(userContext).register(page);
299
- await page.applyDefaultViewport();
300
- await page.startNetworkTracking();
301
- await page.startDialogHandling();
302
- await page.startLogging();
303
- await page.startDownloadTracking();
304
- await page.startFileChooserTracking();
148
+ await page.startTracking();
305
149
  return page;
306
150
  }
307
151
  /**
@@ -363,13 +207,13 @@ export class Browser {
363
207
  });
364
208
  }
365
209
  }
366
- /** A config value, or undefined when it is not set or the config cannot be read */
367
- async function readOptionalConfig(key) {
210
+ /** The configured downloads folder; a missing or unreadable config falls back to the default */
211
+ async function readDownloadsDir() {
368
212
  try {
369
- return await readConfig(key);
213
+ return await readConfig("downloadsDir");
370
214
  }
371
215
  catch (err) {
372
- logger.debug("Could not read %s from config, using the default", key, {
216
+ logger.debug("Could not read downloadsDir from config, using the default", {
373
217
  err,
374
218
  });
375
219
  return undefined;
@@ -385,30 +229,3 @@ async function readCommandTimeout() {
385
229
  return undefined;
386
230
  }
387
231
  }
388
- /** Writes the browser's user.js unless it already holds the current prefs; an outdated one is replaced */
389
- export function ensureBrowserProfile(browserName, profilePath = browserProfilePath(browserName)) {
390
- logger.verbose("Checking browser profile for: %s", browserName);
391
- if (!profilePath) {
392
- logger.verbose("No browser profile path declared, skipping...");
393
- return;
394
- }
395
- if (existsSync(profilePath)) {
396
- if (readFileSync(profilePath, "utf8") === browserProfiles[browserName]) {
397
- logger.verbose("Browser profile is up to date at: %s", profilePath);
398
- return;
399
- }
400
- logger.verbose("Browser profile at %s is outdated, rewriting it", profilePath);
401
- }
402
- else {
403
- logger.verbose("No browser profile created yet for %s", browserName);
404
- }
405
- createBrowserProfile(browserName, profilePath);
406
- }
407
- function createBrowserProfile(browserName, profilePath) {
408
- logger.verbose("Creating browser profile for %s at %s", browserName, profilePath);
409
- mkdirSync(path.dirname(profilePath), { recursive: true });
410
- writeFileSync(profilePath, browserProfiles[browserName], {
411
- encoding: "utf8",
412
- });
413
- logger.verbose("Browser profile created");
414
- }
@@ -8,7 +8,7 @@ export interface BrowserProcess {
8
8
  once(event: "exit", listener: (...args: any[]) => void): unknown;
9
9
  kill(signal?: NodeJS.Signals): boolean;
10
10
  }
11
- export interface ChromeLaunch {
11
+ interface ChromeLaunch {
12
12
  executablePath: string;
13
13
  driverPath: string;
14
14
  profileDir: string;
@@ -67,3 +67,4 @@ export declare class ChromeProcess extends EventEmitter implements BrowserProces
67
67
  ended(): void;
68
68
  kill(signal?: NodeJS.Signals): boolean;
69
69
  }
70
+ export {};
@@ -0,0 +1,32 @@
1
+ import type EventEmitter from "node:events";
2
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
3
+ import type { BrowsingContext } from "../types/bidi-modules/browsing-context.js";
4
+ import type { ContextTree } from "./context-tree.js";
5
+ /** The context tree of a page: created on first use, readable once it exists */
6
+ export interface TreeSource {
7
+ ensure(): Promise<ContextTree>;
8
+ peek(): ContextTree | undefined;
9
+ }
10
+ /**
11
+ * Watches the JavaScript dialogs of one page (frames included) and hands each to the `dialog` listeners of
12
+ * `emitter`; a dialog nobody answers gets the default answer, so nothing hangs.
13
+ */
14
+ export declare class DialogHandling {
15
+ private connector;
16
+ private contextId;
17
+ private tree;
18
+ private emitter;
19
+ private lazySubscription;
20
+ private openDialogs;
21
+ constructor(connector: BiDiConnector, contextId: BrowsingContext, tree: TreeSource, emitter: EventEmitter);
22
+ /** Starts watching for dialogs; the browser does this when it opens a page, `page.on("dialog")` when needed */
23
+ start(): Promise<void>;
24
+ /** Stops listening, forgets the open dialogs and gives the subscription back */
25
+ dispose(): Promise<void>;
26
+ private stopListening;
27
+ private ownsContext;
28
+ private onPromptOpened;
29
+ private onPromptClosed;
30
+ /** Lets the listeners answer; a dialog still open afterwards gets the default answer, so nothing hangs */
31
+ private dispatchDialog;
32
+ }
@@ -0,0 +1,85 @@
1
+ import logger from "../logger/index.js";
2
+ import { Dialog } from "./dialog.js";
3
+ import { Lazy } from "./lazy.js";
4
+ /**
5
+ * Watches the JavaScript dialogs of one page (frames included) and hands each to the `dialog` listeners of
6
+ * `emitter`; a dialog nobody answers gets the default answer, so nothing hangs.
7
+ */
8
+ export class DialogHandling {
9
+ constructor(connector, contextId, tree, emitter) {
10
+ this.connector = connector;
11
+ this.contextId = contextId;
12
+ this.tree = tree;
13
+ this.emitter = emitter;
14
+ this.lazySubscription = new Lazy();
15
+ this.openDialogs = new Map();
16
+ this.onPromptOpened = (params) => {
17
+ if (!this.ownsContext(params.context))
18
+ return;
19
+ this.dispatchDialog(new Dialog(this.connector, this.contextId, params), params).catch((err) => logger.error("Handling a dialog failed", { err }));
20
+ };
21
+ this.onPromptClosed = (params) => {
22
+ if (!this.ownsContext(params.context))
23
+ return;
24
+ const dialog = this.openDialogs.get(params.context);
25
+ this.openDialogs.delete(params.context);
26
+ dialog?.markClosed({
27
+ accepted: params.accepted,
28
+ ...(params.userText !== undefined && { userText: params.userText }),
29
+ });
30
+ };
31
+ }
32
+ /** Starts watching for dialogs; the browser does this when it opens a page, `page.on("dialog")` when needed */
33
+ async start() {
34
+ await this.lazySubscription.get(async () => {
35
+ await this.tree.ensure();
36
+ this.connector.onEvent("browsingContext.userPromptOpened", this.onPromptOpened);
37
+ this.connector.onEvent("browsingContext.userPromptClosed", this.onPromptClosed);
38
+ try {
39
+ return await this.connector.subscribe([
40
+ "browsingContext.userPromptOpened",
41
+ "browsingContext.userPromptClosed",
42
+ ]);
43
+ }
44
+ catch (err) {
45
+ this.stopListening();
46
+ throw err;
47
+ }
48
+ });
49
+ }
50
+ /** Stops listening, forgets the open dialogs and gives the subscription back */
51
+ async dispose() {
52
+ const subscription = this.lazySubscription.take();
53
+ this.stopListening();
54
+ this.openDialogs.clear();
55
+ await (await subscription)?.unsubscribe();
56
+ }
57
+ stopListening() {
58
+ this.connector.offEvent("browsingContext.userPromptOpened", this.onPromptOpened);
59
+ this.connector.offEvent("browsingContext.userPromptClosed", this.onPromptClosed);
60
+ }
61
+ ownsContext(context) {
62
+ return (this.tree.peek()?.isWithin(context, this.contextId) ??
63
+ context === this.contextId);
64
+ }
65
+ /** Lets the listeners answer; a dialog still open afterwards gets the default answer, so nothing hangs */
66
+ async dispatchDialog(dialog, params) {
67
+ this.openDialogs.set(params.context, dialog);
68
+ // rawListeners: the wrappers that make a `once` listener remove itself when called
69
+ const listeners = this.emitter.rawListeners("dialog");
70
+ await Promise.all(listeners.map(async (listener) => {
71
+ try {
72
+ await listener(dialog);
73
+ }
74
+ catch (err) {
75
+ logger.error("A dialog listener threw", { err });
76
+ }
77
+ }));
78
+ if (dialog.handled)
79
+ return;
80
+ // Staying on a page is not what a test that navigates wants: leave it, dismiss everything else
81
+ const accept = params.type === "beforeunload";
82
+ logger.warn('Dialog "%s" was %s automatically; add page.on("dialog") to handle it', params.message, accept ? "accepted" : "dismissed");
83
+ await (accept ? dialog.accept() : dialog.dismiss());
84
+ }
85
+ }
@@ -0,0 +1,44 @@
1
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
2
+ import type { Subscription } from "../transport/subscriptions.js";
3
+ import type { BrowsingContext } from "../types/bidi-modules/browsing-context.js";
4
+ /** What a wait for the document can reach */
5
+ export type DocumentState = "load" | "domcontentloaded";
6
+ /**
7
+ * Follows the document of one page: which navigations reached DOMContentLoaded and load, and waits for a state of
8
+ * the document that is showing (or of the navigation that is on its way).
9
+ */
10
+ export declare class DocumentWatcher {
11
+ private connector;
12
+ private contextId;
13
+ /** How far navigations that did not wait for the load got, by navigation id (the latest few) */
14
+ private navigationProgress;
15
+ private lazyEvents;
16
+ constructor(connector: BiDiConnector, contextId: BrowsingContext);
17
+ /** The progress of `navigation`, created (and the oldest forgotten) when it is not known yet */
18
+ progressOf(navigation: string): {
19
+ domContentLoaded: boolean;
20
+ load: boolean;
21
+ };
22
+ private onDocumentEvent;
23
+ private onDomContentLoaded;
24
+ private onLoad;
25
+ /** Starts recording which navigations reached DOMContentLoaded and load; kept until dispose */
26
+ watch(): Promise<Subscription>;
27
+ /** Stops listening and gives the subscription back */
28
+ dispose(): Promise<void>;
29
+ private canBeAborted;
30
+ /** Subscribes to what a wait listens for; a browser that lacks the navigationAborted event (Firefox) is asked only once */
31
+ private subscribe;
32
+ /**
33
+ * The first of: the state is reached, the navigation `last` fails, the navigation `last` is aborted. Registered
34
+ * before the state is read, so an event in between is not lost.
35
+ */
36
+ private eventsOf;
37
+ /** Whether the document is at `target` already: a navigation we saw fall short beats the document that is showing */
38
+ private readyStateReached;
39
+ /**
40
+ * Resolves when the document reached `target`; rejects with a NavigationError when the navigation `last` (the
41
+ * latest one navigateTo started) fails or is aborted, and with a WaitTimeoutError after `timeout` ms.
42
+ */
43
+ waitForState(target: DocumentState, timeout: number, last: string | undefined): Promise<void>;
44
+ }