@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
@@ -1,12 +1,5 @@
1
- export declare const USAGE = "samurai: browser tests over WebDriver BiDi\n\nUsage:\n samurai run [options] Run the tests (the default command)\n samurai list [options] List the tests without running them\n samurai record <spec> Open a browser and record what you do into a spec file\n samurai init [folder] Scaffold a project: config, an example test, .env.example\n samurai flow <command> List, check and run the flows in ./flows (see below)\n samurai browsers [command] List, install and remove the browsers SAMURAI launches\n\nOptions for run and list:\n --env <name> Environment to run against (or SAMURAI_ENV)\n --file <spec> Only this spec file; repeatable\n --grep <text> Only tests whose full name contains the text\n --group <name> Only the tests of this group of the config's groups\n --json Machine-readable output: JSON lines for run, one array for list\n\nOptions for run:\n --timeout <ms> Time one test may take\n --expect-timeout <ms> Time actions and assertions retry\n --headless, --no-headless Run the browser without a window, or with one.\n Default: the config's use.headless, else headless when CI is set\n --port <n> Browser debugging port (default 9223)\n\nOptions for record (also --env, --port, --json, --headless, --no-headless):\n --new <title> Add a test with this title (creates the spec file if needed)\n --test <name|index> Record into this test; not needed when the file has one\n --at <n> Insert from step n (default: after the last step)\n --url <url> Page to start on (default: the environment's baseURL)\n Stop with Ctrl+C or by closing the browser window.\n\n -h, --help Show this help\n -v, --version Show the version\n\nExit code: 0 when every test passed, 1 when a test failed, 2 for a usage or configuration error.\n\nsamurai flow:\n list The flows in ./flows\n check [<flow>...] Check flows without running them\n run <flow>... | --all Run flows; each writes result/flows/<id>.json\n Options for run: --env, --headless, --timeout, --expect-timeout, --port, --json, --allow-unsupported\n Run `samurai flow` alone for the details.\n\nsamurai browsers:\n list The pinned browsers and every build in the cache (the default)\n install [firefox|chrome|all]\n Download the pinned builds (default: all)\n uninstall <name> [--build <id>] | --unused\n\nRun it in the project folder, the one holding samurai.config.ts.\n";
2
- export interface Io {
3
- out: (text: string) => void;
4
- err: (text: string) => void;
5
- /** Whether output goes to a terminal that shows colours */
6
- color: boolean;
7
- env: Partial<NodeJS.ProcessEnv>;
8
- /** Aborts a recording (Ctrl+C) */
9
- signal?: AbortSignal;
10
- }
1
+ import type { Io } from "./io.js";
2
+ import { USAGE } from "./usage.js";
3
+ export { USAGE };
11
4
  /** The command line as `samurai` runs it. Returns the exit code */
12
5
  export declare function main(argv: string[], io: Io): Promise<number>;
package/dist/cli/main.js CHANGED
@@ -1,68 +1,13 @@
1
- import { readFileSync } from "node:fs";
2
1
  import { parseCommandLine, parseRunnerFlags, parseRunOverrides, } from "./args.js";
3
- import { recordSpec } from "../recorder/index.js";
4
2
  import { listTests, runTests } from "../runner/run.js";
5
- import { stepToSource } from "../steps/index.js";
6
3
  import { ConsoleReporter } from "./console-reporter.js";
7
4
  import { browsersCommand } from "./browsers.js";
8
5
  import { flowCommand } from "./flow.js";
9
6
  import { initProject } from "./init.js";
