@itmetsam/samurai-framework 0.9.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/README.md +4 -5
  2. package/dist/assert/assertion-error.d.ts +1 -1
  3. package/dist/assert/expect.d.ts +5 -50
  4. package/dist/assert/expect.js +5 -182
  5. package/dist/assert/locator-assertions.d.ts +22 -0
  6. package/dist/assert/locator-assertions.js +61 -0
  7. package/dist/assert/pending-assertions.d.ts +7 -0
  8. package/dist/assert/pending-assertions.js +40 -0
  9. package/dist/assert/text-match.d.ts +4 -0
  10. package/dist/assert/text-match.js +11 -0
  11. package/dist/assert/value-assertions.d.ts +23 -0
  12. package/dist/assert/value-assertions.js +82 -0
  13. package/dist/browser/browser-context.d.ts +0 -1
  14. package/dist/browser/browser-context.js +13 -5
  15. package/dist/browser/browser-finder.js +5 -9
  16. package/dist/browser/browser.d.ts +3 -31
  17. package/dist/browser/browser.js +36 -219
  18. package/dist/browser/chrome-launch.d.ts +14 -1
  19. package/dist/browser/chrome-launch.js +44 -1
  20. package/dist/browser/dialog-handling.d.ts +32 -0
  21. package/dist/browser/dialog-handling.js +85 -0
  22. package/dist/browser/document-watcher.d.ts +44 -0
  23. package/dist/browser/document-watcher.js +160 -0
  24. package/dist/browser/download-folders.d.ts +0 -1
  25. package/dist/browser/download-folders.js +7 -15
  26. package/dist/browser/download.js +5 -10
  27. package/dist/browser/downloads-dir.js +2 -2
  28. package/dist/browser/emit.d.ts +7 -0
  29. package/dist/browser/emit.js +16 -0
  30. package/dist/browser/emulation.d.ts +1 -1
  31. package/dist/browser/emulation.js +54 -18
  32. package/dist/browser/file-chooser.js +4 -11
  33. package/dist/browser/history.d.ts +6 -0
  34. package/dist/browser/history.js +89 -0
  35. package/dist/browser/index.d.ts +2 -0
  36. package/dist/browser/index.js +2 -0
  37. package/dist/browser/init-script.d.ts +2 -0
  38. package/dist/browser/init-script.js +16 -0
  39. package/dist/browser/launch-firefox.d.ts +20 -0
  40. package/dist/browser/launch-firefox.js +66 -0
  41. package/dist/browser/launch-flags.d.ts +18 -0
  42. package/dist/browser/launch-flags.js +100 -0
  43. package/dist/browser/lazy.d.ts +21 -0
  44. package/dist/browser/lazy.js +44 -0
  45. package/dist/browser/page-events.d.ts +44 -0
  46. package/dist/browser/page-events.js +58 -0
  47. package/dist/browser/page-logs.js +2 -9
  48. package/dist/browser/page-navigation.d.ts +58 -0
  49. package/dist/browser/page-navigation.js +108 -0
  50. package/dist/browser/page-network.d.ts +69 -0
  51. package/dist/browser/page-network.js +163 -0
  52. package/dist/browser/page-settings.d.ts +35 -0
  53. package/dist/browser/page-settings.js +51 -0
  54. package/dist/browser/page-transfers.d.ts +36 -0
  55. package/dist/browser/page-transfers.js +50 -0
  56. package/dist/browser/page.d.ts +39 -154
  57. package/dist/browser/page.js +134 -758
  58. package/dist/browser/pdf.d.ts +1 -1
  59. package/dist/browser/session.d.ts +16 -0
  60. package/dist/browser/session.js +32 -0
  61. package/dist/browser/viewport.d.ts +19 -0
  62. package/dist/browser/viewport.js +72 -0
  63. package/dist/browser/warm-up.d.ts +1 -1
  64. package/dist/browser/warm-up.js +2 -1
  65. package/dist/browsers/index.d.ts +1 -1
  66. package/dist/browsers/index.js +1 -1
  67. package/dist/browsers/pins.d.ts +0 -2
  68. package/dist/browsers/pins.js +0 -2
  69. package/dist/browsers/store.d.ts +0 -2
  70. package/dist/browsers/store.js +1 -2
  71. package/dist/cli/args.d.ts +19 -7
  72. package/dist/cli/args.js +18 -9
  73. package/dist/cli/browsers.d.ts +1 -2
  74. package/dist/cli/browsers.js +2 -2
  75. package/dist/cli/console-reporter.d.ts +4 -0
  76. package/dist/cli/console-reporter.js +15 -7
  77. package/dist/cli/flow-reporter.js +2 -1
  78. package/dist/cli/flow.d.ts +1 -2
  79. package/dist/cli/flow.js +25 -22
  80. package/dist/cli/init.js +3 -8
  81. package/dist/cli/io.d.ts +10 -0
  82. package/dist/cli/io.js +1 -0
  83. package/dist/cli/main.d.ts +3 -10
  84. package/dist/cli/main.js +11 -137
  85. package/dist/cli/paint.d.ts +2 -0
  86. package/dist/cli/paint.js +4 -0
  87. package/dist/cli/record.d.ts +4 -0
  88. package/dist/cli/record.js +93 -0
  89. package/dist/cli/usage.d.ts +1 -0
  90. package/dist/cli/usage.js +58 -0
  91. package/dist/cli/version.d.ts +2 -0
  92. package/dist/cli/version.js +7 -0
  93. package/dist/config/browsers.d.ts +11 -0
  94. package/dist/config/browsers.js +32 -0
  95. package/dist/config/config.js +8 -1
  96. package/dist/config/mask.d.ts +1 -1
  97. package/dist/config/mask.js +7 -2
  98. package/dist/config/run-settings.d.ts +1 -2
  99. package/dist/config/run-settings.js +1 -6
  100. package/dist/config/timeouts.d.ts +2 -0
  101. package/dist/config/timeouts.js +13 -0
  102. package/dist/config/workers.d.ts +14 -0
  103. package/dist/config/workers.js +29 -0
  104. package/dist/flows/run/branch-context.d.ts +14 -0
  105. package/dist/flows/run/branch-context.js +35 -0
  106. package/dist/flows/run/handlers/condition.d.ts +1 -1
  107. package/dist/flows/run/handlers/end.d.ts +1 -1
  108. package/dist/flows/run/handlers/group.d.ts +1 -1
  109. package/dist/flows/run/handlers/index.d.ts +2 -2
  110. package/dist/flows/run/handlers/parallel.d.ts +1 -1
  111. package/dist/flows/run/handlers/start.d.ts +1 -1
  112. package/dist/flows/run/handlers/support.d.ts +3 -24
  113. package/dist/flows/run/handlers/support.js +1 -1
  114. package/dist/flows/run/handlers/test.d.ts +1 -1
  115. package/dist/flows/run/handlers/types.d.ts +27 -0
  116. package/dist/flows/run/handlers/types.js +1 -0
  117. package/dist/flows/run/index.d.ts +1 -1
  118. package/dist/flows/run/prepare-flow.d.ts +14 -0
  119. package/dist/flows/run/prepare-flow.js +44 -0
  120. package/dist/flows/run/project.d.ts +33 -2
  121. package/dist/flows/run/project.js +139 -49
  122. package/dist/flows/run/run.d.ts +8 -4
  123. package/dist/flows/run/run.js +30 -194
  124. package/dist/flows/run/walker.d.ts +56 -0
  125. package/dist/flows/run/walker.js +225 -0
  126. package/dist/flows/steps/catalog.d.ts +1 -1
  127. package/dist/flows/steps/handler.d.ts +1 -1
  128. package/dist/flows/steps/set.d.ts +1 -1
  129. package/dist/index.js +1 -1
  130. package/dist/locator/actionability.d.ts +33 -0
  131. package/dist/locator/actionability.js +104 -0
  132. package/dist/locator/chain-resolver.d.ts +20 -0
  133. package/dist/locator/chain-resolver.js +102 -0
  134. package/dist/locator/document-swap.d.ts +1 -0
  135. package/dist/locator/document-swap.js +11 -0
  136. package/dist/locator/element-state.d.ts +3 -1
  137. package/dist/locator/element-state.js +11 -3
  138. package/dist/locator/file-actions.d.ts +35 -0
  139. package/dist/locator/file-actions.js +55 -0
  140. package/dist/{script/helpers.js → locator/helper-realm.js} +2 -2
  141. package/dist/locator/input-actions.d.ts +18 -0
  142. package/dist/locator/input-actions.js +80 -0
  143. package/dist/locator/label-locator.d.ts +1 -1
  144. package/dist/locator/label-locator.js +5 -12
  145. package/dist/locator/locator.d.ts +14 -37
  146. package/dist/locator/locator.js +68 -424
  147. package/dist/locator/page-scripts.d.ts +16 -0
  148. package/dist/locator/page-scripts.js +54 -0
  149. package/dist/locator/page-search.d.ts +10 -0
  150. package/dist/locator/page-search.js +19 -0
  151. package/dist/locator/selector-errors.d.ts +0 -4
  152. package/dist/locator/selector-errors.js +0 -7
  153. package/dist/locator/selector.d.ts +14 -13
  154. package/dist/locator/selector.js +10 -16
  155. package/dist/locator/text-locator.d.ts +1 -1
  156. package/dist/locator/text-locator.js +5 -9
  157. package/dist/logger/index.d.ts +6 -0
  158. package/dist/logger/index.js +14 -7
  159. package/dist/network/bidi-wire.d.ts +10 -0
  160. package/dist/network/bidi-wire.js +38 -0
  161. package/dist/network/intercept.d.ts +30 -0
  162. package/dist/network/intercept.js +130 -0
  163. package/dist/network/network-tracker.d.ts +10 -47
  164. package/dist/network/network-tracker.js +34 -68
  165. package/dist/network/network-types.d.ts +33 -0
  166. package/dist/network/network-types.js +1 -0
  167. package/dist/network/network-wait.d.ts +7 -2
  168. package/dist/network/network-wait.js +14 -4
  169. package/dist/network/response.d.ts +1 -1
  170. package/dist/network/response.js +1 -1
  171. package/dist/network/route.d.ts +92 -0
  172. package/dist/network/route.js +142 -0
  173. package/dist/network/router.d.ts +6 -91
  174. package/dist/network/router.js +25 -258
  175. package/dist/recorder/expectation.d.ts +5 -0
  176. package/dist/recorder/expectation.js +20 -0
  177. package/dist/recorder/files-recorder.d.ts +30 -0
  178. package/dist/recorder/files-recorder.js +88 -0
  179. package/dist/recorder/normalise.d.ts +17 -6
  180. package/dist/recorder/normalise.js +96 -91
  181. package/dist/recorder/options.d.ts +56 -0
  182. package/dist/recorder/options.js +1 -0
  183. package/dist/recorder/page-messages.d.ts +35 -0
  184. package/dist/recorder/page-messages.js +26 -0
  185. package/dist/recorder/ranking.js +1 -0
  186. package/dist/recorder/realms.d.ts +6 -0
  187. package/dist/recorder/realms.js +13 -0
  188. package/dist/recorder/recorder.d.ts +10 -74
  189. package/dist/recorder/recorder.js +39 -199
  190. package/dist/recorder/session-browser.d.ts +22 -0
  191. package/dist/recorder/session-browser.js +21 -0
  192. package/dist/recorder/session-secrets.d.ts +8 -0
  193. package/dist/recorder/session-secrets.js +45 -0
  194. package/dist/recorder/session.d.ts +7 -13
  195. package/dist/recorder/session.js +22 -117
  196. package/dist/recorder/spec-source.d.ts +39 -0
  197. package/dist/recorder/spec-source.js +84 -0
  198. package/dist/recorder/toolbar/action-guard.d.ts +21 -0
  199. package/dist/recorder/toolbar/action-guard.js +43 -0
  200. package/dist/recorder/toolbar/actions/assert.d.ts +4 -0
  201. package/dist/recorder/toolbar/actions/assert.js +52 -0
  202. package/dist/recorder/toolbar/actions/insert.d.ts +6 -0
  203. package/dist/recorder/toolbar/actions/insert.js +93 -0
  204. package/dist/recorder/toolbar/actions/pick.d.ts +8 -0
  205. package/dist/recorder/toolbar/actions/pick.js +26 -0
  206. package/dist/recorder/toolbar/actions/reply.d.ts +7 -0
  207. package/dist/recorder/toolbar/actions/reply.js +10 -0
  208. package/dist/recorder/toolbar/controller-fixture.d.ts +47 -0
  209. package/dist/recorder/toolbar/controller-fixture.js +129 -0
  210. package/dist/recorder/toolbar/controller.d.ts +12 -78
  211. package/dist/recorder/toolbar/controller.js +35 -247
  212. package/dist/recorder/toolbar/inspect.d.ts +1 -3
  213. package/dist/recorder/toolbar/inspect.js +2 -2
  214. package/dist/recorder/toolbar/page-scope.d.ts +19 -0
  215. package/dist/recorder/toolbar/page-scope.js +43 -0
  216. package/dist/recorder/toolbar/protocol.d.ts +2 -1
  217. package/dist/recorder/toolbar/source/attach.d.ts +2 -0
  218. package/dist/recorder/toolbar/source/attach.js +41 -0
  219. package/dist/recorder/toolbar/source/dom.d.ts +2 -0
  220. package/dist/recorder/toolbar/source/dom.js +47 -0
  221. package/dist/recorder/toolbar/source/drag.d.ts +2 -0
  222. package/dist/recorder/toolbar/source/drag.js +32 -0
  223. package/dist/recorder/toolbar/source/events-page.d.ts +2 -0
  224. package/dist/recorder/toolbar/source/events-page.js +67 -0
  225. package/dist/recorder/toolbar/source/events-toolbar.d.ts +2 -0
  226. package/dist/recorder/toolbar/source/events-toolbar.js +35 -0
  227. package/dist/recorder/toolbar/source/fields.d.ts +2 -0
  228. package/dist/recorder/toolbar/source/fields.js +44 -0
  229. package/dist/recorder/toolbar/source/handlers.d.ts +2 -0
  230. package/dist/recorder/toolbar/source/handlers.js +134 -0
  231. package/dist/recorder/toolbar/source/names.d.ts +2 -0
  232. package/dist/recorder/toolbar/source/names.js +12 -0
  233. package/dist/recorder/toolbar/source/picking.d.ts +2 -0
  234. package/dist/recorder/toolbar/source/picking.js +72 -0
  235. package/dist/recorder/toolbar/source/placement.d.ts +2 -0
  236. package/dist/recorder/toolbar/source/placement.js +45 -0
  237. package/dist/recorder/toolbar/source/recorder-side.d.ts +2 -0
  238. package/dist/recorder/toolbar/source/recorder-side.js +54 -0
  239. package/dist/recorder/toolbar/source/setup.d.ts +2 -0
  240. package/dist/recorder/toolbar/source/setup.js +50 -0
  241. package/dist/recorder/toolbar/source/views-panels.d.ts +2 -0
  242. package/dist/recorder/toolbar/source/views-panels.js +150 -0
  243. package/dist/recorder/toolbar/source/views-rail.d.ts +2 -0
  244. package/dist/recorder/toolbar/source/views-rail.js +40 -0
  245. package/dist/recorder/toolbar/source/views-render.d.ts +2 -0
  246. package/dist/recorder/toolbar/source/views-render.js +48 -0
  247. package/dist/recorder/toolbar/source.js +30 -856
  248. package/dist/recorder/toolbar/types.d.ts +70 -0
  249. package/dist/recorder/toolbar/types.js +1 -0
  250. package/dist/recorder/toolbar-bridge.d.ts +43 -0
  251. package/dist/recorder/toolbar-bridge.js +106 -0
  252. package/dist/runner/browsers.d.ts +10 -0
  253. package/dist/runner/browsers.js +12 -0
  254. package/dist/runner/groups.d.ts +5 -1
  255. package/dist/runner/groups.js +9 -2
  256. package/dist/runner/options.d.ts +31 -0
  257. package/dist/runner/options.js +1 -0
  258. package/dist/runner/pooled-run.d.ts +43 -0
  259. package/dist/runner/pooled-run.js +75 -0
  260. package/dist/runner/ports.d.ts +2 -0
  261. package/dist/runner/ports.js +2 -0
  262. package/dist/runner/registry.d.ts +1 -1
  263. package/dist/runner/registry.js +1 -1
  264. package/dist/runner/reporter.d.ts +40 -11
  265. package/dist/runner/reporter.js +69 -16
  266. package/dist/runner/run-test.d.ts +16 -0
  267. package/dist/runner/run-test.js +178 -0
  268. package/dist/runner/run.d.ts +5 -2
  269. package/dist/runner/run.js +28 -28
  270. package/dist/runner/select.d.ts +24 -0
  271. package/dist/runner/select.js +77 -0
  272. package/dist/runner/test-dir.d.ts +13 -0
  273. package/dist/runner/test-dir.js +27 -0
  274. package/dist/runner/test-runner.d.ts +27 -50
  275. package/dist/runner/test-runner.js +102 -264
  276. package/dist/runner/worker-pool.d.ts +67 -0
  277. package/dist/runner/worker-pool.js +275 -0
  278. package/dist/runner/worker-process.d.ts +6 -0
  279. package/dist/runner/worker-process.js +54 -0
  280. package/dist/runner/worker-protocol.d.ts +67 -0
  281. package/dist/runner/worker-protocol.js +1 -0
  282. package/dist/runner/worker-session.d.ts +7 -0
  283. package/dist/runner/worker-session.js +60 -0
  284. package/dist/runner/worker.d.ts +26 -0
  285. package/dist/runner/worker.js +121 -0
  286. package/dist/runner/workers-option.d.ts +15 -0
  287. package/dist/runner/workers-option.js +22 -0
  288. package/dist/script/call-function.d.ts +1 -9
  289. package/dist/script/call-function.js +2 -19
  290. package/dist/script/element-handle.d.ts +1 -2
  291. package/dist/script/element-handle.js +1 -2
  292. package/dist/script/serialize.d.ts +3 -12
  293. package/dist/script/serialize.js +53 -62
  294. package/dist/steps/parse/call.d.ts +3 -0
  295. package/dist/steps/parse/call.js +81 -0
  296. package/dist/steps/parse/expectation.d.ts +5 -0
  297. package/dist/steps/parse/expectation.js +71 -0
  298. package/dist/steps/parse/file-entry.d.ts +4 -0
  299. package/dist/steps/parse/file-entry.js +74 -0
  300. package/dist/steps/parse/literals.d.ts +4 -0
  301. package/dist/steps/parse/literals.js +28 -0
  302. package/dist/steps/parse/step.d.ts +4 -0
  303. package/dist/steps/parse/step.js +81 -0
  304. package/dist/steps/parse.js +2 -323
  305. package/dist/transport/bidi-connection.d.ts +4 -2
  306. package/dist/transport/bidi-connection.js +9 -43
  307. package/dist/transport/log-redaction.d.ts +13 -0
  308. package/dist/transport/log-redaction.js +51 -0
  309. package/dist/transport/subscriptions.js +18 -13
  310. package/dist/transport/unsupported.d.ts +7 -0
  311. package/dist/transport/unsupported.js +13 -0
  312. package/dist/types/bidi-modules/browser.d.ts +1 -12
  313. package/dist/types/bidi-modules/browsing-context.d.ts +8 -18
  314. package/dist/types/bidi-modules/emulation.d.ts +29 -21
  315. package/dist/types/bidi-modules/input.d.ts +1 -9
  316. package/dist/types/bidi-modules/network.d.ts +29 -24
  317. package/dist/types/bidi-modules/script.d.ts +0 -9
  318. package/dist/types/bidi-modules/session.d.ts +0 -8
  319. package/dist/types/bidi-modules/storage.d.ts +0 -5
  320. package/dist/types/bidi.d.ts +7 -37
  321. package/dist/types/config.d.ts +17 -3
  322. package/dist/types/test.d.ts +15 -2
  323. package/dist/wait/wait-until.d.ts +1 -5
  324. package/dist/wait/wait-until.js +7 -16
  325. package/package.json +3 -2
  326. package/dist/config/default.d.ts +0 -2
  327. package/dist/config/default.js +0 -3
  328. package/dist/types/bidi-modules/web-extensions.d.ts +0 -9
  329. package/dist/types/bidi-protocols/command.d.ts +0 -7
  330. package/dist/types/bidi-protocols/event.d.ts +0 -17
  331. /package/dist/{script/helpers.d.ts → locator/helper-realm.d.ts} +0 -0
