@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
@@ -0,0 +1,160 @@
1
+ import { callFunction } from "../script/call-function.js";
2
+ import { BiDiError } from "../transport/bidi-error.js";
3
+ import { Lazy } from "./lazy.js";
4
+ import { NavigationError } from "./navigation-error.js";
5
+ /** How many navigations keep their load progress */
6
+ const NAVIGATIONS_REMEMBERED = 20;
7
+ /** Events a connection's browser rejected as unknown, so nothing asks for them again */
8
+ const unsupportedEventsByConnector = new WeakMap();
9
+ function unsupportedEvents(connector) {
10
+ let events = unsupportedEventsByConnector.get(connector);
11
+ if (!events)
12
+ unsupportedEventsByConnector.set(connector, (events = new Set()));
13
+ return events;
14
+ }
15
+ /** A wait ends with the navigation it was waiting for failing or being aborted */
16
+ function navigationFailure(url, what) {
17
+ throw new NavigationError(url, what, undefined, "waitForLoadState");
18
+ }
19
+ /**
20
+ * Follows the document of one page: which navigations reached DOMContentLoaded and load, and waits for a state of
21
+ * the document that is showing (or of the navigation that is on its way).
22
+ */
23
+ export class DocumentWatcher {
24
+ constructor(connector, contextId) {
25
+ this.connector = connector;
26
+ this.contextId = contextId;
27
+ /** How far navigations that did not wait for the load got, by navigation id (the latest few) */
28
+ this.navigationProgress = new Map();
29
+ this.lazyEvents = new Lazy();
30
+ this.onDocumentEvent = (kind) => (info) => {
31
+ if (info.context !== this.contextId || info.navigation === null)
32
+ return;
33
+ const progress = this.progressOf(info.navigation);
34
+ progress.domContentLoaded = true;
35
+ if (kind === "load")
36
+ progress.load = true;
37
+ };
38
+ this.onDomContentLoaded = this.onDocumentEvent("domContentLoaded");
39
+ this.onLoad = this.onDocumentEvent("load");
40
+ }
41
+ /** The progress of `navigation`, created (and the oldest forgotten) when it is not known yet */
42
+ progressOf(navigation) {
43
+ let progress = this.navigationProgress.get(navigation);
44
+ if (!progress) {
45
+ progress = { domContentLoaded: false, load: false };
46
+ this.navigationProgress.set(navigation, progress);
47
+ // Only the latest few navigations matter
48
+ if (this.navigationProgress.size > NAVIGATIONS_REMEMBERED) {
49
+ this.navigationProgress.delete(this.navigationProgress.keys().next().value);
50
+ }
51
+ }
52
+ return progress;
53
+ }
54
+ /** Starts recording which navigations reached DOMContentLoaded and load; kept until dispose */
55
+ watch() {
56
+ return this.lazyEvents.get(async () => {
57
+ this.connector.onEvent("browsingContext.domContentLoaded", this.onDomContentLoaded);
58
+ this.connector.onEvent("browsingContext.load", this.onLoad);
59
+ try {
60
+ return await this.connector.subscribe([
61
+ "browsingContext.domContentLoaded",
62
+ "browsingContext.load",
63
+ ]);
64
+ }
65
+ catch (err) {
66
+ this.connector.offEvent("browsingContext.domContentLoaded", this.onDomContentLoaded);
67
+ this.connector.offEvent("browsingContext.load", this.onLoad);
68
+ throw err;
69
+ }
70
+ });
71
+ }
72
+ /** Stops listening and gives the subscription back */
73
+ async dispose() {
74
+ const subscription = this.lazyEvents.take();
75
+ this.connector.offEvent("browsingContext.domContentLoaded", this.onDomContentLoaded);
76
+ this.connector.offEvent("browsingContext.load", this.onLoad);
77
+ await (await subscription)?.unsubscribe();
78
+ }
79
+ canBeAborted() {
80
+ return !unsupportedEvents(this.connector).has("browsingContext.navigationAborted");
81
+ }
82
+ /** Subscribes to what a wait listens for; a browser that lacks the navigationAborted event (Firefox) is asked only once */
83
+ async subscribe(reachedEvent) {
84
+ const required = [
85
+ reachedEvent,
86
+ "browsingContext.navigationFailed",
87
+ ];
88
+ if (!this.canBeAborted())
89
+ return this.connector.subscribe([...required]);
90
+ try {
91
+ return await this.connector.subscribe([
92
+ ...required,
93
+ "browsingContext.navigationAborted",
94
+ ]);
95
+ }
96
+ catch (err) {
97
+ if (!(err instanceof BiDiError) || err.code !== "invalid argument")
98
+ throw err;
99
+ unsupportedEvents(this.connector).add("browsingContext.navigationAborted");
100
+ return this.connector.subscribe([...required]);
101
+ }
102
+ }
103
+ /**
104
+ * The first of: the state is reached, the navigation `last` fails, the navigation `last` is aborted. Registered
105
+ * before the state is read, so an event in between is not lost.
106
+ */
107
+ eventsOf(reachedEvent, last, listen) {
108
+ const connector = this.connector;
109
+ return Promise.race([
110
+ connector.waitForEvent(reachedEvent, (p) => p.context === this.contextId, listen),
111
+ connector
112
+ .waitForEvent("browsingContext.navigationFailed", (p) => p.context === this.contextId &&
113
+ (last === undefined || p.navigation === last), listen)
114
+ .then((p) => navigationFailure(p.url, "navigation failed")),
115
+ ...(this.canBeAborted()
116
+ ? [
117
+ connector
118
+ .waitForEvent("browsingContext.navigationAborted", (p) => p.context === this.contextId &&
119
+ last !== undefined &&
120
+ p.navigation === last, listen)
121
+ .then((p) => navigationFailure(p.url, "navigation aborted")),
122
+ ]
123
+ : []),
124
+ ]);
125
+ }
126
+ /** Whether the document is at `target` already: a navigation we saw fall short beats the document that is showing */
127
+ async readyStateReached(target, last) {
128
+ const known = last === undefined ? undefined : this.navigationProgress.get(last);
129
+ if (known && !(target === "load" ? known.load : known.domContentLoaded))
130
+ return false;
131
+ const readyState = await callFunction(this.connector, this.contextId, "() => document.readyState", [], { awaitPromise: false });
132
+ return target === "load"
133
+ ? readyState === "complete"
134
+ : readyState === "interactive" || readyState === "complete";
135
+ }
136
+ /**
137
+ * Resolves when the document reached `target`; rejects with a NavigationError when the navigation `last` (the
138
+ * latest one navigateTo started) fails or is aborted, and with a WaitTimeoutError after `timeout` ms.
139
+ */
140
+ async waitForState(target, timeout, last) {
141
+ const reachedEvent = target === "load"
142
+ ? "browsingContext.load"
143
+ : "browsingContext.domContentLoaded";
144
+ const subscription = await this.subscribe(reachedEvent);
145
+ const abort = new AbortController();
146
+ try {
147
+ const events = this.eventsOf(reachedEvent, last, {
148
+ timeout,
149
+ signal: abort.signal,
150
+ });
151
+ events.catch(() => { }); // settled below, or aborted in finally
152
+ if (!(await this.readyStateReached(target, last)))
153
+ await events;
154
+ }
155
+ finally {
156
+ abort.abort();
157
+ await subscription.unsubscribe();
158
+ }
159
+ }
160
+ }
@@ -21,7 +21,6 @@ export declare class ChromeDownloadFolders {
21
21
  private stagingRoot;
22
22
  /** Points the browser at a new, empty folder for the download after this one */
23
23
  private rotate;
24
- private removeIfEmpty;
25
24
  private onBegin;
26
25
  private onEnd;
27
26
  }