10
- export const USAGE = `samurai: browser tests over WebDriver BiDi
11
-
12
- Usage:
13
- samurai run [options] Run the tests (the default command)
14
- samurai list [options] List the tests without running them
15
- samurai record <spec> Open a browser and record what you do into a spec file
16
- samurai init [folder] Scaffold a project: config, an example test, .env.example
17
- samurai flow <command> List, check and run the flows in ./flows (see below)
18
- samurai browsers [command] List, install and remove the browsers SAMURAI launches
19
-
20
- Options for run and list:
21
- --env <name> Environment to run against (or SAMURAI_ENV)
22
- --file <spec> Only this spec file; repeatable
23
- --grep <text> Only tests whose full name contains the text
24
- --group <name> Only the tests of this group of the config's groups
25
- --json Machine-readable output: JSON lines for run, one array for list
26
-
27
- Options for run:
28
- --timeout <ms> Time one test may take
29
- --expect-timeout <ms> Time actions and assertions retry
30
- --headless, --no-headless Run the browser without a window, or with one.
31
- Default: the config's use.headless, else headless when CI is set
32
- --port <n> Browser debugging port (default 9223)
33
-
34
- Options for record (also --env, --port, --json, --headless, --no-headless):
35
- --new <title> Add a test with this title (creates the spec file if needed)
36
- --test <name|index> Record into this test; not needed when the file has one
37
- --at <n> Insert from step n (default: after the last step)
38
- --url <url> Page to start on (default: the environment's baseURL)
39
- Stop with Ctrl+C or by closing the browser window.
40
-
41
- -h, --help Show this help
42
- -v, --version Show the version
43
-
44
- Exit code: 0 when every test passed, 1 when a test failed, 2 for a usage or configuration error.
45
-
46
- samurai flow:
47
- list The flows in ./flows
48
- check [<flow>...] Check flows without running them
49
- run <flow>... | --all Run flows; each writes result/flows/<id>.json
50
- Options for run: --env, --headless, --timeout, --expect-timeout, --port, --json, --allow-unsupported
51
- Run \`samurai flow\` alone for the details.
52
-
53
- samurai browsers:
54
- list The pinned browsers and every build in the cache (the default)
55
- install [firefox|chrome|all]
56
- Download the pinned builds (default: all)
57
- uninstall <name> [--build <id>] | --unused
58
-
59
- Run it in the project folder, the one holding samurai.config.ts.
60
- `;
61
- function version() {
62
- const manifest = new URL("../../package.json", import.meta.url);
63
- return JSON.parse(readFileSync(manifest, "utf8"))
64
- .version;
65
- }
7
+ import { recordCommand } from "./record.js";
8
+ import { USAGE } from "./usage.js";
9
+ import { frameworkVersion } from "./version.js";
10
+ export { USAGE };
66
11
  /** The command line as `samurai` runs it. Returns the exit code */