@@ -0,0 +1,70 @@
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
+ import type { ActionReply, PickInfo, ToolbarMessage, ToolbarState } from "./protocol.js";
5
+ /** What `inspect` found for a picked element: the reply, and the locator behind each Count choice */
6
+ export interface Inspection {
7
+ info: PickInfo & {
8
+ ok: true;
9
+ };
10
+ /** `counts[i]` is the locator `info.counts[i]` describes */
11
+ counts: LocatorCall[];
12
+ }
13
+ /** Everything the controller does to the browser and the project; the recorder implements these */
14
+ export interface ControllerDeps {
15
+ /** Sends the state to every loaded top-level document's toolbar */
16
+ push(state: ToolbarState): void;
17
+ reply(id: number, result: PickInfo | ActionReply): void;
18
+ currentUrl(): Promise<string>;
19
+ rank(target: ElementHandle, candidates: LocatorCall[]): Promise<LocatorSpec | undefined>;
20
+ inspect(target: ElementHandle, locator: LocatorSpec, candidates: LocatorCall[]): Promise<Inspection>;
21
+ /** Types into the field with capture muted */
22
+ type(locator: LocatorSpec, value: string): Promise<void>;
23
+ mute(): Promise<void>;
24
+ unmute(): Promise<void>;
25
+ addVariable(name: string, value: string): Promise<void>;
26
+ setSecret(name: string, value: string): Promise<void>;
27
+ /** A secret was written to the project (called before its value is typed) */
28
+ secretAdded(name: string): void;
29
+ observe(observation: Observation): void;
30
+ stop(): void;
31
+ onStatus(status: {
32
+ state: "recording" | "paused";
33
+ toolbar: boolean;
34
+ }): void;
35
+ }
36
+ export interface ControllerOptions {
37
+ environment: string;
38
+ canAddVariables: boolean;
39
+ variables: Record<string, string | number | boolean>;
40
+ /** Name → value; undefined when not set */
41
+ secrets: Record<string, string | undefined>;
42
+ secretsIgnored: boolean;
43
+ /** Screenshot steps already in the test, for the default name */
44
+ screenshots: number;
45
+ }
46
+ export type Message = ToolbarMessage & {
47
+ target?: ElementHandle;
48
+ };
49
+ export type Of<A extends Message["action"]> = Extract<Message, {
50
+ action: A;
51
+ }>;
52
+ /** The element picked for an Assert, and the locator behind each of its Count choices */
53
+ export interface PickState {
54
+ id: number;
55
+ locator: LocatorSpec;
56
+ counts: LocatorCall[];
57
+ }
58
+ /** What the action handlers (assert, insert, add a variable or secret) use of the controller */
59
+ export interface ActionContext {
60
+ readonly deps: ControllerDeps;
61
+ readonly options: ControllerOptions;
62
+ readonly variables: Map<string, string>;
63
+ readonly secrets: Map<string, string | undefined>;
64
+ isPaused(): boolean;
65
+ /** The picked element an `assert` with the same id refers to */
66
+ pick(): PickState | undefined;
67
+ setPick(pick: PickState | undefined): void;
68
+ showToast(text: string): void;
69
+ push(): void;
70
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import type Page from "../browser/page.js";
2
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
3
+ import type { Observation } from "./normalise.js";
4
+ import type { ToolbarOptions } from "./options.js";
5
+ import { ToolbarController } from "./toolbar/controller.js";
6
+ /** The toolbar's hook in its sandbox */
7
+ export declare const TOOLBAR_HOOK: string;
8
+ /** What the bridge needs of the recording it belongs to */
9
+ export interface BridgeHost {
10
+ page: Page;
11
+ connector: BiDiConnector;
12
+ context: string;
13
+ mute(): Promise<void>;
14
+ unmute(): Promise<void>;
15
+ /** A step the toolbar made (wait, screenshot, insert, assertion) */
16
+ observe(observation: Observation): void;
17
+ /** The toolbar's Stop, when the caller did not ask to be told */
18
+ stop(): void;
19
+ /** Runs `work` in order with the recording's other events */
20
+ enqueue(work: () => Promise<void>): void;
21
+ }
22
+ /**
23
+ * The recorder's side of the toolbar: builds its controller, sends it state and replies, and takes its messages
24
+ * only from the toolbar's own sandbox realm (the page cannot reach that sandbox).
25
+ */
26
+ export declare class ToolbarBridge {
27
+ private readonly host;
28
+ readonly controller: ToolbarController;
29
+ /** The context's `samurai-toolbar` sandbox realms, as last listed */
30
+ private realms;
31
+ /** Messages waiting for a realm lookup, and those that arrived behind them (kept in order) */
32
+ private backlog;
33
+ private waiting;
34
+ constructor(host: BridgeHost, toolbar: ToolbarOptions);
35
+ /** A message that came on the toolbar's channel from `realm` */
36
+ receive(realm: string, data: unknown): void;
37
+ private listRealms;
38
+ /** A message from the toolbar's own sandbox */
39
+ private fromToolbar;
40
+ private createController;
41
+ /** Calls the toolbar hook's `method` in the loaded document; a document between loads has no toolbar yet */
42
+ private call;
43
+ }
@@ -0,0 +1,106 @@
1
+ import logger from "../logger/index.js";
2
+ import { callFunction } from "../script/call-function.js";
3
+ import { fromRemoteValue } from "../script/serialize.js";
4
+ import { locatorFromSpec } from "../steps/build.js";
5
+ import { addVariable, setSecret } from "../project/index.js";
6
+ import { listSandboxRealms } from "./realms.js";
7
+ import { rankLocator } from "./ranking.js";
8
+ import { ToolbarController } from "./toolbar/controller.js";
9
+ import { inspectElement } from "./toolbar/inspect.js";
10
+ import { TOOLBAR_KEY, TOOLBAR_SANDBOX, } from "./toolbar/protocol.js";
11
+ /** The toolbar's hook in its sandbox */
12
+ export const TOOLBAR_HOOK = `globalThis[Symbol.for(${JSON.stringify(TOOLBAR_KEY)})]`;
13
+ /**
14
+ * The recorder's side of the toolbar: builds its controller, sends it state and replies, and takes its messages
15
+ * only from the toolbar's own sandbox realm (the page cannot reach that sandbox).
16
+ */
17
+ export class ToolbarBridge {
18
+ constructor(host, toolbar) {
19
+ this.host = host;
20
+ /** The context's `samurai-toolbar` sandbox realms, as last listed */
21
+ this.realms = new Set();
22
+ /** Messages waiting for a realm lookup, and those that arrived behind them (kept in order) */
23
+ this.backlog = Promise.resolve();
24
+ this.waiting = 0;
25
+ this.controller = this.createController(toolbar);
26
+ }
27
+ /** A message that came on the toolbar's channel from `realm` */
28
+ receive(realm, data) {
29
+ if (this.waiting === 0 && this.realms.has(realm)) {
30
+ this.fromToolbar(data);
31
+ return;
32
+ }
33
+ // A sandbox not seen yet (a new document's toolbar): look it up, and keep later messages behind it
34
+ this.waiting++;
35
+ this.backlog = this.backlog
36
+ .then(async () => {
37
+ if (!this.realms.has(realm))
38
+ await this.listRealms();
39
+ if (this.realms.has(realm))
40
+ this.fromToolbar(data);
41
+ else
42
+ logger.warn("Recorder ignored a toolbar message from outside the toolbar's sandbox");
43
+ })
44
+ .catch((err) => {
45
+ logger.debug("Could not check a toolbar message's realm", { err });
46
+ })
47
+ .finally(() => this.waiting--);
48
+ }
49
+ async listRealms() {
50
+ this.realms = await listSandboxRealms(this.host.connector, this.host.context, TOOLBAR_SANDBOX);
51
+ }
52
+ /** A message from the toolbar's own sandbox */
53
+ fromToolbar(data) {
54
+ const message = fromRemoteValue(data);
55
+ const controller = this.controller;
56
+ if (message.type !== "toolbar" || !("action" in message))
57
+ return;
58
+ if (message.action === "ready") {
59
+ // Not behind a slow insert: the ready watchdog is waiting for it
60
+ void controller.handle(message);
61
+ }
62
+ else if (controller.accept(message)) {
63
+ // Decided as it arrives: a double click's second copy would only run after the first had finished
64
+ this.host.enqueue(async () => controller.handle(message));
65
+ }
66
+ }
67
+ createController(toolbar) {
68
+ const { page, connector, context } = this.host;
69
+ const { projectDir, environment, onStatus, onStop, onSecretAdded } = toolbar;
70
+ return new ToolbarController({
71
+ push: (state) => this.call("update", [state]),
72
+ reply: (id, result) => this.call("reply", [id, result]),
73
+ currentUrl: () => page.url(),
74
+ rank: (target, candidates) => rankLocator(page, target, candidates),
75
+ inspect: (target, locator, candidates) => inspectElement(page, connector, context, target, locator, candidates),
76
+ type: async (locator, value) => {
77
+ await this.host.mute();
78
+ try {
79
+ await locatorFromSpec(page, locator).fill(value, {
80
+ timeout: 5000,
81
+ });
82
+ }
83
+ finally {
84
+ await this.host.unmute();
85
+ }
86
+ },
87
+ mute: () => this.host.mute(),
88
+ unmute: () => this.host.unmute(),
89
+ addVariable: (name, value) => addVariable(projectDir, environment, name, value),
90
+ setSecret: (name, value) => setSecret(projectDir, environment, name, value),
91
+ secretAdded: (name) => onSecretAdded?.(name),
92
+ observe: (observation) => this.host.observe(observation),
93
+ stop: () => {
94
+ if (onStop)
95
+ onStop();
96
+ else
97
+ this.host.stop();
98
+ },
99
+ onStatus: (status) => onStatus?.(status),
100
+ }, toolbar);
101
+ }
102
+ /** Calls the toolbar hook's `method` in the loaded document; a document between loads has no toolbar yet */
103
+ call(method, args) {
104
+ callFunction(this.host.connector, this.host.context, `(...args) => { ${TOOLBAR_HOOK}?.${method}(...args); }`, args, { sandbox: TOOLBAR_SANDBOX, awaitPromise: false }).catch((err) => logger.debug("Could not reach the recorder toolbar", { err }));
105
+ }
106
+ }
@@ -0,0 +1,10 @@
1
+ import type { SupportedBrowser } from "../types/browser.js";
2
+ /**
3
+ * The browsers a run uses, the first that is set winning: the command line, the group being run, the project, then
4
+ * Firefox. An empty list counts as not given.
5
+ */
6
+ export declare function browsersFor(sources: {
7
+ flag?: readonly SupportedBrowser[] | undefined;
8
+ group?: readonly SupportedBrowser[] | undefined;
9
+ project?: readonly SupportedBrowser[] | undefined;
10
+ }): SupportedBrowser[];
@@ -0,0 +1,12 @@
1
+ /** The browsers of a run that names none */
2
+ const DEFAULT_BROWSERS = ["firefox"];
3
+ /**
4
+ * The browsers a run uses, the first that is set winning: the command line, the group being run, the project, then
5
+ * Firefox. An empty list counts as not given.
6
+ */
7
+ export function browsersFor(sources) {
8
+ for (const list of [sources.flag, sources.group, sources.project])
9
+ if (list && list.length > 0)
10
+ return [...list];
11
+ return [...DEFAULT_BROWSERS];
12
+ }
@@ -1,3 +1,4 @@
1
+ import type { SupportedBrowser } from "../types/browser.js";
1
2
  import type { SamuraiGroup } from "../types/config.js";
2
3
  /** The test folder when the config names none */
3
4
  export declare const DEFAULT_SRC_DIR = "./src";
@@ -22,6 +23,8 @@ export interface ResolvedGroup {
22
23
  file: string;
23
24
  title: string;
24
25
  }[];
26
+ /** The browsers the group's tests run in: its own `browsers`, else the project's, else Firefox */
27
+ browsers: SupportedBrowser[];
25
28
  }
