@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,24 +1,6 @@
1
- const TEXT_LIMIT = 100;
1
+ import { expectationFor, secretNameFor } from "./expectation.js";
2
2
  /** A picker that opens within this many ms of a click was opened by it */
3
- export const PICKER_WINDOW = 1000;
4
- /** The name a secret gets in `secrets.<NAME>`: the field's name or id, upper snake case */
5
- export function secretNameFor(hint) {
6
- const name = (hint ?? "")
7
- .replace(/([a-z\d])([A-Z])/g, "$1_$2")
8
- .replace(/[^A-Za-z0-9]+/g, "_")
9
- .replace(/^_+|_+$/g, "")
10
- .toUpperCase();
11
- return name === "" || /^\d/.test(name) ? "PASSWORD" : name;
12
- }
13
- export function expectationFor(text, value) {
14
- if (value !== undefined)
15
- return { matcher: "toHaveValue", expected: value };
16
- if (text === "")
17
- return { matcher: "toBeVisible" };
18
- return text.length <= TEXT_LIMIT
19
- ? { matcher: "toHaveText", expected: text }
20
- : { matcher: "toContainText", expected: text.slice(0, TEXT_LIMIT).trim() };
21
- }
3
+ const PICKER_WINDOW = 1000;
22
4
  /**
23
5
  * Turns what the tester did into steps:
24
6
  * - typing into a field becomes one `fill`, rewritten as the text grows (a password becomes a secret reference);
@@ -93,82 +75,105 @@ export class Normaliser {
93
75
  this.lastFill = undefined;
94
76
  return [this.insert(fromToolbar)];
95
77
  }
96
- // The sandbox reports a picker for any click on a file input, including ones a page script fires:
97
- // it is only a hint, and only the very last step, recorded just before, may be replaced or removed
98
- // (a page's own input.click() also counts as the opening click, bounded to the last step within PICKER_WINDOW)
99
- if (observation.type === "picker") {
100
- const click = this.lastClick;
101
- this.picker =
102
- click && click.index === this.next - 1 && at - click.at <= PICKER_WINDOW
103
- ? click.index
104
- : undefined;
105
- return [];
78
+ switch (observation.type) {
79
+ case "picker":
80
+ return this.pickerOpened(at);
81
+ case "picker-cancel":
82
+ return this.pickerCancelled();
83
+ case "files":
84
+ return this.filesSet(observation, at);
85
+ case "click":
86
+ return this.clicked(observation, at);
87
+ case "press":
88
+ return this.pressed(observation, at);
89
+ case "assert":
90
+ return this.asserted(observation);
91
+ case "input":
92
+ return this.typed(observation, at);
93
+ default:
94
+ return [];
106
95
  }
107
- if (observation.type === "picker-cancel") {
108
- const index = this.picker;
96
+ }
97
+ /**
98
+ * The sandbox reports a picker for any click on a file input, including ones a page script fires: it is only a
99
+ * hint, and only the very last step, recorded just before, may be replaced or removed (a page's own
100
+ * input.click() also counts as the opening click, bounded to the last step within PICKER_WINDOW)
101
+ */
102
+ pickerOpened(at) {
103
+ const click = this.lastClick;
104
+ this.picker =
105
+ click && click.index === this.next - 1 && at - click.at <= PICKER_WINDOW
106
+ ? click.index
107
+ : undefined;
108
+ return [];
109
+ }
110
+ /** The picker closed without a choice: the click that opened it is taken out again */
111
+ pickerCancelled() {
112
+ const index = this.picker;
113
+ this.picker = undefined;
114
+ if (index === undefined || index !== this.next - 1)
115
+ return [];
116
+ this.next--;
117
+ this.lastClick = undefined;
118
+ this.lastStep = undefined;
119
+ return [{ op: "remove", index }];
120
+ }
121
+ filesSet(observation, at) {
122
+ this.lastFill = undefined;
123
+ this.lastAction = at;
124
+ const step = {
125
+ kind: observation.via === "drop" ? "dropFiles" : "setInputFiles",
126
+ locator: observation.locator,
127
+ files: observation.files,
128
+ };
129
+ const index = this.picker;
130
+ if (observation.via === "input" &&
131
+ index !== undefined &&
132
+ index === this.next - 1) {
109
133
  this.picker = undefined;
110
- if (index === undefined || index !== this.next - 1)
111
- return [];
112
- this.next--;
113
134
  this.lastClick = undefined;
114
- this.lastStep = undefined;
115
- return [{ op: "remove", index }];
135
+ this.lastStep = step;
136
+ return [{ op: "replace", index, step }];
116
137
  }
117
- if (observation.type === "files") {
118
- this.lastFill = undefined;
119
- this.lastAction = at;
120
- const step = {
121
- kind: observation.via === "drop" ? "dropFiles" : "setInputFiles",
138
+ return [this.insert(step)];
139
+ }
140
+ clicked(observation, at) {
141
+ if (observation.textEntry)
142
+ return []; // focusing a field: the fill that follows needs no click
143
+ this.lastFill = undefined;
144
+ this.lastAction = at;
145
+ const events = [
146
+ this.insert({ kind: "click", locator: observation.locator }),
147
+ ];
148
+ this.lastClick = { index: this.next - 1, at };
149
+ return events;
150
+ }
151
+ pressed(observation, at) {
152
+ // The page may react by clearing the field: what is typed next is a new fill, not a rewrite of this one
153
+ this.lastFill = undefined;
154
+ this.lastAction = at;
155
+ return [
156
+ this.insert({
157
+ kind: "press",
122
158
  locator: observation.locator,
123
- files: observation.files,
124
- };
125
- const index = this.picker;
126
- if (observation.via === "input" &&
127
- index !== undefined &&
128
- index === this.next - 1) {
129
- this.picker = undefined;
130
- this.lastClick = undefined;
131
- this.lastStep = step;
132
- return [{ op: "replace", index, step }];
133
- }
134
- return [this.insert(step)];
135
- }
136
- if (observation.type === "click") {
137
- if (observation.textEntry)
138
- return []; // focusing a field: the fill that follows needs no click
139
- this.lastFill = undefined;
140
- this.lastAction = at;
141
- const events = [
142
- this.insert({ kind: "click", locator: observation.locator }),
143
- ];
144
- this.lastClick = { index: this.next - 1, at };
145
- return events;
146
- }
147
- if (observation.type === "press") {
148
- // The page may react by clearing the field: what is typed next is a new fill, not a rewrite of this one
149
- this.lastFill = undefined;
150
- this.lastAction = at;
151
- return [
152
- this.insert({
153
- kind: "press",
154
- locator: observation.locator,
155
- key: observation.pressed,
156
- }),
157
- ];
158
- }
159
- if (observation.type === "assert") {
160
- this.lastFill = undefined;
161
- return [
162
- this.insert({
163
- kind: "expect",
164
- locator: observation.locator,
165
- not: false,
166
- expectation: expectationFor(observation.text, observation.value),
167
- }),
168
- ];
169
- }
170
- if (observation.type !== "input")
171
- return [];
159
+ key: observation.pressed,
160
+ }),
161
+ ];
162
+ }
163
+ /** An Alt+click */
164
+ asserted(observation) {
165
+ this.lastFill = undefined;
166
+ return [
167
+ this.insert({
168
+ kind: "expect",
169
+ locator: observation.locator,
170
+ not: false,
171
+ expectation: expectationFor(observation.text, observation.value),
172
+ }),
173
+ ];
174
+ }
175
+ /** Typing: one `fill` per field, rewritten as the text grows */
176
+ typed(observation, at) {
172
177
  this.lastAction = at;
173
178
  const step = {
174
179
  kind: "fill",
@@ -0,0 +1,56 @@
1
+ import type { RecorderEvent } from "./normalise.js";
2
+ import type { ControllerDeps, ControllerOptions } from "./toolbar/types.js";
3
+ /** The toolbar's settings: the recording's environment, and where its variables and secrets are written */
4
+ export type ToolbarOptions = ControllerOptions & {
5
+ /** The project folder: `samurai.config.ts` and `.env.<environment>` */
6
+ projectDir: string;
7
+ /**
8
+ * Called when the recording is paused or resumed, or the toolbar fails (or comes back) on a page.
9
+ * There is no initial call: until the first one the recording is `{ state: "recording", toolbar: true }`
10
+ */
11
+ onStatus?: ControllerDeps["onStatus"];
12
+ /** A secret was saved to `.env.<environment>` from the panel (before its value is typed) */
13
+ onSecretAdded?: (name: string) => void;
14
+ /**
15
+ * The toolbar's Stop; the caller then stops the recording (`recorder.stop()`), as `recordSpec` does.
16
+ * Without it, Stop runs `recorder.stop()` itself: the toolbar goes away and nothing more is recorded, but
17
+ * nothing tells the `page.record()` caller, so pass `onStop` to learn of it.
18
+ */
19
+ onStop?: () => void;
20
+ };
21
+ /** Where uploads made while recording are stored */
22
+ export interface FileRecording {
23
+ /** The project folder */
24
+ projectDir: string;
25
+ /** Relative to the project folder. @default "files" */
26
+ filesDir?: string;
27
+ /** A file that isn't stored (over 50 MB, or unreadable): its step points at `path`, which doesn't exist */
28
+ onMissing?: (file: MissingFile) => void;
29
+ /** @internal Tests only: also record events no person caused (`isTrusted` false) */
30
+ trustSynthetic?: boolean;
31
+ }
32
+ export interface MissingFile {
33
+ /** Project-relative, as the step has it */
34
+ path: string;
35
+ name: string;
36
+ size: number;
37
+ reason: "too-big" | "unreadable";
38
+ }
39
+ export interface RecorderOptions {
40
+ /** Called for every step the tester produces, in order. Steps get indexes from `at` on */
41
+ onEvent: (event: RecorderEvent) => void;
42
+ /**
43
+ * Index the first recorded step gets: record from the middle of a test by passing the number of steps
44
+ * before it. The page must already be in the state those steps lead to; replaying them is up to the caller.
45
+ * @default 0
46
+ */
47
+ at?: number;
48
+ /** Write the page the recording starts on as a `goto` step. Pass false when `at` > 0. @default true */
49
+ initialGoto?: boolean;
50
+ /** URLs inside it are written relative to it, as `page.goto` resolves them */
51
+ baseURL?: string;
52
+ /** Shows the recorder toolbar in the page (pause, assert, insert, wait, screenshot, stop). Without it, no toolbar */
53
+ toolbar?: ToolbarOptions;
54
+ /** Records uploads (file inputs and drops) into the project's files folder. Without it, uploads aren't recorded */
55
+ files?: FileRecording;
56
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import type { ElementHandle } from "../script/element-handle.js";
2
+ import type { LocatorCall, LocatorSpec } from "../steps/model.js";
3
+ import type { Observation } from "./normalise.js";
4
+ /** What the files capture script sends */
5
+ export type FilesMessage = {
6
+ type: "picker";
7
+ } | {
8
+ type: "picker-cancel";
9
+ } | {
10
+ type: "files";
11
+ via: "input" | "drop";
12
+ target: ElementHandle;
13
+ candidates: LocatorCall[];
14
+ files: {
15
+ id: number;
16
+ name: string;
17
+ type: string;
18
+ size: number;
19
+ }[];
20
+ };
21
+ /** What the capture script sends */
22
+ export interface PageMessage {
23
+ type: "click" | "input" | "press" | "assert";
24
+ target: ElementHandle;
25
+ candidates: LocatorCall[];
26
+ textEntry?: boolean;
27
+ /** The key of a `press` message */
28
+ pressed?: string;
29
+ value?: string;
30
+ secret?: boolean;
31
+ secretName?: string;
32
+ text?: string;
33
+ }
34
+ /** What a page message says, as the normaliser takes it once its element has a locator */
35
+ export declare function observationFor(message: PageMessage, locator: LocatorSpec): Observation;
@@ -0,0 +1,26 @@
1
+ /** What a page message says, as the normaliser takes it once its element has a locator */
2
+ export function observationFor(message, locator) {
3
+ const key = message.target.sharedId;
4
+ return message.type === "click"
5
+ ? { type: "click", key, locator, textEntry: message.textEntry === true }
6
+ : message.type === "press"
7
+ ? { type: "press", key, locator, pressed: message.pressed ?? "" }
8
+ : message.type === "assert"
9
+ ? {
10
+ type: "assert",
11
+ key,
12
+ locator,
13
+ text: message.text ?? "",
14
+ ...(message.value !== undefined && { value: message.value }),
15
+ }
16
+ : {
17
+ type: "input",
18
+ key,
19
+ locator,
20
+ ...(message.value !== undefined && { value: message.value }),
21
+ ...(message.secret && { secret: true }),
22
+ ...(message.secretName !== undefined && {
23
+ secretName: message.secretName,
24
+ }),
25
+ };
26
+ }
@@ -7,6 +7,7 @@ import { locatorFromSpec } from "../steps/build.js";
7
7
  export async function rankLocator(page, target, candidates, maxFallbacks = 2) {
8
8
  const verified = [];
9
9
  for (const candidate of candidates) {
10
+ // The primary plus `maxFallbacks` fallbacks: stop once one more than that is verified
10
11
  if (verified.length > maxFallbacks)
11
12
  break;
12
13
  const found = await locatorFromSpec(page, {
@@ -0,0 +1,6 @@
1
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
2
+ /**
3
+ * The window realms of `sandbox` in the context, as the browser lists them now. A script message names the realm it
4
+ * came from; only messages from a sandbox the page cannot reach are taken.
5
+ */
6
+ export declare function listSandboxRealms(connector: BiDiConnector, context: string, sandbox: string): Promise<Set<string>>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The window realms of `sandbox` in the context, as the browser lists them now. A script message names the realm it
3
+ * came from; only messages from a sandbox the page cannot reach are taken.
4
+ */
5
+ export async function listSandboxRealms(connector, context, sandbox) {
6
+ const { realms } = await connector.send("script.getRealms", {
7
+ context,
8
+ type: "window",
9
+ });
10
+ return new Set(realms
11
+ .filter((info) => info.type === "window" && info.sandbox === sandbox)
12
+ .map((info) => info.realm));
13
+ }
@@ -1,62 +1,10 @@
1
1
  import type Page from "../browser/page.js";
2
2
  import type { BiDiConnector } from "../transport/bidi-connection.js";
3
- import { type RecorderEvent } from "./normalise.js";
4
- import { type ControllerDeps, type ControllerOptions } from "./toolbar/controller.js";
3
+ import { missingFileText } from "./files-recorder.js";
4
+ import type { MissingFile, RecorderOptions } from "./options.js";
5
5
  export type { RecorderEvent } from "./normalise.js";
6
- /** The toolbar's settings: the recording's environment, and where its variables and secrets are written */
7
- export type ToolbarOptions = ControllerOptions & {
8
- /** The project folder: `samurai.config.ts` and `.env.<environment>` */
9
- projectDir: string;
10
- /**
11
- * Called when the recording is paused or resumed, or the toolbar fails (or comes back) on a page.
12
- * There is no initial call: until the first one the recording is `{ state: "recording", toolbar: true }`
13
- */
14
- onStatus?: ControllerDeps["onStatus"];
15
- /** A secret was saved to `.env.<environment>` from the panel (before its value is typed) */
16
- onSecretAdded?: (name: string) => void;
17
- /**
18
- * The toolbar's Stop; the caller then stops the recording (`recorder.stop()`), as `recordSpec` does.
19
- * Without it, Stop runs `recorder.stop()` itself: the toolbar goes away and nothing more is recorded, but
20
- * nothing tells the `page.record()` caller, so pass `onStop` to learn of it.
21
- */
22
- onStop?: () => void;
23
- };
24
- /** Where uploads made while recording are stored */
25
- export interface FileRecording {
26
- /** The project folder */
27
- projectDir: string;
28
- /** Relative to the project folder. @default "files" */
29
- filesDir?: string;
30
- /** A file that isn't stored (over 50 MB, or unreadable): its step points at `path`, which doesn't exist */
31
- onMissing?: (file: MissingFile) => void;
32
- /** @internal Tests only: also record events no person caused (`isTrusted` false) */
33
- trustSynthetic?: boolean;
34
- }
35
- export interface MissingFile {
36
- /** Project-relative, as the step has it */
37
- path: string;
38
- name: string;
39
- size: number;
40
- reason: "too-big" | "unreadable";
41
- }
42
- export interface RecorderOptions {
43
- /** Called for every step the tester produces, in order. Steps get indexes from `at` on */
44
- onEvent: (event: RecorderEvent) => void;
45
- /**
46
- * Index the first recorded step gets: record from the middle of a test by passing the number of steps
47
- * before it. The page must already be in the state those steps lead to; replaying them is up to the caller.
48
- * @default 0
49
- */
50
- at?: number;
51
- /** Write the page the recording starts on as a `goto` step. Pass false when `at` > 0. @default true */
52
- initialGoto?: boolean;
53
- /** URLs inside it are written relative to it, as `page.goto` resolves them */
54
- baseURL?: string;
55
- /** Shows the recorder toolbar in the page (pause, assert, insert, wait, screenshot, stop). Without it, no toolbar */
56
- toolbar?: ToolbarOptions;
57
- /** Records uploads (file inputs and drops) into the project's files folder. Without it, uploads aren't recorded */
58
- files?: FileRecording;
59
- }
6
+ export type { FileRecording, MissingFile, RecorderOptions, ToolbarOptions, } from "./options.js";
7
+ export { missingFileText };
60
8
  /**
61
9
  * Watches the tester use the page and reports steps as they happen. A preload script reports clicks,
62
10
  * typing and Alt+click assertions over a `script.message` channel; each element is given locators that
@@ -78,23 +26,19 @@ export declare class Recorder {
78
26
  /** Set when stopping begins: files messages after it are not taken */
79
27
  private ending;
80
28
  private stopping;
81
- private controller;
29
+ private toolbar;
30
+ private files;
82
31
  private constructor();
83
32
  /** The files capture script's channel, handed only to its sandbox */
84
33
  private readonly filesChannel;
85
34
  private filesPreload;
86
35
  private filesRealms;
87
- private readonly stored;
88
- private readonly missing;
36
+ /** The toolbar's controller, when the recording has a toolbar */
37
+ private get controller();
89
38
  /** Files stored or reused in the files folder by this recording, project-relative */
90
39
  get storedFiles(): string[];
91
40
  /** Files whose steps point at a path that wasn't written */
92
41
  get missingFiles(): MissingFile[];
93
- /** The context's `samurai-toolbar` sandbox realms, as last listed: the only realms toolbar messages are taken from */
94
- private toolbarRealms;
95
- /** Toolbar messages waiting for a realm lookup, and those that arrived behind them (kept in order) */
96
- private toolbarBacklog;
97
- private toolbarWaiting;
98
42
  static start(target: {
99
43
  page: Page;
100
44
  connector: BiDiConnector;
@@ -108,25 +52,19 @@ export declare class Recorder {
108
52
  */
109
53
  private readonly onMessage;
110
54
  private listFilesRealms;
111
- private listToolbarRealms;
112
- /** A message from the toolbar's own sandbox */
113
- private fromToolbar;
114
55
  private readonly onLoad;
115
56
  private readonly onNavigation;
116
57
  private subscription;
117
58
  private preload;
118
59
  private toolbarPreload;
119
60
  private begin;
120
- private createController;
121
- /** Calls the toolbar hook's `method` in the loaded document; a document between loads has no toolbar yet */
122
- private callToolbar;
61
+ private createToolbar;
62
+ private createFiles;
123
63
  private enqueue;
124
64
  private emit;
125
65
  private locatorFor;
126
66
  private handle;
127
67
  private fromFiles;
128
- /** Saves one kept file to the files folder; returns the path its step gets */
129
- private storePageFile;
130
68
  private release;
131
69
  /**
132
70
  * Pauses capture in the loaded document: clicks and typing are not reported until `unmute`.
@@ -140,5 +78,3 @@ export declare class Recorder {
140
78
  stop(): Promise<void>;
141
79
  private end;
142
80
  }
143
- /** The warning for a file that isn't saved, as the toolbar and the CLI show it */
144
- export declare function missingFileText(file: MissingFile, folder: string): string;