@itmetsam/samurai-framework 0.10.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 (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,26 +1,19 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { mkdtemp, open, rm } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import path from "node:path";
5
2
  import logger from "../logger/index.js";
6
3
  import { addPreload, runInLoadedContexts } from "../script/preload.js";
7
- import { callFunction } from "../script/call-function.js";
8
4
  import { ChannelArg, fromRemoteValue } from "../script/serialize.js";
9
- import { ElementHandle } from "../script/element-handle.js";
10
- import { locatorFromSpec } from "../steps/build.js";
11
- import { addVariable, cleanFileName, filesFolder, RECORD_LIMIT, setSecret, storeFile, } from "../project/index.js";
12
- import { humanSize } from "../steps/files.js";
13
5
  import { CAPTURE_MUTE, CAPTURE_OFF, CAPTURE_SOURCE, CAPTURE_UNMUTE, } from "./capture.js";
14
6
  import { FILES_SANDBOX, filesCaptureSource } from "./files-capture.js";
15
- import { disposeFilesCapture, readPageFile, releasePageFile, } from "./files-reader.js";
16
- import { Normaliser, } from "./normalise.js";
7
+ import { disposeFilesCapture, releasePageFile } from "./files-reader.js";
8
+ import { FilesRecorder, missingFileText } from "./files-recorder.js";
9
+ import { Normaliser } from "./normalise.js";
10
+ import { observationFor, } from "./page-messages.js";
11
+ import { listSandboxRealms } from "./realms.js";
17
12
  import { rankLocator } from "./ranking.js";
18
- import { ToolbarController, } from "./toolbar/controller.js";
19
- import { inspectElement } from "./toolbar/inspect.js";
20
- import { TOOLBAR_KEY, TOOLBAR_SANDBOX, } from "./toolbar/protocol.js";
13
+ import { TOOLBAR_HOOK, ToolbarBridge } from "./toolbar-bridge.js";
14
+ import { TOOLBAR_SANDBOX } from "./toolbar/protocol.js";
21
15
  import { TOOLBAR_SOURCE } from "./toolbar/source.js";
22
- /** The toolbar's hook in its sandbox */
23
- const HOOK = `globalThis[Symbol.for(${JSON.stringify(TOOLBAR_KEY)})]`;
16
+ export { missingFileText };
24
17
  /**
25
18
  * Watches the tester use the page and reports steps as they happen. A preload script reports clicks,
26
19
  * typing and Alt+click assertions over a `script.message` channel; each element is given locators that
@@ -47,13 +40,6 @@ export class Recorder {
47
40
  /** The files capture script's channel, handed only to its sandbox */
48
41
  this.filesChannel = `samurai-files-${randomUUID()}`;
49
42
  this.filesRealms = new Set();
50
- this.stored = new Set();
51
- this.missing = [];
52
- /** The context's `samurai-toolbar` sandbox realms, as last listed: the only realms toolbar messages are taken from */
53
- this.toolbarRealms = new Set();
54
- /** Toolbar messages waiting for a realm lookup, and those that arrived behind them (kept in order) */
55
- this.toolbarBacklog = Promise.resolve();
56
- this.toolbarWaiting = 0;
57
43
  /**
58
44
  * Page messages come only on the capture channel; toolbar messages only on the toolbar channel and
59
45
  * only from the toolbar's sandbox realm. Neither channel is reachable from page script (each stays in
@@ -86,28 +72,9 @@ export class Recorder {
86
72
  });
87
73
  return;
88
74
  }
89
- if (params.channel !== this.toolbarChannel || !this.controller)
75
+ if (params.channel !== this.toolbarChannel || !this.toolbar)
90
76
  return;
91
- const realm = params.source.realm ?? "";
92
- if (this.toolbarWaiting === 0 && this.toolbarRealms.has(realm)) {
93
- this.fromToolbar(params.data);
94
- return;
95
- }
96
- // A sandbox not seen yet (a new document's toolbar): look it up, and keep later messages behind it
97
- this.toolbarWaiting++;
98
- this.toolbarBacklog = this.toolbarBacklog
99
- .then(async () => {
100
- if (!this.toolbarRealms.has(realm))
101
- await this.listToolbarRealms();
102
- if (this.toolbarRealms.has(realm))
103
- this.fromToolbar(params.data);
104
- else
105
- logger.warn("Recorder ignored a toolbar message from outside the toolbar's sandbox");
106
- })
107
- .catch((err) => {
108
- logger.debug("Could not check a toolbar message's realm", { err });
109
- })
110
- .finally(() => this.toolbarWaiting--);
77
+ this.toolbar.receive(params.source.realm ?? "", params.data);
111
78
  };
112
79
  this.onLoad = (params) => {
113
80
  if (params.context === this.context && params.url !== "about:blank")
@@ -126,13 +93,17 @@ export class Recorder {
126
93
  });
127
94
  };
128
95
  }
96
+ /** The toolbar's controller, when the recording has a toolbar */
97
+ get controller() {
98
+ return this.toolbar?.controller;
99
+ }
129
100
  /** Files stored or reused in the files folder by this recording, project-relative */
130
101
  get storedFiles() {
131
- return [...this.stored];
102
+ return this.files?.storedFiles ?? [];
132
103
  }
133
104
  /** Files whose steps point at a path that wasn't written */
134
105
  get missingFiles() {
135
- return [...this.missing];
106
+ return this.files?.missingFiles ?? [];
136
107
  }
137
108
  static async start(target, options) {
138
109
  const at = options.at ?? 0;
@@ -147,42 +118,13 @@ export class Recorder {
147
118
  return recorder;
148
119
  }
149
120
  async listFilesRealms() {
150
- const { realms } = await this.connector.send("script.getRealms", {
151
- context: this.context,
152
- type: "window",
153
- });
154
- this.filesRealms = new Set(realms
155
- .filter((info) => info.type === "window" && info.sandbox === FILES_SANDBOX)
156
- .map((info) => info.realm));
157
- }
158
- async listToolbarRealms() {
159
- const { realms } = await this.connector.send("script.getRealms", {
160
- context: this.context,
161
- type: "window",
162
- });
163
- this.toolbarRealms = new Set(realms
164
- .filter((info) => info.type === "window" && info.sandbox === TOOLBAR_SANDBOX)
165
- .map((info) => info.realm));
166
- }
167
- /** A message from the toolbar's own sandbox */
168
- fromToolbar(data) {
169
- const message = fromRemoteValue(data);
170
- const controller = this.controller;
171
- if (!controller || message.type !== "toolbar" || !("action" in message))
172
- return;
173
- if (message.action === "ready") {
174
- // Not behind a slow insert: the ready watchdog is waiting for it
175
- void controller.handle(message);
176
- }
177
- else if (controller.accept(message)) {
178
- // Decided as it arrives: a double click's second copy would only run after the first had finished
179
- this.enqueue(async () => controller.handle(message));
180
- }
121
+ this.filesRealms = await listSandboxRealms(this.connector, this.context, FILES_SANDBOX);
181
122
  }
182
123
  async begin(initialGoto) {
183
124
  const toolbar = this.options.toolbar;
184
125
  if (toolbar)
185
- this.controller = this.createController(toolbar);
126
+ this.toolbar = this.createToolbar(toolbar);
127
+ this.files = this.createFiles();
186
128
  this.connector.onEvent("script.message", this.onMessage);
187
129
  this.connector.onEvent("browsingContext.navigationStarted", this.onNavigation);
188
130
  if (toolbar)
@@ -215,7 +157,7 @@ export class Recorder {
215
157
  onRunError: "log",
216
158
  });
217
159
  }
218
- if (this.controller) {
160
+ if (this.toolbar) {
219
161
  this.toolbarPreload = await addPreload(this.connector, {
220
162
  source: TOOLBAR_SOURCE,
221
163
  sandbox: TOOLBAR_SANDBOX,
@@ -224,7 +166,7 @@ export class Recorder {
224
166
  onRunError: "log",
225
167
  });
226
168
  // The document that is loaded now gets its state and its ready watchdog as a new one would
227
- this.controller.loaded();
169
+ this.toolbar.controller.loaded();
228
170
  }
229
171
  }
230
172
  catch (err) {
@@ -233,43 +175,26 @@ export class Recorder {
233
175
  throw err;
234
176
  }
235
177
  }
236
- createController(toolbar) {
237
- const { projectDir, environment, onStatus, onStop, onSecretAdded } = toolbar;
238
- return new ToolbarController({
239
- push: (state) => this.callToolbar("update", [state]),
240
- reply: (id, result) => this.callToolbar("reply", [id, result]),
241
- currentUrl: () => this.page.url(),
242
- rank: (target, candidates) => rankLocator(this.page, target, candidates),
243
- inspect: (target, locator, candidates) => inspectElement(this.page, this.connector, this.context, target, locator, candidates),
244
- type: async (locator, value) => {
245
- await this.mute();
246
- try {
247
- await locatorFromSpec(this.page, locator).fill(value, {
248
- timeout: 5000,
249
- });
250
- }
251
- finally {
252
- await this.unmute();
253
- }
254
- },
178
+ createToolbar(toolbar) {
179
+ return new ToolbarBridge({
180
+ page: this.page,
181
+ connector: this.connector,
182
+ context: this.context,
255
183
  mute: () => this.mute(),
256
184
  unmute: () => this.unmute(),
257
- addVariable: (name, value) => addVariable(projectDir, environment, name, value),
258
- setSecret: (name, value) => setSecret(projectDir, environment, name, value),
259
- secretAdded: (name) => onSecretAdded?.(name),
260
185
  observe: (observation) => this.emit(this.normaliser.observe(observation, Date.now())),
261
- stop: () => {
262
- if (onStop)
263
- onStop();
264
- else
265
- void this.stop();
266
- },
267
- onStatus: (status) => onStatus?.(status),
186
+ stop: () => void this.stop(),
187
+ enqueue: (work) => this.enqueue(work),
268
188
  }, toolbar);
269
189
  }
270
- /** Calls the toolbar hook's `method` in the loaded document; a document between loads has no toolbar yet */
271
- callToolbar(method, args) {
272
- callFunction(this.connector, this.context, `(...args) => { ${HOOK}?.${method}(...args); }`, args, { sandbox: TOOLBAR_SANDBOX, awaitPromise: false }).catch((err) => logger.debug("Could not reach the recorder toolbar", { err }));
190
+ createFiles() {
191
+ const recording = this.options.files;
192
+ if (!recording)
193
+ return undefined;
194
+ return new FilesRecorder(this.connector, this.context, recording, {
195
+ warn: (text) => this.controller?.warn(text),
196
+ saving: (name) => this.controller?.saving(name),
197
+ });
273
198
  }
274
199
  enqueue(work) {
275
200
  this.queue.tail = this.queue.tail.then(work).catch((err) => {
@@ -304,29 +229,7 @@ export class Recorder {
304
229
  logger.warn("Recorder found no locator that matches only the element the tester used; the %s was skipped", message.type);
305
230
  return;
306
231
  }
307
- const key = message.target.sharedId;
308
- const observation = message.type === "click"
309
- ? { type: "click", key, locator, textEntry: message.textEntry === true }
310
- : message.type === "press"
311
- ? { type: "press", key, locator, pressed: message.pressed ?? "" }
312
- : message.type === "assert"
313
- ? {
314
- type: "assert",
315
- key,
316
- locator,
317
- text: message.text ?? "",
318
- ...(message.value !== undefined && { value: message.value }),
319
- }
320
- : {
321
- type: "input",
322
- key,
323
- locator,
324
- ...(message.value !== undefined && { value: message.value }),
325
- ...(message.secret && { secret: true }),
326
- ...(message.secretName !== undefined && {
327
- secretName: message.secretName,
328
- }),
329
- };
232
+ const observation = observationFor(message, locator);
330
233
  this.emit(this.normaliser.observe(observation, Date.now()));
331
234
  }
332
235
  async fromFiles(message) {
@@ -352,7 +255,7 @@ export class Recorder {
352
255
  const files = [];
353
256
  for (const file of message.files) {
354
257
  started++;
355
- files.push({ path: await this.storePageFile(file) });
258
+ files.push({ path: await this.files.store(file) });
356
259
  }
357
260
  this.emit(this.normaliser.observe({ type: "files", via: message.via, locator, files }, now));
358
261
  }
@@ -360,62 +263,6 @@ export class Recorder {
360
263
  await release(started);
361
264
  }
362
265
  }
363
- /** Saves one kept file to the files folder; returns the path its step gets */
364
- async storePageFile(file) {
365
- const recording = this.options.files;
366
- const folder = filesFolder(recording.projectDir, recording.filesDir);
367
- const notSaved = (reason) => {
368
- const missing = {
369
- path: folder.relative
370
- ? `${folder.relative}/${cleanFileName(file.name)}`
371
- : cleanFileName(file.name),
372
- name: file.name,
373
- size: file.size,
374
- reason,
375
- };
376
- this.missing.push(missing);
377
- recording.onMissing?.(missing);
378
- this.controller?.warn(missingFileText(missing, folder.relative));
379
- return missing.path;
380
- };
381
- if (file.size > RECORD_LIMIT) {
382
- await releasePageFile(this.connector, this.context, file.id).catch(() => undefined);
383
- return notSaved("too-big");
384
- }
385
- this.controller?.saving(file.name);
386
- let temp;
387
- try {
388
- temp = await mkdtemp(path.join(tmpdir(), "samurai-upload-"));
389
- const content = path.join(temp, "content");
390
- const out = await open(content, "w");
391
- try {
392
- await readPageFile(this.connector, this.context, file.id, file.size, (bytes) => out.write(bytes));
393
- }
394
- finally {
395
- await out.close();
396
- }
397
- const stored = await storeFile({
398
- projectDir: recording.projectDir,
399
- ...(recording.filesDir !== undefined && {
400
- filesDir: recording.filesDir,
401
- }),
402
- name: file.name,
403
- content,
404
- });
405
- this.stored.add(stored.path);
406
- return stored.path;
407
- }
408
- catch (err) {
409
- logger.warn("Recorder could not save %s", file.name, { err });
410
- return notSaved("unreadable");
411
- }
412
- finally {
413
- this.controller?.saving(undefined);
414
- if (temp)
415
- await rm(temp, { recursive: true, force: true }).catch(() => undefined);
416
- await releasePageFile(this.connector, this.context, file.id).catch(() => undefined);
417
- }
418
- }
419
266
  async release() {
420
267
  this.connector.offEvent("script.message", this.onMessage);
421
268
  this.connector.offEvent("browsingContext.navigationStarted", this.onNavigation);
@@ -466,7 +313,7 @@ export class Recorder {
466
313
  if (this.toolbarPreload) {
467
314
  // The window may stay open: take the toolbar and its listeners off the page
468
315
  await runInLoadedContexts(this.connector, {
469
- source: `() => { ${HOOK}?.dispose(); }`,
316
+ source: `() => { ${TOOLBAR_HOOK}?.dispose(); }`,
470
317
  contexts: [this.context],
471
318
  sandbox: TOOLBAR_SANDBOX,
472
319
  onRunError: "log",
@@ -480,10 +327,3 @@ export class Recorder {
480
327
  await this.release();
481
328
  }
482
329
  }
483
- /** The warning for a file that isn't saved, as the toolbar and the CLI show it */
484
- export function missingFileText(file, folder) {
485
- const where = folder === "" ? "the project folder" : `${folder}/`;
486
- return file.reason === "too-big"
487
- ? `${file.name} is ${humanSize(file.size)}. Files over 50 MB aren't saved: add it to ${where} yourself, or use a generated file.`
488
- : `${file.name} couldn't be read, so it isn't saved: add it to ${where} yourself, or use a generated file.`;
489
- }
@@ -0,0 +1,22 @@
1
+ import { Browser } from "../browser/browser.js";
2
+ import type Page from "../browser/page.js";
3
+ import type { SupportedBrowser } from "../types/browser.js";
4
+ interface BrowserChoice {
5
+ browser?: SupportedBrowser | undefined;
6
+ port?: number | undefined;
7
+ signal?: AbortSignal | undefined;
8
+ }
9
+ /**
10
+ * The browser a recording uses: the one asked for, else the first of the project's (recording in two browsers at
11
+ * once makes no sense). A fresh build is started once first, so a missing browser or a slow first start stops
12
+ * the recording before the spec is touched.
13
+ */
14
+ export declare function prepareRecordingBrowser(options: BrowserChoice): Promise<SupportedBrowser>;
15
+ /** Opens the browser of `prepareRecordingBrowser` */
16
+ export declare function launchRecordingBrowser(name: SupportedBrowser, options: BrowserChoice & {
17
+ headless: boolean;
18
+ }): Promise<{
19
+ browser: Browser;
20
+ page: Page;
21
+ }>;
22
+ export {};
@@ -0,0 +1,21 @@
1
+ import { Browser } from "../browser/browser.js";
2
+ import { ensureWarm } from "../browser/warm-up.js";
3
+ import { readConfig } from "../config/config.js";
4
+ import { browsersFor } from "../runner/browsers.js";
5
+ import { DEFAULT_PORT } from "../runner/ports.js";
6
+ /**
7
+ * The browser a recording uses: the one asked for, else the first of the project's (recording in two browsers at
8
+ * once makes no sense). A fresh build is started once first, so a missing browser or a slow first start stops
9
+ * the recording before the spec is touched.
10
+ */
11
+ export async function prepareRecordingBrowser(options) {
12
+ const name = options.browser ??
13
+ browsersFor({ project: await readConfig("browsers") })[0];
14
+ await ensureWarm(name, { port: options.port ?? DEFAULT_PORT }, options.signal);
15
+ options.signal?.throwIfAborted();
16
+ return name;
17
+ }
18
+ /** Opens the browser of `prepareRecordingBrowser` */
19
+ export function launchRecordingBrowser(name, options) {
20
+ return Browser.launch(name, { port: options.port ?? DEFAULT_PORT, headless: options.headless }, options.signal);
21
+ }
@@ -0,0 +1,8 @@
1
+ import type { PreparedRun } from "../runner/prepare-run.js";
2
+ import type { SamuraiTestConfig } from "../types/config.js";
3
+ import type { RecorderEvent } from "./normalise.js";
4
+ import type { ControllerOptions } from "./toolbar/types.js";
5
+ /** What the toolbar shows of the project: its environment, variables, secrets and the test's screenshots */
6
+ export declare function toolbarOptions(run: Pick<PreparedRun, "settings" | "secrets">, source: string, dir: string, config: Pick<SamuraiTestConfig, "environments">, testIndex: number): Promise<ControllerOptions>;
7
+ /** The secrets a recording needs set: those its steps read, then those added from the panel, once each */
8
+ export declare function resultSecrets(events: readonly RecorderEvent[], added: Iterable<string>): string[];
@@ -0,0 +1,45 @@
1
+ import { isGitIgnored } from "../project/index.js";
2
+ import { parseSpec } from "../steps/parse.js";
3
+ /** Every `secrets.<NAME>` a parsed value contains, at any depth */
4
+ function secretNames(node, found) {
5
+ if (Array.isArray(node))
6
+ node.forEach((item) => secretNames(item, found));
7
+ else if (node !== null && typeof node === "object") {
8
+ const record = node;
9
+ if (record.kind === "secret" && typeof record.name === "string")
10
+ found.add(record.name);
11
+ Object.values(record).forEach((item) => secretNames(item, found));
12
+ }
13
+ }
14
+ /** What the toolbar shows of the project: its environment, variables, secrets and the test's screenshots */
15
+ export async function toolbarOptions(run, source, dir, config, testIndex) {
16
+ const tests = parseSpec(source);
17
+ const names = new Set(run.secrets.keys());
18
+ secretNames(tests, names);
19
+ const secrets = {};
20
+ for (const name of names)
21
+ secrets[name] = run.secrets.get(name);
22
+ const { environment } = run.settings;
23
+ return {
24
+ environment,
25
+ canAddVariables: typeof config.environments === "object",
26
+ variables: run.settings.variables,
27
+ secrets,
28
+ secretsIgnored: await isGitIgnored(dir, `.env.${environment}`),
29
+ screenshots: tests[testIndex]?.steps.filter(({ step }) => step.kind === "screenshot")
30
+ .length ?? 0,
31
+ };
32
+ }
33
+ /** The secrets a recording needs set: those its steps read, then those added from the panel, once each */
34
+ export function resultSecrets(events, added) {
35
+ return [
36
+ ...new Set([
37
+ ...events.flatMap((event) => event.op !== "remove" &&
38
+ event.step.kind === "fill" &&
39
+ event.step.value.kind === "secret"
40
+ ? [event.step.value.name]
41
+ : []),
42
+ ...added,
43
+ ]),
44
+ ];
45
+ }
@@ -1,12 +1,11 @@
1
- import { Browser } from "../browser/browser.js";
1
+ import type { Browser } from "../browser/browser.js";
2
2
  import type Page from "../browser/page.js";
3
- import type { SupportedBrowser } from "../types/browser.js";
4
- import type { PreparedRun } from "../runner/prepare-run.js";
5
3
  import { type ProjectOptions } from "../runner/project.js";
6
- import type { SamuraiTestConfig } from "../types/config.js";
7
- import type { ControllerOptions } from "./toolbar/controller.js";
4
+ import type { SupportedBrowser } from "../types/browser.js";
8
5
  import type { RecorderEvent } from "./normalise.js";
9
6
  import { type MissingFile } from "./recorder.js";
7
+ export { pickTest } from "./spec-source.js";
8
+ export { resultSecrets, toolbarOptions } from "./session-secrets.js";
10
9
  export interface RecordSpecOptions extends ProjectOptions {
11
10
  /** The spec file, absolute or relative to the project folder. Created when `create` is given and it doesn't exist */
12
11
  file: string;
@@ -23,7 +22,7 @@ export interface RecordSpecOptions extends ProjectOptions {
23
22
  url?: string;
24
23
  /** The browser to record in. @default the first of the config's `browsers`, else Firefox */
25
24
  browser?: SupportedBrowser;
26
- /** The browser's debugging port. @default 9223 */
25
+ /** The browser's debugging port. @default DEFAULT_PORT (9223) */
27
26
  port?: number;
28
27
  /** Run the browser without a window. A person can't use it then; for tests and machines without a display. @default the config's `use.headless`, else headless on CI and a window otherwise */
29
28
  headless?: boolean;
@@ -67,14 +66,6 @@ export interface RecordResult {
67
66
  /** The files folder was created by this recording */
68
67
  firstFiles: boolean;
69
68
  }
70
- /** What the toolbar shows of the project: its environment, variables, secrets and the test's screenshots */
71
- export declare function toolbarOptions(run: Pick<PreparedRun, "settings" | "secrets">, source: string, dir: string, config: Pick<SamuraiTestConfig, "environments">, testIndex: number): Promise<ControllerOptions>;
72
- /** The secrets a recording needs set: those its steps read, then those added from the panel, once each */
73
- export declare function resultSecrets(events: readonly RecorderEvent[], added: Iterable<string>): string[];
74
- /** The index of the test `selector` names (see `RecordSpecOptions.test`) */
75
- export declare function pickTest(tests: readonly {
76
- name: string;
77
- }[], selector: string | number | undefined): number;
78
69
  /**
79
70
  * Opens a browser window and records what the person does into a spec file, step by step, until `signal`
80
71
  * aborts or the window is closed. Every step is written to the file as it happens, as a minimal edit.