67
12
  export async function main(argv, io) {
68
13
  try {
@@ -75,7 +20,7 @@ export async function main(argv, io) {
75
20
  return 0;
76
21
  }
77
22
  if (values.version) {
78
- io.out(`${version()}\n`);
23
+ io.out(`${frameworkVersion()}\n`);
79
24
  return 0;
80
25
  }
81
26
  if (command === "flow")
@@ -91,93 +36,22 @@ export async function main(argv, io) {
91
36
  io.out("\nNext: install the dependencies, then `samurai browsers install firefox`, then `samurai run`.\n");
92
37
  return 0;
93
38
  }
94
- if (command === "record") {
95
- const file = positionals[0];
96
- if (!file)
97
- throw new Error("Which spec? Usage: samurai record <spec> [--new <title>]");
98
- const at = values.at === undefined ? undefined : Number(values.at);
99
- if (at !== undefined && (!Number.isInteger(at) || at < 0))
100
- throw new Error(`--at must be a step number, got "${values.at}"`);
101
- const flags = parseRunnerFlags(rest);
102
- const test = values.test === undefined
103
- ? undefined
104
- : /^\d+$/.test(values.test)
105
- ? Number(values.test)
106
- : values.test;
107
- let count = 0;
108
- let state = "recording";
109
- let toolbarWarned = false;
110
- const result = await recordSpec({
111
- ...parseRunOverrides(rest, io.env),
112
- file,
113
- ...(test !== undefined && { test }),
114
- ...(values.new !== undefined && { create: values.new }),
115
- ...(at !== undefined && { at }),
116
- ...(values.url !== undefined && { url: values.url }),
117
- ...(flags.headless !== undefined && { headless: flags.headless }),
118
- ...(flags.port !== undefined && { port: flags.port }),
119
- ...(io.signal && { signal: io.signal }),
120
- onStarted: () => {
121
- if (!values.json)
122
- io.out("Recording. Alt+click an element to assert on it. Press Stop in the toolbar, Ctrl+C or close the window to stop.\n");
123
- },
124
- onStatus: (status) => {
125
- if (values.json)
126
- return;
127
- if (!status.toolbar && !toolbarWarned) {
128
- toolbarWarned = true;
129
- io.out("The toolbar couldn't load on this page. Alt+click still records assertions.\n");
130
- }
131
- if (status.state !== state) {
132
- state = status.state;
133
- io.out(state === "paused" ? "Paused\n" : "Recording\n");
134
- }
135
- },
136
- onMissingFile: (_file, text) => {
137
- if (!values.json)
138
- io.out(`${text}\n`);
139
- },
140
- onEvent: (event) => {
141
- if (values.json)
142
- return void io.out(`${JSON.stringify(event)}\n`);
143
- if (event.op === "remove") {
144
- count--;
145
- return void io.out(` - step ${event.index + 1} (the click that opened the file picker)\n`);
146
- }
147
- io.out(` ${event.op === "insert" ? "+" : "~"} ${stepToSource(event.step)}\n`);
148
- if (event.op === "insert")
149
- count++;
150
- },
151
- });
152
- if (!values.json) {
153
- io.out(`\nRecorded ${count} ${count === 1 ? "step" : "steps"} into "${result.test}" in ${result.file}\n`);
154
- if (result.unsetSecrets.length > 0) {
155
- io.out(`Set ${result.unsetSecrets.map((name) => `SAMURAI_SECRET_${name}`).join(", ")} (environment variable or .env.<environment>) before running it.\n`);
156
- }
157
- if (result.missingFiles.length > 0) {
158
- io.out(`Add ${result.missingFiles.map((f) => f.path).join(", ")} before running it (or use generatedFile).\n`);
159
- }
160
- const hasFiles = result.files.length + result.missingFiles.length > 0;
161
- const folder = result.filesFolder === "" ? "." : result.filesFolder;
162
- if (hasFiles && result.filesIgnored)
163
- io.out(`${folder}/ is ignored by git; CI won't have these files.\n`);
164
- if (hasFiles && result.firstFiles)
165
- io.out(`Recorded files are stored in ${folder}/ and committed with your tests.\n`);
166
- }
167
- return 0;
168
- }
39
+ if (command === "record")
40
+ return await recordCommand({ values, positionals }, io);
169
41
  if (command !== "run" && command !== "list") {
170
42
  io.err(`Unknown command "${command}"\n\n${USAGE}`);
171
43
  return 2;
172
44
  }
173
- const flags = parseRunnerFlags(rest);
45
+ const flags = parseRunnerFlags(values);
174
46
  const options = {
175
- ...parseRunOverrides(rest, io.env),
47
+ ...parseRunOverrides(values, io.env),
176
48
  ...(flags.headless !== undefined && { headless: flags.headless }),
177
49
  ...(flags.port !== undefined && { port: flags.port }),
50
+ ...(flags.workers !== undefined && { workers: flags.workers }),
178
51
  ...(flags.grep !== undefined && { grep: flags.grep }),
179
52
  ...(flags.group !== undefined && { group: flags.group }),
180
53
  ...(flags.files && { files: flags.files }),
54
+ ...(flags.browsers && { browsers: flags.browsers }),
181
55
  };
182
56
  if (command === "list") {
183
57
  const tests = await listTests(options);
@@ -0,0 +1,2 @@
1
+ /** Wraps text in an ANSI colour code when `color` is on, and leaves it as it is when not */
2
+ export declare function painter(color: boolean): (code: number, text: string) => string;
@@ -0,0 +1,4 @@
1
+ /** Wraps text in an ANSI colour code when `color` is on, and leaves it as it is when not */
2
+ export function painter(color) {
3
+ return (code, text) => (color ? `\u001b[${code}m${text}\u001b[0m` : text);
4
+ }
@@ -0,0 +1,4 @@
1
+ import { type ParsedCommandLine } from "./args.js";
2
+ import type { Io } from "./io.js";
3
+ /** `samurai record <spec>`: records into a spec file and prints what happened. Returns the exit code */
4
+ export declare function recordCommand({ values, positionals }: ParsedCommandLine, io: Io): Promise<number>;
@@ -0,0 +1,93 @@
1
+ import { recordSpec } from "../recorder/index.js";
2
+ import { stepToSource } from "../steps/index.js";
3
+ import { parseRunnerFlags, parseRunOverrides, } from "./args.js";
4
+ /** Prints a recording for a person, or as JSON lines, while it happens; counts the steps written */
5
+ function recordingPrinter(io, json) {
6
+ let count = 0;
7
+ let state = "recording";
8
+ let toolbarWarned = false;
9
+ const handlers = {
10
+ onStarted: () => {
11
+ if (!json)
12
+ io.out("Recording. Alt+click an element to assert on it. Press Stop in the toolbar, Ctrl+C or close the window to stop.\n");
13
+ },
14
+ onStatus: (status) => {
15
+ if (json)
16
+ return;
17
+ if (!status.toolbar && !toolbarWarned) {
18
+ toolbarWarned = true;
19
+ io.out("The toolbar couldn't load on this page. Alt+click still records assertions.\n");
20
+ }
21
+ if (status.state !== state) {
22
+ state = status.state;
23
+ io.out(state === "paused" ? "Paused\n" : "Recording\n");
24
+ }
25
+ },
26
+ onMissingFile: (_file, text) => {
27
+ if (!json)
28
+ io.out(`${text}\n`);
29
+ },
30
+ onEvent: (event) => {
31
+ if (json)
32
+ return void io.out(`${JSON.stringify(event)}\n`);
33
+ if (event.op === "remove") {
34
+ count--;
35
+ return void io.out(` - step ${event.index + 1} (the click that opened the file picker)\n`);
36
+ }
37
+ io.out(` ${event.op === "insert" ? "+" : "~"} ${stepToSource(event.step)}\n`);
38
+ if (event.op === "insert")
39
+ count++;
40
+ },
41
+ };
42
+ return { handlers, steps: () => count };
43
+ }
44
+ /** What is left to do after a recording: the steps written, secrets to set, files to add, notes on the files folder */
45
+ function summarise(io, result, steps) {
46
+ io.out(`\nRecorded ${steps} ${steps === 1 ? "step" : "steps"} into "${result.test}" in ${result.file}\n`);
47
+ if (result.unsetSecrets.length > 0) {
48
+ io.out(`Set ${result.unsetSecrets.map((name) => `SAMURAI_SECRET_${name}`).join(", ")} (environment variable or .env.<environment>) before running it.\n`);
49
+ }
50
+ if (result.missingFiles.length > 0) {
51
+ io.out(`Add ${result.missingFiles.map((f) => f.path).join(", ")} before running it (or use generatedFile).\n`);
52
+ }
53
+ const hasFiles = result.files.length + result.missingFiles.length > 0;
54
+ const folder = result.filesFolder === "" ? "." : result.filesFolder;
55
+ if (hasFiles && result.filesIgnored)
56
+ io.out(`${folder}/ is ignored by git; CI won't have these files.\n`);
57
+ if (hasFiles && result.firstFiles)
58
+ io.out(`Recorded files are stored in ${folder}/ and committed with your tests.\n`);
59
+ }
60
+ /** `samurai record <spec>`: records into a spec file and prints what happened. Returns the exit code */
61
+ export async function recordCommand({ values, positionals }, io) {
62
+ const file = positionals[0];
63
+ if (!file)
64
+ throw new Error("Which spec? Usage: samurai record <spec> [--new <title>]");
65
+ const at = values.at === undefined ? undefined : Number(values.at);
66
+ if (at !== undefined && (!Number.isInteger(at) || at < 0))
67
+ throw new Error(`--at must be a step number, got "${values.at}"`);
68
+ const flags = parseRunnerFlags(values);
69
+ if (flags.browsers && flags.browsers.length > 1)
70
+ throw new Error("--browser: a recording uses one browser");
71
+ const test = values.test === undefined
72
+ ? undefined
73
+ : /^\d+$/.test(values.test)
74
+ ? Number(values.test)
75
+ : values.test;
76
+ const printer = recordingPrinter(io, values.json === true);
77
+ const result = await recordSpec({
78
+ ...parseRunOverrides(values, io.env),
79
+ file,
80
+ ...(test !== undefined && { test }),
81
+ ...(values.new !== undefined && { create: values.new }),
82
+ ...(at !== undefined && { at }),
83
+ ...(values.url !== undefined && { url: values.url }),
84
+ ...(flags.headless !== undefined && { headless: flags.headless }),
85
+ ...(flags.port !== undefined && { port: flags.port }),
86
+ ...(flags.browsers && { browser: flags.browsers[0] }),
87
+ ...(io.signal && { signal: io.signal }),
88
+ ...printer.handlers,
89
+ });
90
+ if (!values.json)
91
+ summarise(io, result, printer.steps());
92
+ return 0;
93
+ }
@@ -0,0 +1 @@
1
+ export declare const USAGE = "samurai: browser tests over WebDriver BiDi\n\nUsage:\n samurai run [options] Run the tests (the default command)\n samurai list [options] List the tests without running them\n samurai record <spec> Open a browser and record what you do into a spec file\n samurai init [folder] Scaffold a project: config, an example test, .env.example\n samurai flow <command> List, check and run the flows in ./flows (see below)\n samurai browsers [command] List, install and remove the browsers SAMURAI launches\n\nOptions for run and list:\n --env <name> Environment to run against (or SAMURAI_ENV)\n --file <spec> Only this spec file; repeatable\n --grep <text> Only tests whose full name contains the text\n --group <name> Only the tests of this group of the config's groups\n --browser <name> Run in this browser (firefox or chrome) instead of the config's\n browsers; repeat it for more than one\n --json Machine-readable output: JSON lines for run, one array for list\n\nOptions for run:\n --timeout <ms> Time one test may take\n --expect-timeout <ms> Time actions and assertions retry\n --headless, --no-headless Run the browser without a window, or with one.\n Default: the config's use.headless, else headless when CI is set\n --port <n> Browser debugging port (default 9223)\n --workers <n|n%> Tests that run at the same time, each in a worker process: a number\n from 1, or a share of the CPUs like 50%. Default: the config's\n workers (or SAMURAI_WORKERS), else half the CPUs\n\nOptions for record (also --env, --port, --json, --headless, --no-headless):\n --browser <name> Record in this browser (default: the first of the config's browsers)\n --new <title> Add a test with this title (creates the spec file if needed)\n --test <name|index> Record into this test; not needed when the file has one\n --at <n> Insert from step n (default: after the last step)\n --url <url> Page to start on (default: the environment's baseURL)\n Stop with Ctrl+C or by closing the browser window.\n\n -h, --help Show this help\n -v, --version Show the version\n\nExit code: 0 when every test passed, 1 when a test failed, 2 for a usage or configuration error.\n\nsamurai flow:\n list The flows in ./flows\n check [<flow>...] Check flows without running them\n run <flow>... | --all Run flows; each writes result/flows/<id>.json\n Options for run: --env, --headless, --timeout, --expect-timeout, --port, --json, --allow-unsupported\n Run `samurai flow` alone for the details.\n\nsamurai browsers:\n list The pinned browsers and every build in the cache (the default)\n install [firefox|chrome|all]\n Download the pinned builds (default: all)\n uninstall <name> [--build <id>] | --unused\n\nRun it in the project folder, the one holding samurai.config.ts.\n";
@@ -0,0 +1,58 @@
1
+ import { DEFAULT_PORT } from "../runner/run.js";
2
+ export const USAGE = `samurai: browser tests over WebDriver BiDi
3
+
4
+ Usage:
5
+ samurai run [options] Run the tests (the default command)
6
+ samurai list [options] List the tests without running them
7
+ samurai record <spec> Open a browser and record what you do into a spec file
8
+ samurai init [folder] Scaffold a project: config, an example test, .env.example
9
+ samurai flow <command> List, check and run the flows in ./flows (see below)
10
+ samurai browsers [command] List, install and remove the browsers SAMURAI launches
11
+
12
+ Options for run and list:
13
+ --env <name> Environment to run against (or SAMURAI_ENV)
14
+ --file <spec> Only this spec file; repeatable
15
+ --grep <text> Only tests whose full name contains the text
16
+ --group <name> Only the tests of this group of the config's groups
17
+ --browser <name> Run in this browser (firefox or chrome) instead of the config's
18
+ browsers; repeat it for more than one
19
+ --json Machine-readable output: JSON lines for run, one array for list
20
+
21
+ Options for run:
22
+ --timeout <ms> Time one test may take
23
+ --expect-timeout <ms> Time actions and assertions retry
24
+ --headless, --no-headless Run the browser without a window, or with one.
25
+ Default: the config's use.headless, else headless when CI is set
26
+ --port <n> Browser debugging port (default ${DEFAULT_PORT})
27
+ --workers <n|n%> Tests that run at the same time, each in a worker process: a number
28
+ from 1, or a share of the CPUs like 50%. Default: the config's
29
+ workers (or SAMURAI_WORKERS), else half the CPUs
30
+
31
+ Options for record (also --env, --port, --json, --headless, --no-headless):
32
+ --browser <name> Record in this browser (default: the first of the config's browsers)
33
+ --new <title> Add a test with this title (creates the spec file if needed)
34
+ --test <name|index> Record into this test; not needed when the file has one
35
+ --at <n> Insert from step n (default: after the last step)
36
+ --url <url> Page to start on (default: the environment's baseURL)
37
+ Stop with Ctrl+C or by closing the browser window.
38
+
39
+ -h, --help Show this help
40
+ -v, --version Show the version
41
+
42
+ Exit code: 0 when every test passed, 1 when a test failed, 2 for a usage or configuration error.
43
+
44
+ samurai flow:
45
+ list The flows in ./flows
46
+ check [<flow>...] Check flows without running them
47
+ run <flow>... | --all Run flows; each writes result/flows/<id>.json
48
+ Options for run: --env, --headless, --timeout, --expect-timeout, --port, --json, --allow-unsupported
49
+ Run \`samurai flow\` alone for the details.
50
+
51
+ samurai browsers:
52
+ list The pinned browsers and every build in the cache (the default)
53
+ install [firefox|chrome|all]
54
+ Download the pinned builds (default: all)
55
+ uninstall <name> [--build <id>] | --unused
56
+
57
+ Run it in the project folder, the one holding samurai.config.ts.
58
+ `;
@@ -0,0 +1,2 @@
1
+ /** The version of this package */
2
+ export declare function frameworkVersion(): string;
@@ -0,0 +1,7 @@
1
+ import { readFileSync } from "node:fs";
2
+ /** The version of this package */
3
+ export function frameworkVersion() {
4
+ const manifest = new URL("../../package.json", import.meta.url);
5
+ return JSON.parse(readFileSync(manifest, "utf8"))
6
+ .version;
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { SupportedBrowser } from "../types/browser.js";
2
+ import type { SamuraiTestConfig } from "../types/config.js";
3
+ /** Every browser a config may name, in the order the messages list them */
4
+ export declare const BROWSER_NAMES: readonly SupportedBrowser[];
5
+ /** Throws when `list` is not a non-empty list of distinct browser names; `where` names the key in the message */
6
+ export declare function checkBrowserList(list: unknown, where: string): void;
7
+ /**
8
+ * Checks the browser keys of a config: the removed `browser`, and `browsers` on the project and on each group.
9
+ * Throws an Error that says what to write instead.
10
+ */
11
+ export declare function validateBrowserConfig(config: SamuraiTestConfig): void;
@@ -0,0 +1,32 @@
1
+ /** Every browser a config may name, in the order the messages list them */
2
+ export const BROWSER_NAMES = ["firefox", "chrome"];
3
+ const isBrowser = (value) => typeof value === "string" &&
4
+ BROWSER_NAMES.includes(value);
5
+ /** Throws when `list` is not a non-empty list of distinct browser names; `where` names the key in the message */
6
+ export function checkBrowserList(list, where) {
7
+ if (!Array.isArray(list) || list.length === 0)
8
+ throw new Error(`${where} must list at least one browser.`);
9
+ const seen = new Set();
10
+ for (const name of list) {
11
+ if (!isBrowser(name))
12
+ throw new Error(`Unknown browser "${String(name)}" in ${where}. Choose ${BROWSER_NAMES.join(" or ")}.`);
13
+ if (seen.has(name))
14
+ throw new Error(`Browser "${name}" is listed twice in ${where}.`);
15
+ seen.add(name);
16
+ }
17
+ }
18
+ /**
19
+ * Checks the browser keys of a config: the removed `browser`, and `browsers` on the project and on each group.
20
+ * Throws an Error that says what to write instead.
21
+ */
22
+ export function validateBrowserConfig(config) {
23
+ const legacy = config.browser;
24
+ if (legacy !== undefined)
25
+ throw new Error(`"browser" was replaced by "browsers": use browsers: [${JSON.stringify(legacy)}]`);
26
+ if (config.browsers !== undefined)
27
+ checkBrowserList(config.browsers, "browsers");
28
+ config.groups?.forEach((group, index) => {
29
+ if (group.browsers !== undefined)
30
+ checkBrowserList(group.browsers, `groups[${index}].browsers`);
31
+ });
32
+ }
@@ -1,6 +1,8 @@
1
1
  import path from "path";
2
2
  import logger from "../logger/index.js";
3
3
  import { pathToFileURL } from "url";
4
+ import { validateBrowserConfig } from "./browsers.js";
5
+ import { validateWorkersConfig } from "./workers.js";
4
6
  let activeProject;
5
7
  /** Makes `project` the one config, secrets, reports and downloads are resolved for; `undefined` ends it */
6
8
  export function setActiveProject(project) {
@@ -22,14 +24,19 @@ export function browsersDir() {
22
24
  }
23
25
  /** The whole `samurai.config.ts` of the project (the working directory unless a project is active) */
24
26
  export async function loadConfig() {
25
- if (activeProject?.config)
27
+ if (activeProject?.config) {
28
+ validateBrowserConfig(activeProject.config);
29
+ validateWorkersConfig(activeProject.config);
26
30
  return activeProject.config;
31
+ }
27
32
  const configUrl = path.join(projectDir(), "samurai.config.ts");
28
33
  logger.debug("Reading config from: %s", configUrl);
29
34
  const config = (await import(pathToFileURL(configUrl).href));
30
35
  if (!config.default) {
31
36
  throw new Error("samurai.config.ts does not export defineConfig as default!");
32
37
  }
38
+ validateBrowserConfig(config.default);
39
+ validateWorkersConfig(config.default);
33
40
  return config.default;
34
41
  }
35
42
  /** A project's config without keeping the project active (`withProject` is one at a time): the given one, else its samurai.config.ts */
@@ -1,7 +1,7 @@
1
1
  export declare const MASK = "\u2022\u2022\u2022\u2022";
2
2
  /** Registers a secret value; every later maskText/maskDeep/log line hides it */
3
3
  export declare function registerSecret(name: string, value: string): void;
4
- /** Forgets every registered secret (tests only) */
4
+ /** Forgets every registered secret: a run starts with none */
5
5
  export declare function resetSecrets(): void;
6
6
  export declare function isShortSecret(value: string): boolean;
7
7
  export declare function maskText(text: string): string;
@@ -12,7 +12,7 @@ export function registerSecret(name, value) {
12
12
  secrets.set(name, value);
13
13
  rebuild();
14
14
  }
15
- /** Forgets every registered secret (tests only) */
15
+ /** Forgets every registered secret: a run starts with none */
16
16
  export function resetSecrets() {
17
17
  secrets.clear();
18
18
  patterns = [];
@@ -25,7 +25,12 @@ function escapeRegExp(value) {
25
25
  }
26
26
  /** A value plus its JSON-escaped, inspect-escaped and percent-encoded spellings */
27
27
  function variants(value) {
28
- const forms = [value, JSON.stringify(value).slice(1, -1), inspect(value).slice(1, -1), encodeURIComponent(value)];
28
+ const forms = [
29
+ value,
30
+ JSON.stringify(value).slice(1, -1),
31
+ inspect(value).slice(1, -1),
32
+ encodeURIComponent(value),
33
+ ];
29
34
  return forms.filter((form) => form !== "");
30
35
  }
31
36
  function rebuild() {
@@ -23,9 +23,9 @@ export interface RunSettings {
23
23
  export declare class UnknownEnvironmentError extends Error {
24
24
  constructor(message: string);
25
25
  }
26
- /** Picks the environment and applies precedence: run override > environment > project > built-in default */
27
26
  /** True when the `CI` environment variable says this is a CI run (set, and not "", "0" or "false") */
28
27
  export declare function isCI(env?: Partial<NodeJS.ProcessEnv>): boolean;
28
+ /** Picks the environment and applies precedence: run override > environment > project > built-in default */
29
29
  export declare function resolveRunSettings(config: SamuraiTestConfig, overrides?: RunOverrides,
30
30
  /** Where `CI` is read from */
31
31
  env?: Partial<NodeJS.ProcessEnv>): RunSettings;
@@ -33,4 +33,3 @@ env?: Partial<NodeJS.ProcessEnv>): RunSettings;
33
33
  export declare function setRunSettings(settings: RunSettings | undefined): void;
34
34
  /** The active run's settings, or undefined outside a run (unit and browser tests) */
35
35
  export declare function peekRunSettings(): RunSettings | undefined;
36
- export declare function getRunSettings(): RunSettings;
@@ -8,12 +8,12 @@ export class UnknownEnvironmentError extends Error {
8
8
  this.name = "UnknownEnvironmentError";
9
9
  }
10
10
  }
11
- /** Picks the environment and applies precedence: run override > environment > project > built-in default */
12
11
  /** True when the `CI` environment variable says this is a CI run (set, and not "", "0" or "false") */
13
12
  export function isCI(env = process.env) {
14
13
  const value = env.CI?.trim().toLowerCase();
15
14
  return (value !== undefined && value !== "" && value !== "0" && value !== "false");
16
15
  }
16
+ /** Picks the environment and applies precedence: run override > environment > project > built-in default */
17
17
  export function resolveRunSettings(config, overrides = {},
18
18
  /** Where `CI` is read from */
19
19
  env = process.env) {
@@ -65,8 +65,3 @@ export function setRunSettings(settings) {
65
65
  export function peekRunSettings() {
66
66
  return active;
67
67
  }
68
- export function getRunSettings() {
69
- if (!active)
70
- throw new Error("No test run is active; run settings are set when the runner starts");
71
- return active;
72
- }
@@ -0,0 +1,2 @@
1
+ /** Per-call timeout, else the active run's expect timeout, else config `expect.timeout`, else 5000 */
2
+ export declare function resolveTimeout(perCall?: number): Promise<number>;
@@ -0,0 +1,13 @@
1
+ import { readConfig } from "./config.js";
2
+ import { peekRunSettings } from "./run-settings.js";
3
+ /** The retry time of actions and assertions when nothing sets one (ms) */
4
+ const DEFAULT_EXPECT_TIMEOUT = 5000;
5
+ /** Per-call timeout, else the active run's expect timeout, else config `expect.timeout`, else 5000 */
6
+ export async function resolveTimeout(perCall) {
7
+ if (perCall !== undefined)
8
+ return perCall;
9
+ const run = peekRunSettings();
10
+ if (run)
11
+ return run.expectTimeout;
12
+ return (await readConfig("expect"))?.timeout ?? DEFAULT_EXPECT_TIMEOUT;
13
+ }
@@ -0,0 +1,14 @@
1
+ import type { SamuraiTestConfig } from "../types/config.js";
2
+ /** A number of workers, or a share of the CPUs */
3
+ export type ParsedWorkers = {
4
+ count: number;
5
+ } | {
6
+ percent: number;
7
+ };
8
+ /**
9
+ * Reads a `workers` value: a whole number from 1, as a number or as text, or a percentage like `50%` when
10
+ * `percentages` is on. `where` names the source (`--workers`, `SAMURAI_WORKERS`, a config key) in the error.
11
+ */
12
+ export declare function parseWorkers(value: unknown, where: string, percentages?: boolean): ParsedWorkers;
13
+ /** Checks the `workers` keys of a config: the project's and each group's. Throws an Error that names the key */
14
+ export declare function validateWorkersConfig(config: SamuraiTestConfig): void;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Reads a `workers` value: a whole number from 1, as a number or as text, or a percentage like `50%` when
3
+ * `percentages` is on. `where` names the source (`--workers`, `SAMURAI_WORKERS`, a config key) in the error.
4
+ */
5
+ export function parseWorkers(value, where, percentages = true) {
6
+ if (typeof value === "number" && Number.isInteger(value) && value >= 1)
7
+ return { count: value };
8
+ if (typeof value === "string") {
9
+ const text = value.trim();
10
+ if (/^\d+$/.test(text) && Number(text) >= 1)
11
+ return { count: Number(text) };
12
+ const share = /^(\d+)%$/.exec(text);
13
+ if (percentages && share) {
14
+ const percent = Number(share[1]);
15
+ if (percent >= 1 && percent <= 100)
16
+ return { percent };
17
+ }
18
+ }
19
+ throw new Error(`${where} must be a whole number from 1${percentages ? " or a percentage like 50%" : ""}, got "${String(value)}"`);
20
+ }
21
+ /** Checks the `workers` keys of a config: the project's and each group's. Throws an Error that names the key */
22
+ export function validateWorkersConfig(config) {
23
+ if (config.workers !== undefined)
24
+ parseWorkers(config.workers, "workers");
25
+ config.groups?.forEach((group, index) => {
26
+ if (group.workers !== undefined)
27
+ parseWorkers(group.workers, `groups[${index}].workers`, false);
28
+ });
29
+ }
@@ -0,0 +1,14 @@
1
+ import type { ExpressionContext } from "../core/index.js";
2
+ /** A branch of a Parallel node: its own copy of the variables and node outputs, and the variables as they were when it forked */
3
+ export interface Fork {
4
+ context: ExpressionContext;
5
+ base: Record<string, unknown>;
6
+ }
7
+ export declare function forkContext(context: ExpressionContext): Fork;
8
+ /**
9
+ * Takes what the branches wrote into `into`: every node output, and every variable a branch changed. A variable
10
+ * two branches changed to different values is a clash and is not taken; the first clash is returned.
11
+ */
12
+ export declare function mergeForks(into: ExpressionContext, forks: readonly Fork[]): {
13
+ clash?: string;
14
+ };