26
29
  /**
27
30
  * The real path of `p` (symlinks resolved), or `p` itself when it does not exist. Registered test files come
@@ -33,10 +36,11 @@ export declare const toPosix: (p: string) => string;
33
36
  /** The id of a test: its spec file relative to `srcDir` with `/` separators, then `::` and its full name */
34
37
  export declare const testId: (file: string, name: string) => string;
35
38
  /** Works out which of `tests` (files relative to `srcDir`) belong to the group */
36
- export declare function resolveGroup(group: SamuraiGroup, tests: GroupTest[], projectDir: string, srcDir: string): ResolvedGroup;
39
+ export declare function resolveGroup(group: SamuraiGroup, tests: GroupTest[], projectDir: string, srcDir: string, projectBrowsers?: readonly SupportedBrowser[]): ResolvedGroup;
37
40
  /** Resolves every group of the config */
38
41
  export declare function resolveGroups(config: {
39
42
  groups?: SamuraiGroup[];
43
+ browsers?: SupportedBrowser[];
40
44
  }, tests: GroupTest[], projectDir: string, srcDir: string): ResolvedGroup[];
41
45
  /** The group called `name`, or an error naming the groups there are */
42
46
  export declare function findGroup<T extends {
@@ -1,5 +1,6 @@
1
1
  import { realpathSync } from "node:fs";
2
2
  import { matchesGlob, relative, resolve } from "node:path";
3
+ import { browsersFor } from "./browsers.js";
3
4
  /** The test folder when the config names none */
4
5
  export const DEFAULT_SRC_DIR = "./src";
5
6
  /** The spec pattern when a group names none */
@@ -21,7 +22,11 @@ export const toPosix = (p) => p.replace(/\\/g, "/");
21
22
  /** The id of a test: its spec file relative to `srcDir` with `/` separators, then `::` and its full name */
22
23
  export const testId = (file, name) => `${toPosix(file)}::${name}`;
23
24
  /** Works out which of `tests` (files relative to `srcDir`) belong to the group */
24
- export function resolveGroup(group, tests, projectDir, srcDir) {
25
+ export function resolveGroup(group, tests, projectDir, srcDir, projectBrowsers) {
26
+ const browsers = browsersFor({
27
+ group: group.browsers,
28
+ project: projectBrowsers,
29
+ });
25
30
  const found = tests.map((t) => ({ ...t, id: testId(t.file, t.name) }));
26
31
  // Tests picked by hand override src and testMatch
27
32
  if (group.tests) {
@@ -38,6 +43,7 @@ export function resolveGroup(group, tests, projectDir, srcDir) {
38
43
  missing: picked
39
44
  .filter((t) => !known.has(t.id))
40
45
  .map(({ file, title }) => ({ file, title })),
46
+ browsers,
41
47
  };
42
48
  }
43
49
  const base = group.src ? resolve(projectDir, group.src) : srcDir;
@@ -54,11 +60,12 @@ export function resolveGroup(group, tests, projectDir, srcDir) {
54
60
  pattern: [group.src, group.testMatch].filter(Boolean).join(" · ") || pattern,
55
61
  testIds,
56
62
  missing: [],
63
+ browsers,
57
64
  };
58
65
  }
59
66
  /** Resolves every group of the config */
60
67
  export function resolveGroups(config, tests, projectDir, srcDir) {
61
- return (config.groups ?? []).map((group) => resolveGroup(group, tests, projectDir, srcDir));
68
+ return (config.groups ?? []).map((group) => resolveGroup(group, tests, projectDir, srcDir, config.browsers));
62
69
  }
63
70
  /** The group called `name`, or an error naming the groups there are */
64
71
  export function findGroup(groups, name) {
@@ -0,0 +1,31 @@
1
+ import type { SupportedBrowser } from "../types/browser.js";
2
+ import type { RunEvent } from "./reporter.js";
3
+ /** What decides which tests of the found files run, and how the browser starts */
4
+ export interface RunnerOptions {
5
+ /** Spec files to run, absolute or relative to the project folder. Default: every `*.spec.ts` under `srcDir` */
6
+ files?: string[];
7
+ /** Only tests whose full name matches (a string must be contained in it) */
8
+ grep?: string | RegExp;
9
+ /** Only tests with exactly one of these full names (`describe` titles joined with " > ") */
10
+ testNames?: string[];
11
+ /** Only the tests of this group of the config's `groups` (unknown name: an error). Combines with the other filters */
12
+ group?: string;
13
+ /** The browsers to run in, instead of the group's or the project's `browsers` (the command line's `--browser`) */
14
+ browsers?: SupportedBrowser[];
15
+ /**
16
+ * How many tests run at the same time, each in a worker process: a whole number from 1 or a share of the CPUs
17
+ * like `"50%"` (the command line's `--workers`). Beats `SAMURAI_WORKERS` and the config's `workers`.
18
+ * @default the config's `workers`, else half the CPUs
19
+ */
20
+ workers?: number | string;
21
+ /** Run the browser without a window. @default false */
22
+ headless?: boolean;
23
+ /** Remote debugging port of the browser. @default DEFAULT_PORT (9223) */
24
+ port?: number;
25
+ /** Where the report is written; `false` writes none. @default `<project>/result/report.json` */
26
+ reportPath?: string | false;
27
+ /** Receives start and end of the run and of every test, as they happen */
28
+ onEvent?: (event: RunEvent) => void;
29
+ /** Aborting stops the running test (its browser is closed), skips the rest and reports the run as failed */
30
+ signal?: AbortSignal;
31
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import type { SupportedBrowser } from "../types/browser.js";
2
+ import type { SamuraiGroup } from "../types/config.js";
3
+ import type { RegisteredTestCase, TestResult } from "../types/test.js";
4
+ import type { ResolvedGroup } from "./groups.js";
5
+ import type TestReporter from "./reporter.js";
6
+ import type { RunHooks } from "./worker-pool.js";
7
+ import type { WorkItem } from "./worker-protocol.js";
8
+ /** What a run needs from a worker pool: `WorkerPool` itself, or a fake in tests */
9
+ export interface PooledRunner {
10
+ run(items: WorkItem[], hooks?: RunHooks): Promise<TestResult[]>;
11
+ }
12
+ /** A test in a browser, as the run lists it: the work item at the same position says the same in a form a worker reads */
13
+ export interface PlannedTest {
14
+ test: RegisteredTestCase;
15
+ browser: SupportedBrowser;
16
+ }
17
+ /**
18
+ * Lists the run: every selected test in the first browser, then every selected test in the next one. The
19
+ * position in the list is the item's `index`. `serialKey` says which group makes a test run alone.
20
+ */
21
+ export declare function planItems(selected: RegisteredTestCase[], browsers: SupportedBrowser[], serialKey: (test: RegisteredTestCase) => string | undefined): {
22
+ items: WorkItem[];
23
+ planned: PlannedTest[];
24
+ };
25
+ /**
26
+ * Which key makes a test run alone: the first of the config's groups with `workers: 1` that holds it. `groups`
27
+ * and `configGroups` are the same list, resolved and as written.
28
+ */
29
+ export declare function serialKeyOf(groups: ResolvedGroup[], configGroups: SamuraiGroup[] | undefined, srcDir: string): (test: RegisteredTestCase) => string | undefined;
30
+ /**
31
+ * How a run goes: through the pool, in this process, or in this process because the workers could not read the
32
+ * config (it was given as an object; a worker reads `samurai.config.ts` from the project folder)
33
+ */
34
+ export declare function poolMode(input: {
35
+ workers: number;
36
+ items: number;
37
+ inlineConfig: boolean;
38
+ }): "pool" | "in-process" | "inline-config";
39
+ /**
40
+ * Runs the planned items on the pool and reports them as they happen. An item the pool answers without events
41
+ * (a queued one cut short by an abort, or one whose worker died before it began) is reported here at the end.
42
+ */
43
+ export declare function runPooled(pool: PooledRunner, planned: PlannedTest[], items: WorkItem[], reporter: TestReporter, signal?: AbortSignal): Promise<void>;
@@ -0,0 +1,75 @@
1
+ import { idOf } from "./select.js";
2
+ /**
3
+ * Lists the run: every selected test in the first browser, then every selected test in the next one. The
4
+ * position in the list is the item's `index`. `serialKey` says which group makes a test run alone.
5
+ */
6
+ export function planItems(selected, browsers, serialKey) {
7
+ const planned = browsers.flatMap((browser) => selected.map((test) => ({ test, browser })));
8
+ const items = planned.map(({ test, browser }, index) => {
9
+ const key = serialKey(test);
10
+ return {
11
+ index,
12
+ file: test.file,
13
+ testName: test.name,
14
+ browser,
15
+ ...(key !== undefined && { serialKey: key }),
16
+ };
17
+ });
18
+ return { items, planned };
19
+ }
20
+ /**
21
+ * Which key makes a test run alone: the first of the config's groups with `workers: 1` that holds it. `groups`
22
+ * and `configGroups` are the same list, resolved and as written.
23
+ */
24
+ export function serialKeyOf(groups, configGroups, srcDir) {
25
+ const keys = new Map();
26
+ groups.forEach((group, index) => {
27
+ if (configGroups?.[index]?.workers !== 1)
28
+ return;
29
+ for (const id of group.testIds)
30
+ if (!keys.has(id))
31
+ keys.set(id, `group:${group.name}`);
32
+ });
33
+ return (test) => keys.get(idOf(test, srcDir));
34
+ }
35
+ /**
36
+ * How a run goes: through the pool, in this process, or in this process because the workers could not read the
37
+ * config (it was given as an object; a worker reads `samurai.config.ts` from the project folder)
38
+ */
39
+ export function poolMode(input) {
40
+ if (input.workers <= 1 || input.items <= 1)
41
+ return "in-process";
42
+ return input.inlineConfig ? "inline-config" : "pool";
43
+ }
44
+ /**
45
+ * Runs the planned items on the pool and reports them as they happen. An item the pool answers without events
46
+ * (a queued one cut short by an abort, or one whose worker died before it began) is reported here at the end.
47
+ */
48
+ export async function runPooled(pool, planned, items, reporter, signal) {
49
+ reporter.setOrder(planned);
50
+ const started = new Set();
51
+ const ended = new Set();
52
+ const begin = (index) => {
53
+ if (started.has(index))
54
+ return;
55
+ started.add(index);
56
+ const { test, browser } = planned[index];
57
+ reporter.onTestStart(test, browser);
58
+ };
59
+ const end = (index, result) => {
60
+ begin(index);
61
+ ended.add(index);
62
+ const { test, browser } = planned[index];
63
+ reporter.onRemoteEnd(test, browser, result);
64
+ };
65
+ const results = await pool.run(items, {
66
+ ...(signal && { signal }),
67
+ onEvent: (event) => event.type === "test-start"
68
+ ? begin(event.item.index)
69
+ : end(event.item.index, event.result),
70
+ });
71
+ results.forEach((result, index) => {
72
+ if (!ended.has(index))
73
+ end(index, result);
74
+ });
75
+ }
@@ -0,0 +1,2 @@
1
+ /** The browser's debugging port when a run or a recording is given none */
2
+ export declare const DEFAULT_PORT = 9223;
@@ -0,0 +1,2 @@
1
+ /** The browser's debugging port when a run or a recording is given none */
2
+ export const DEFAULT_PORT = 9223;
@@ -3,5 +3,5 @@ import type { RegisteredTestCase, TestCaseBase } from "../types/test.js";
3
3
  export declare function describe(title: string, fn: () => void): void;
4
4
  export declare function test(title: string, fn: TestCaseBase["function"]): void;
5
5
  export declare function registeredTests(): readonly RegisteredTestCase[];
6
- /** Forgets every registered test (tests only) */
6
+ /** Forgets every registered test: a run registers its specs afresh */
7
7
  export declare function clearRegistry(): void;
@@ -26,7 +26,7 @@ export function test(title, fn) {
26
26
  export function registeredTests() {
27
27
  return registered;
28
28
  }
29
- /** Forgets every registered test (tests only) */
29
+ /** Forgets every registered test: a run registers its specs afresh */
30
30
  export function clearRegistry() {
31
31
  registered.length = 0;
32
32
  titleStack.length = 0;
@@ -1,17 +1,24 @@
1
1
  import type { RegisteredTestCase, TestError, TestLogEntry, TestResult, TestSummary } from "../types/test.js";
2
+ import type { SupportedBrowser } from "../types/browser.js";
2
3
  /** What a run reports while it happens */
3
4
  export type RunEvent = {
4
5
  type: "run-start";
5
6
  environment: string;
7
+ /** Every selected test in every browser */
6
8
  total: number;
9
+ browsers: SupportedBrowser[];
10
+ /** How many tests run at the same time; absent when they run one after the other */
11
+ workers?: number;
7
12
  } | {
8
13
  type: "test-start";
9
14
  name: string;
10
15
  file: string;
16
+ browser: SupportedBrowser;
11
17
  } | {
12
18
  type: "test-end";
13
19
  name: string;
14
20
  file: string;
21
+ browser: SupportedBrowser;
15
22
  result: TestResult;
16
23
  } | {
17
24
  type: "run-end";
@@ -20,28 +27,50 @@ export type RunEvent = {
20
27
  export interface ReporterOptions {
21
28
  /** The environment the run uses, recorded in the report */
22
29
  environment: string;
30
+ /** The browsers the run uses, in the order they run; recorded in the report */
31
+ browsers: SupportedBrowser[];
23
32
  /** Where the report is written. @default <cwd>/result/report.json */
24
33
  output?: string | false;
25
34
  /** Called with every event, in order. A throwing listener is ignored */
26
35
  onEvent?: (event: RunEvent) => void;
27
36
  }
28
- export default class TestReporter {
37
+ /** What `runOneTest` reports a test to: the reporter of a run, or a worker that relays it */
38
+ export interface TestSink {
39
+ onTestStart(test: RegisteredTestCase, browser: SupportedBrowser): void;
40
+ onTestEnd(test: RegisteredTestCase, browser: SupportedBrowser, error?: TestError, extras?: TestEndExtras): void;
41
+ }
42
+ /** What a test's report entry lists besides its status */
43
+ export interface TestEndExtras {
44
+ logs?: TestLogEntry[];
45
+ logsDropped?: number;
46
+ screenshots?: {
47
+ name: string;
48
+ path: string;
49
+ }[];
50
+ }
51
+ export default class TestReporter implements TestSink {
29
52
  private testResults;
30
53
  private summary;
31
54
  private environment;
55
+ private browsers;
32
56
  private output;
33
57
  private listener;
58
+ /** Keys of the run's tests in the order the report lists them; unset: the order they started in */
59
+ private order;
34
60
  constructor(options: ReporterOptions);
35
61
  private emit;
36
- onStart(total?: number): void;
62
+ /** Lists the tests of the run in the order the report shows them, whichever order they end in */
63
+ setOrder(tests: {
64
+ test: RegisteredTestCase;
65
+ browser: SupportedBrowser;
66
+ }[]): void;
67
+ onStart(total: number, workers?: number): void;
37
68
  onEnd(): Promise<TestSummary>;
38
- onTestStart(test: RegisteredTestCase): void;
39
- onTestEnd(test: RegisteredTestCase, error?: TestError, extras?: {
40
- logs?: TestLogEntry[];
41
- logsDropped?: number;
42
- screenshots?: {
43
- name: string;
44
- path: string;
45
- }[];
46
- }): void;
69
+ onTestStart(test: RegisteredTestCase, browser: SupportedBrowser): void;
70
+ /**
71
+ * Ends a test that ran in a worker: the worker built the result (masked, with its own duration), this keeps the
72
+ * start time from this process's clock, as the worker's clock has another origin
73
+ */
74
+ onRemoteEnd(test: RegisteredTestCase, browser: SupportedBrowser, result: TestResult): void;
75
+ onTestEnd(test: RegisteredTestCase, browser: SupportedBrowser, error?: TestError, extras?: TestEndExtras): void;
47
76
  }