@@ -1,6 +1,7 @@
1
- import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, } from "node:fs";
1
+ import { existsSync, mkdirSync, renameSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import logger from "../logger/index.js";
4
+ import { removeIfEmpty } from "./downloads-dir.js";
4
5
  /** `report.csv` becomes `report(1).csv`, `report(2).csv`, … (the names Firefox gives) until a name is free in `dir` */
5
6
  export function uniqueName(dir, name) {
6
7
  if (!existsSync(path.join(dir, name)))
@@ -24,7 +25,7 @@ export class ChromeDownloadFolders {
24
25
  this.dir = dir;
25
26
  this.counter = 0;
26
27
  this.disposed = false;
27
- this.onBegin = (_params) => {
28
+ this.onBegin = () => {
28
29
  if (this.disposed)
29
30
  return;
30
31
  this.rotate().catch((err) => logger.error("Could not set a new download folder", { err }));
@@ -37,7 +38,7 @@ export class ChromeDownloadFolders {
37
38
  const to = path.join(this.dir, uniqueName(this.dir, path.basename(from)));
38
39
  renameSync(from, to);
39
40
  params.filepath = to;
40
- this.removeIfEmpty(path.dirname(from));
41
+ removeIfEmpty(path.dirname(from));
41
42
  }
42
43
  catch (err) {
43
44
  logger.error("Could not move a finished download", { err });
@@ -68,8 +69,8 @@ export class ChromeDownloadFolders {
68
69
  this.connector.offEvent("browsingContext.downloadEnd", this.onEnd);
69
70
  await this.subscription?.unsubscribe().catch(() => undefined);
70
71
  if (this.staging)
71
- this.removeIfEmpty(this.staging);
72
- this.removeIfEmpty(this.stagingRoot());
72
+ removeIfEmpty(this.staging);
73
+ removeIfEmpty(this.stagingRoot());
73
74
  }
74
75
  stagingRoot() {
75
76
  return path.join(this.dir, ".staging");
@@ -83,15 +84,6 @@ export class ChromeDownloadFolders {
83
84
  downloadBehavior: { type: "allowed", destinationFolder: this.staging },
84
85
  });
85
86
  if (previous)
86
- this.removeIfEmpty(previous);
87
- }
88
- removeIfEmpty(dir) {
89
- try {
90
- if (readdirSync(dir).length === 0)
91
- rmSync(dir, { recursive: true });
92
- }
93
- catch {
94
- // gone already, or not empty: leave it
95
- }
87
+ removeIfEmpty(previous);
96
88
  }
97
89
  }
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from "node:events";
2
- import logger from "../logger/index.js";
2
+ import { emitSafely } from "./emit.js";
3
3
  import { WaitTimeoutError } from "../wait/wait-until.js";
4
4
  /** A download did not complete */
5
5
  export class DownloadError extends Error {
@@ -80,21 +80,16 @@ export class DownloadTracker {
80
80
  return;
81
81
  const download = new Download(params.url, params.suggestedFilename, this.timeout);
82
82
  this.active.set(params.navigation, download);
83
- for (const listener of this.events.rawListeners("download")) {
84
- try {
85
- listener(download);
86
- }
87
- catch (err) {
88
- logger.error("A download listener threw", { err });
89
- }
90
- }
83
+ emitSafely(this.events, "download", [download]);
91
84
  };
92
85
  this.onEnd = (params) => {
93
86
  const download = this.active.get(params.navigation);
94
87
  if (!download)
95
88
  return;
96
89
  this.active.delete(params.navigation);
97
- download.finish(params.status === "complete" ? { status: "complete", path: params.filepath } : { status: "canceled" });
90
+ download.finish(params.status === "complete"
91
+ ? { status: "complete", path: params.filepath }
92
+ : { status: "canceled" });
98
93
  };
99
94
  }
100
95
  static async start(connector, tree, contextId, options) {
@@ -1,7 +1,7 @@
1
1
  import { mkdirSync, rmdirSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import logger from "../logger/index.js";
4
- import { BiDiError } from "../transport/bidi-error.js";
4
+ import { isUnsupported } from "../transport/unsupported.js";
5
5
  let counter = 0;
6
6
  /**
7
7
  * Makes downloads land in a folder of their own, so a test knows where to look and runs do not mix: a new folder
@@ -17,7 +17,7 @@ export async function prepareDownloadsDir(connector, baseDir) {
17
17
  }
18
18
  catch (err) {
19
19
  removeIfEmpty(dir);
20
- if (err instanceof BiDiError && (err.code === "unknown command" || err.code === "unsupported operation")) {
20
+ if (isUnsupported(err)) {
21
21
  logger.warn("The browser cannot be told where to save downloads; they go to its own folder");
22
22
  return undefined;
23
23
  }
@@ -0,0 +1,7 @@
1
+ import type EventEmitter from "node:events";
2
+ /**
3
+ * Calls the listeners of `event` in order with `args`. A listener that throws is logged, not rethrown: it must not
4
+ * stop the other listeners or the code that raised the event. `rawListeners` so the wrappers that make a `once`
5
+ * listener remove itself are called.
6
+ */
7
+ export declare function emitSafely(emitter: EventEmitter, event: string, args: unknown[], label?: string): void;
@@ -0,0 +1,16 @@
1
+ import logger from "../logger/index.js";
2
+ /**
3
+ * Calls the listeners of `event` in order with `args`. A listener that throws is logged, not rethrown: it must not
4
+ * stop the other listeners or the code that raised the event. `rawListeners` so the wrappers that make a `once`
5
+ * listener remove itself are called.
6
+ */
7
+ export function emitSafely(emitter, event, args, label = event) {
8
+ for (const listener of emitter.rawListeners(event)) {
9
+ try {
10
+ listener(...args);
11
+ }
12
+ catch (err) {
13
+ logger.error("A %s listener threw", label, { err });
14
+ }
15
+ }
16
+ }
@@ -1,4 +1,4 @@
1
- import { UnsupportedOperationError } from "../locator/selector-errors.js";
1
+ import { UnsupportedOperationError } from "../transport/unsupported.js";
2
2
  import type { BiDiConnector } from "../transport/bidi-connection.js";
3
3
  declare const ORIENTATIONS: readonly ["portrait-primary", "portrait-secondary", "landscape-primary", "landscape-secondary"];
4
4
  type OrientationType = (typeof ORIENTATIONS)[number];
@@ -1,5 +1,4 @@
1
- import { UnsupportedOperationError } from "../locator/selector-errors.js";
2
- import { BiDiError } from "../transport/bidi-error.js";
1
+ import { isUnsupported, UnsupportedOperationError, } from "../transport/unsupported.js";
3
2
  const ORIENTATIONS = [
4
3
  "portrait-primary",
5
4
  "portrait-secondary",
@@ -20,7 +19,7 @@ export class EmulationError extends Error {
20
19
  /** The browser does not implement an emulation command */
21
20
  export class EmulationUnsupportedError extends UnsupportedOperationError {
22
21
  constructor(option, applied) {
23
- super(`emulate({ ${option} }) is not supported by the browser`);
22
+ super(`emulate(): ${option} is not supported by the browser`);
24
23
  this.name = "EmulationUnsupportedError";
25
24
  this.option = option;
26
25
  this.applied = applied;
@@ -34,14 +33,19 @@ function plan(options) {
34
33
  if (options.locale !== undefined) {
35
34
  if (options.locale !== null) {
36
35
  try {
37
- if (options.locale === "" || Intl.getCanonicalLocales(options.locale).length !== 1)
36
+ if (options.locale === "" ||
37
+ Intl.getCanonicalLocales(options.locale).length !== 1)
38
38
  throw new RangeError();
39
39
  }
40
40
  catch {
41
41
  throw new RangeError(`emulate(): invalid locale "${options.locale}"`);
42
42
  }
43
43
  }
44
- steps.push({ option: "locale", method: "emulation.setLocaleOverride", params: { locale: options.locale } });
44
+ steps.push({
45
+ option: "locale",
46
+ method: "emulation.setLocaleOverride",
47
+ params: { locale: options.locale },
48
+ });
45
49
  }
46
50
  if (options.timezone !== undefined) {
47
51
  if (options.timezone !== null) {
@@ -52,23 +56,36 @@ function plan(options) {
52
56
  throw new RangeError(`emulate(): unknown timezone "${options.timezone}"`);
53
57
  }
54
58
  }
55
- steps.push({ option: "timezone", method: "emulation.setTimezoneOverride", params: { timezone: options.timezone } });
59
+ steps.push({
60
+ option: "timezone",
61
+ method: "emulation.setTimezoneOverride",
62
+ params: { timezone: options.timezone },
63
+ });
56
64
  }
57
65
  if (options.userAgent !== undefined) {
58
66
  if (options.userAgent === "")
59
67
  throw new RangeError("emulate(): userAgent must not be empty");
60
- steps.push({ option: "userAgent", method: "emulation.setUserAgentOverride", params: { userAgent: options.userAgent } });
68
+ steps.push({
69
+ option: "userAgent",
70
+ method: "emulation.setUserAgentOverride",
71
+ params: { userAgent: options.userAgent },
72
+ });
61
73
  }
62
74
  if (options.geolocation !== undefined) {
63
75
  const place = options.geolocation;
64
76
  if (place !== null) {
65
- if (!isNumber(place.latitude) || place.latitude < -90 || place.latitude > 90) {
77
+ if (!isNumber(place.latitude) ||
78
+ place.latitude < -90 ||
79
+ place.latitude > 90) {
66
80
  throw new RangeError("emulate(): latitude must be between -90 and 90");
67
81
  }
68
- if (!isNumber(place.longitude) || place.longitude < -180 || place.longitude > 180) {
82
+ if (!isNumber(place.longitude) ||
83
+ place.longitude < -180 ||
84
+ place.longitude > 180) {
69
85
  throw new RangeError("emulate(): longitude must be between -180 and 180");
70
86
  }
71
- if (place.accuracy !== undefined && (!isNumber(place.accuracy) || place.accuracy < 0)) {
87
+ if (place.accuracy !== undefined &&
88
+ (!isNumber(place.accuracy) || place.accuracy < 0)) {
72
89
  throw new RangeError("emulate(): accuracy must be 0 or more");
73
90
  }
74
91
  }
@@ -82,33 +99,52 @@ function plan(options) {
82
99
  steps.push({
83
100
  option: "offline",
84
101
  method: "emulation.setNetworkConditions",
85
- params: { networkConditions: options.offline ? { type: "offline" } : null },
102
+ params: {
103
+ networkConditions: options.offline ? { type: "offline" } : null,
104
+ },
86
105
  });
87
106
  }
88
107
  if (options.orientation !== undefined) {
89
108
  let screenOrientation = null;
90
109
  if (options.orientation !== null) {
91
- const type = (options.orientation === "portrait" || options.orientation === "landscape"
110
+ const type = (options.orientation === "portrait" ||
111
+ options.orientation === "landscape"
92
112
  ? `${options.orientation}-primary`
93
113
  : options.orientation);
94
114
  if (!ORIENTATIONS.includes(type))
95
115
  throw new RangeError(`emulate(): unknown orientation "${options.orientation}"`);
96
- screenOrientation = { natural: type.startsWith("portrait") ? "portrait" : "landscape", type };
116
+ screenOrientation = {
117
+ natural: type.startsWith("portrait") ? "portrait" : "landscape",
118
+ type,
119
+ };
97
120
  }
98
- steps.push({ option: "orientation", method: "emulation.setScreenOrientationOverride", params: { screenOrientation } });
121
+ steps.push({
122
+ option: "orientation",
123
+ method: "emulation.setScreenOrientationOverride",
124
+ params: { screenOrientation },
125
+ });
99
126
  }
100
127
  if (options.screen !== undefined) {
101
128
  const area = options.screen;
102
- if (area !== null && !(isPositiveInteger(area.width) && isPositiveInteger(area.height))) {
129
+ if (area !== null &&
130
+ !(isPositiveInteger(area.width) && isPositiveInteger(area.height))) {
103
131
  throw new RangeError("emulate(): screen width and height must be positive integers");
104
132
  }
105
- steps.push({ option: "screen", method: "emulation.setScreenSettingsOverride", params: { screenArea: area } });
133
+ steps.push({
134
+ option: "screen",
135
+ method: "emulation.setScreenSettingsOverride",
136
+ params: { screenArea: area },
137
+ });
106
138
  }
107
139
  if (options.touch !== undefined) {
108
140
  if (options.touch !== null && !isPositiveInteger(options.touch)) {
109
141
  throw new RangeError("emulate(): touch must be a positive integer");
110
142
  }
111
- steps.push({ option: "touch", method: "emulation.setTouchOverride", params: { maxTouchPoints: options.touch } });
143
+ steps.push({
144
+ option: "touch",
145
+ method: "emulation.setTouchOverride",
146
+ params: { maxTouchPoints: options.touch },
147
+ });
112
148
  }
113
149
  if (options.javaScriptEnabled !== undefined) {
114
150
  steps.push({
@@ -134,7 +170,7 @@ export async function applyEmulation(connector, target, options) {
134
170
  await connector.send(step.method, { ...step.params, ...target });
135
171
  }
136
172
  catch (err) {
137
- if (err instanceof BiDiError && (err.code === "unknown command" || err.code === "unsupported operation")) {
173
+ if (isUnsupported(err)) {
138
174
  throw new EmulationUnsupportedError(step.option, applied);
139
175
  }
140
176
  throw new EmulationError(step.option, applied, err);
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from "node:events";
2
- import logger from "../logger/index.js";
2
+ import { emitSafely } from "./emit.js";
3
3
  import { resolveFileInputs } from "./generated-files.js";
4
4
  /**
5
5
  * A native file picker the page asked for (a click on an `<input type=file>` or its label). Nothing appears on screen:
@@ -22,10 +22,10 @@ export class FileChooser {
22
22
  const sharedId = this.info.element
23
23
  ?.sharedId;
24
24
  if (!sharedId)
25
- throw new Error("file chooser has no element reference");
25
+ throw new Error("setFiles(): the file chooser has no element reference");
26
26
  const paths = await resolveFileInputs(files, "setFiles");
27
27
  if (paths.length > 1 && !this.info.multiple) {
28
- throw new Error("file chooser does not accept multiple files");
28
+ throw new Error("setFiles(): the file chooser does not accept multiple files");
29
29
  }
30
30
  await this.connector.send("input.setFiles", {
31
31
  context: this.info.context,
@@ -46,14 +46,7 @@ export class FileChooserTracker {
46
46
  if (!this.tree.isWithin(params.context, this.contextId))
47
47
  return;
48
48
  const chooser = new FileChooser(this.connector, params);
49
- for (const listener of this.events.rawListeners("filechooser")) {
50
- try {
51
- listener(chooser);
52
- }
53
- catch (err) {
54
- logger.error("A filechooser listener threw", { err });
55
- }
56
- }
49
+ emitSafely(this.events, "filechooser", [chooser]);
57
50
  };
58
51
  }
59
52
  static async start(connector, tree, contextId) {
@@ -0,0 +1,6 @@
1
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
2
+ /**
3
+ * Goes one entry back (-1) or forward (1) in the context's history and resolves once the entry is shown. False when
4
+ * there is no such entry.
5
+ */
6
+ export declare function traverseHistory(connector: BiDiConnector, context: string, delta: -1 | 1): Promise<boolean>;
@@ -0,0 +1,89 @@
1
+ import logger from "../logger/index.js";
2
+ import { callFunction } from "../script/call-function.js";
3
+ import { BiDiError } from "../transport/bidi-error.js";
4
+ /** Time (ms) a history traversal may go without a sign that the entry is shown before it is taken as done */
5
+ const HISTORY_SETTLE_TIMEOUT = 1000;
6
+ /**
7
+ * Goes one entry back (-1) or forward (1) in the context's history and resolves once the entry is shown. False when
8
+ * there is no such entry.
9
+ */
10
+ export async function traverseHistory(connector, context, delta) {
11
+ if (delta === -1 && (await atFirstEntry(connector, context)))
12
+ return false;
13
+ const settled = await watchHistoryTraversal(connector, context);
14
+ try {
15
+ await connector.send("browsingContext.traverseHistory", {
16
+ context,
17
+ delta,
18
+ });
19
+ // Firefox answers once the entry is shown; Chrome answers before, and a script call in between can hit the
20
+ // document that is going away
21
+ await settled.done;
22
+ return true;
23
+ }
24
+ catch (err) {
25
+ if (err instanceof BiDiError && err.code === "no such history entry")
26
+ return false;
27
+ throw err;
28
+ }
29
+ finally {
30
+ await settled.stop();
31
+ }
32
+ }
33
+ /**
34
+ * Whether going back would only reach the blank page a tab starts with. Firefox does not keep that page in the
35
+ * history, Chrome does; the Navigation API (Chrome only) lets Chrome say the same as Firefox.
36
+ */
37
+ async function atFirstEntry(connector, context) {
38
+ try {
39
+ return await callFunction(connector, context, `() => {
40
+ const nav = globalThis.navigation;
41
+ if (!nav) return false;
42
+ const entries = nav.entries();
43
+ const index = nav.currentEntry.index;
44
+ return index === 0 || (index === 1 && entries[0].url === "about:blank");
45
+ }`, [], { awaitPromise: false });
46
+ }
47
+ catch (err) {
48
+ logger.debug("Could not read the history position", { err });
49
+ return false;
50
+ }
51
+ }
52
+ /**
53
+ * Starts listening for the end of a history traversal (the new entry committed, or the history or fragment changed
54
+ * within the document). `done` resolves at the first such event, or after a second without one (a page restored from
55
+ * the back-forward cache may say nothing); it never rejects.
56
+ */
57
+ async function watchHistoryTraversal(connector, context) {
58
+ const events = [
59
+ "browsingContext.navigationCommitted",
60
+ "browsingContext.historyUpdated",
61
+ "browsingContext.fragmentNavigated",
62
+ ];
63
+ let subscription;
64
+ try {
65
+ subscription = await connector.subscribe([...events]);
66
+ }
67
+ catch (err) {
68
+ logger.debug("Could not watch a history traversal", { err });
69
+ return { done: Promise.resolve(), stop: async () => { } };
70
+ }
71
+ let finish;
72
+ const done = new Promise((resolve) => (finish = resolve));
73
+ const timer = setTimeout(finish, HISTORY_SETTLE_TIMEOUT);
74
+ const listener = (info) => {
75
+ if (info.context === context)
76
+ finish();
77
+ };
78
+ for (const event of events)
79
+ connector.onEvent(event, listener);
80
+ return {
81
+ done,
82
+ stop: async () => {
83
+ clearTimeout(timer);
84
+ for (const event of events)
85
+ connector.offEvent(event, listener);
86
+ await subscription?.unsubscribe().catch(() => undefined);
87
+ },
88
+ };
89
+ }
@@ -1,4 +1,6 @@
1
1
  export { Browser, type BrowserLaunchOptions, type ContextOptions, } from "./browser.js";
2
2
  export { BrowserContext } from "./browser-context.js";
3
3
  export { default as Page } from "./page.js";
4
+ export { DownloadTimeoutError } from "./download.js";
5
+ export { EmulationUnsupportedError } from "./emulation.js";
4
6
  export { findBrowser } from "./browser-finder.js";
@@ -1,4 +1,6 @@
1
1
  export { Browser, } from "./browser.js";
2
2
  export { BrowserContext } from "./browser-context.js";
3
3
  export { default as Page } from "./page.js";
4
+ export { DownloadTimeoutError } from "./download.js";
5
+ export { EmulationUnsupportedError } from "./emulation.js";
4
6
  export { findBrowser } from "./browser-finder.js";
@@ -0,0 +1,2 @@
1
+ /** Wraps an init script as one function declaration; the argument is embedded as JSON because preload scripts take no arguments */
2
+ export declare function initScriptSource(script: string | ((arg?: any) => void), arg: unknown): string;
@@ -0,0 +1,16 @@
1
+ import { toFunctionDeclaration } from "../script/call-function.js";
2
+ /** Wraps an init script as one function declaration; the argument is embedded as JSON because preload scripts take no arguments */
3
+ export function initScriptSource(script, arg) {
4
+ if (typeof script === "string") {
5
+ if (arg !== undefined) {
6
+ throw new TypeError("addInitScript(): a string script does not take an argument; pass a function instead");
7
+ }
8
+ return `() => {\n${script}\n}`;
9
+ }
10
+ const declaration = toFunctionDeclaration(script);
11
+ const argument = arg === undefined ? "undefined" : JSON.stringify(arg);
12
+ if (argument === undefined) {
13
+ throw new TypeError("addInitScript(): the argument must be JSON-serializable");
14
+ }
15
+ return `() => (${declaration})(${argument})`;
16
+ }
@@ -0,0 +1,20 @@
1
+ import { BiDiConnector } from "../transport/bidi-connection.js";
2
+ import type { BrowserProcess } from "./chrome-launch.js";
3
+ import { type BrowserLaunchOptions } from "./launch-flags.js";
4
+ export interface FirefoxLaunch {
5
+ executablePath: string;
6
+ /** Names the browser in the start timeout error */
7
+ label: string;
8
+ launchOptions: Partial<BrowserLaunchOptions>;
9
+ commandTimeout: number | undefined;
10
+ signal?: AbortSignal | undefined;
11
+ }
12
+ /**
13
+ * Starts Firefox, waits for it to announce its BiDi endpoint, connects and opens the BiDi session.
14
+ * Rejects with an Error when the process exits first or the handshake fails, with the abort reason when aborted,
15
+ * and with a message string when the debugging port is taken. A failed launch stops what it started.
16
+ */
17
+ export declare function launchFirefox(launch: FirefoxLaunch): Promise<{
18
+ proc: BrowserProcess;
19
+ connector: BiDiConnector;
20
+ }>;