@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
@@ -46,7 +46,7 @@ declare const PAPER: {
46
46
  readonly height: 14.8;
47
47
  };
48
48
  };
49
- export type PaperFormat = keyof typeof PAPER;
49
+ type PaperFormat = keyof typeof PAPER;
50
50
  export interface PdfOptions {
51
51
  /** Also write the PDF here (folders are created) */
52
52
  path?: string;
@@ -0,0 +1,16 @@
1
+ import { type BiDiConnector } from "../transport/bidi-connection.js";
2
+ /** session.new got no reply in time */
3
+ export declare class BrowserStartTimeoutError extends Error {
4
+ constructor(label: string, timeout: number);
5
+ }
6
+ /** Sends session.new with its own, longer timeout; `label` names the browser in the timeout error */
7
+ export declare function startSession(connector: Pick<BiDiConnector, "send">, label: string, commandTimeout: number | undefined): Promise<void>;
8
+ /**
9
+ * Stops what a failed launch started. A browser left running holds the profile (the next launch
10
+ * fails with "already open") and keeps the test process alive.
11
+ */
12
+ export declare function abandonLaunch(browserProc: {
13
+ kill(): boolean;
14
+ }, biDiConnector: {
15
+ kill(): void;
16
+ } | undefined): void;
@@ -0,0 +1,32 @@
1
+ import { DEFAULT_COMMAND_TIMEOUT, } from "../transport/bidi-connection.js";
2
+ import { BiDiError } from "../transport/bidi-error.js";
3
+ import { SESSION_CAPABILITIES } from "./session-capabilities.js";
4
+ /** session.new waits at least this long (ms): the first start after an install can take a while (macOS checks the new app) */
5
+ const SESSION_START_TIMEOUT = 120_000;
6
+ /** session.new got no reply in time */
7
+ export class BrowserStartTimeoutError extends Error {
8
+ constructor(label, timeout) {
9
+ super(`${label} did not finish starting within ${Math.round(timeout / 1000)}s. The first start after an install can be slow; run again.`);
10
+ this.name = "BrowserStartTimeoutError";
11
+ }
12
+ }
13
+ /** Sends session.new with its own, longer timeout; `label` names the browser in the timeout error */
14
+ export async function startSession(connector, label, commandTimeout) {
15
+ const timeout = Math.max(commandTimeout ?? DEFAULT_COMMAND_TIMEOUT, SESSION_START_TIMEOUT);
16
+ try {
17
+ await connector.send("session.new", { capabilities: SESSION_CAPABILITIES }, { timeout });
18
+ }
19
+ catch (err) {
20
+ if (err instanceof BiDiError && err.code === "timeout")
21
+ throw new BrowserStartTimeoutError(label, timeout);
22
+ throw err;
23
+ }
24
+ }
25
+ /**
26
+ * Stops what a failed launch started. A browser left running holds the profile (the next launch
27
+ * fails with "already open") and keeps the test process alive.
28
+ */
29
+ export function abandonLaunch(browserProc, biDiConnector) {
30
+ biDiConnector?.kill();
31
+ browserProc.kill();
32
+ }
@@ -0,0 +1,19 @@
1
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
2
+ /** The size a page gets unless config `use.viewport` says otherwise */
3
+ export declare const DEFAULT_VIEWPORT: {
4
+ width: number;
5
+ height: number;
6
+ };
7
+ /** A viewport as `page.setViewport` takes it: a size and optionally a device pixel ratio */
8
+ export interface Viewport {
9
+ width: number;
10
+ height: number;
11
+ devicePixelRatio?: number;
12
+ }
13
+ /** Throws RangeError when the size or the ratio is not usable, before anything is sent */
14
+ export declare function validateViewport({ width, height, devicePixelRatio, }: Viewport): void;
15
+ /**
16
+ * Gives the context this size (and optionally this device pixel ratio); `null` puts the browser's own size back
17
+ * and waits until the page has really got it.
18
+ */
19
+ export declare function applyViewport(connector: BiDiConnector, context: string, viewport: Viewport | null): Promise<void>;
@@ -0,0 +1,72 @@
1
+ import { callFunction } from "../script/call-function.js";
2
+ /** The size a page gets unless config `use.viewport` says otherwise */
3
+ export const DEFAULT_VIEWPORT = { width: 1280, height: 720 };
4
+ /** Time (ms) without a `resize` event after which a viewport reset counts as done */
5
+ const RESIZE_QUIET_TIME = 50;
6
+ /** Time (ms) a viewport reset waits for a `resize` event that may never come (the size did not change) */
7
+ const RESIZE_MAX_WAIT = 300;
8
+ /** Throws RangeError when the size or the ratio is not usable, before anything is sent */
9
+ export function validateViewport({ width, height, devicePixelRatio, }) {
10
+ for (const [name, value] of [
11
+ ["width", width],
12
+ ["height", height],
13
+ ]) {
14
+ if (!Number.isInteger(value) || value <= 0) {
15
+ throw new RangeError(`setViewport(): ${name} must be a positive integer, got ${value}`);
16
+ }
17
+ }
18
+ if (devicePixelRatio !== undefined &&
19
+ !(devicePixelRatio > 0 && Number.isFinite(devicePixelRatio))) {
20
+ throw new RangeError(`setViewport(): devicePixelRatio must be a positive number, got ${devicePixelRatio}`);
21
+ }
22
+ }
23
+ /**
24
+ * Gives the context this size (and optionally this device pixel ratio); `null` puts the browser's own size back
25
+ * and waits until the page has really got it.
26
+ */
27
+ export async function applyViewport(connector, context, viewport) {
28
+ if (viewport === null) {
29
+ await untilResized(connector, context, () => connector.send("browsingContext.setViewport", {
30
+ context,
31
+ viewport: null,
32
+ devicePixelRatio: null,
33
+ }));
34
+ return;
35
+ }
36
+ validateViewport(viewport);
37
+ const { width, height, devicePixelRatio } = viewport;
38
+ await connector.send("browsingContext.setViewport", {
39
+ context,
40
+ viewport: { width, height },
41
+ ...(devicePixelRatio !== undefined && { devicePixelRatio }),
42
+ });
43
+ }
44
+ /**
45
+ * Runs `change`, then waits until the page has stopped resizing. Chrome answers a viewport reset before the window
46
+ * has its own size back, so a read right after it can still show the old size. The wait ends `RESIZE_QUIET_TIME`
47
+ * after the last `resize` event, and after `RESIZE_MAX_WAIT` when the size does not change at all (no event
48
+ * comes). Never fails: a page that cannot be watched is simply not waited for.
49
+ */
50
+ async function untilResized(connector, context, change) {
51
+ const sandbox = { sandbox: "samurai-viewport" };
52
+ const watching = await callFunction(connector, context, `() => {
53
+ globalThis.__resized = new Promise((resolve) => {
54
+ let timer;
55
+ const finish = () => {
56
+ clearTimeout(timer);
57
+ removeEventListener("resize", onResize);
58
+ resolve();
59
+ };
60
+ const onResize = () => {
61
+ clearTimeout(timer);
62
+ timer = setTimeout(finish, ${RESIZE_QUIET_TIME});
63
+ };
64
+ addEventListener("resize", onResize);
65
+ timer = setTimeout(finish, ${RESIZE_MAX_WAIT});
66
+ });
67
+ }`, [], { ...sandbox, awaitPromise: false }).then(() => true, () => false);
68
+ await change();
69
+ if (!watching)
70
+ return;
71
+ await callFunction(connector, context, "() => globalThis.__resized", [], sandbox).catch(() => undefined);
72
+ }
@@ -1,6 +1,6 @@
1
1
  import { type ResolvedBrowser } from "../browsers/resolve.js";
2
2
  import type { SupportedBrowser } from "../types/browser.js";
3
- import { type BrowserLaunchOptions } from "./browser.js";
3
+ import type { BrowserLaunchOptions } from "./launch-flags.js";
4
4
  /** Written into a managed build's folder once it started there; its first start can be slow (macOS checks the new app) */
5
5
  export declare const LAUNCHED_STAMP = ".samurai-launched";
6
6
  /**
@@ -2,7 +2,8 @@ import { existsSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { projectBrowser } from "../browsers/resolve.js";
4
4
  import logger from "../logger/index.js";
5
- import { Browser, BrowserStartTimeoutError, } from "./browser.js";
5
+ import { Browser } from "./browser.js";
6
+ import { BrowserStartTimeoutError } from "./session.js";
6
7
  /** Written into a managed build's folder once it started there; its first start can be slow (macOS checks the new app) */
7
8
  export const LAUNCHED_STAMP = ".samurai-launched";
8
9
  /**
@@ -1,6 +1,6 @@
1
1
  export { browsersCacheDir } from "./cache-dir.js";
2
2
  export type { CachedBuild, Downloader } from "./downloader.js";
3
3
  export { ENV_CHROMEDRIVER_PATH, ENV_PATH, type Override } from "./overrides.js";
4
- export { LAUNCHABLE, MANAGED_BROWSERS, PINNED, isManagedBrowser, type ManagedBrowser, } from "./pins.js";
4
+ export { MANAGED_BROWSERS, PINNED, isManagedBrowser, type ManagedBrowser, } from "./pins.js";
5
5
  export { browserStatus, projectBrowser, projectExecutable, resolveBrowser, resolveExecutable, type ResolvedBrowser, } from "./resolve.js";
6
6
  export { BrowserNotInstalledError, installBrowser, listInstalled, managedBuildDir, managedChromedriver, managedExecutable, uninstallBrowser, type BrowserStatus, type InstalledBuild, type StoreOptions, } from "./store.js";
@@ -1,5 +1,5 @@
1
1
  export { browsersCacheDir } from "./cache-dir.js";
2
2
  export { ENV_CHROMEDRIVER_PATH, ENV_PATH } from "./overrides.js";
3
- export { LAUNCHABLE, MANAGED_BROWSERS, PINNED, isManagedBrowser, } from "./pins.js";
3
+ export { MANAGED_BROWSERS, PINNED, isManagedBrowser, } from "./pins.js";
4
4
  export { browserStatus, projectBrowser, projectExecutable, resolveBrowser, resolveExecutable, } from "./resolve.js";
5
5
  export { BrowserNotInstalledError, installBrowser, listInstalled, managedBuildDir, managedChromedriver, managedExecutable, uninstallBrowser, } from "./store.js";
@@ -11,6 +11,4 @@ export declare const PINNED: {
11
11
  };
12
12
  export type ManagedBrowser = keyof typeof PINNED;
13
13
  export declare const MANAGED_BROWSERS: ManagedBrowser[];
14
- /** The managed browsers the framework can launch (Chrome through its chromedriver) */
15
- export declare const LAUNCHABLE: readonly ManagedBrowser[];
16
14
  export declare function isManagedBrowser(name: string): name is ManagedBrowser;
@@ -7,8 +7,6 @@ export const PINNED = {
7
7
  },
8
8
  };
9
9
  export const MANAGED_BROWSERS = Object.keys(PINNED);
10
- /** The managed browsers the framework can launch (Chrome through its chromedriver) */
11
- export const LAUNCHABLE = ["firefox", "chrome"];
12
10
  export function isManagedBrowser(name) {
13
11
  return Object.prototype.hasOwnProperty.call(PINNED, name);
14
12
  }
@@ -20,8 +20,6 @@ export interface BrowserStatus {
20
20
  /** unavailable: no download exists for this OS and architecture */
21
21
  state: "installed" | "missing" | "unavailable";
22
22
  executablePath?: string;
23
- /** Whether the framework can launch it yet */
24
- launchable: boolean;
25
23
  /** A path set on purpose, which launches use instead of the managed build */
26
24
  override?: Override;
27
25
  }
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { browsersCacheDir } from "./cache-dir.js";
4
4
  import { COMPLETE_MARKER, puppeteerDownloader, } from "./downloader.js";
5
5
  import { overrideOf, } from "./overrides.js";
6
- import { isManagedBrowser, LAUNCHABLE, PINNED, } from "./pins.js";
6
+ import { isManagedBrowser, PINNED } from "./pins.js";
7
7
  export { COMPLETE_MARKER };
8
8
  /** Each install downloads into its own `<cache>/.staging-<random>/` and moves the finished build into place */
9
9
  const STAGING_PREFIX = ".staging-";
@@ -107,7 +107,6 @@ export function statusOf(name, options = {}) {
107
107
  name,
108
108
  label: pin.label,
109
109
  buildId: pin.buildId,
110
- launchable: LAUNCHABLE.includes(name),
111
110
  ...(override && { override }),
112
111
  };
113
112
  if (!downloader.platform())
@@ -1,6 +1,8 @@
1
- import type { ProjectOptions } from "../runner/run.js";
2
- /** The values a command line can override for one run */
3
- type RunOverrides = Pick<ProjectOptions, "environment" | "timeout" | "expectTimeout">;
1
+ import { type ProjectOptions, type RunnerOptions } from "../runner/run.js";
2
+ /** `firefox` or `chrome` */
3
+ type SupportedBrowser = NonNullable<RunnerOptions["browsers"]>[number];
4
+ /** The values a command line can override for one run: the environment and the two timeouts */
5
+ type EnvAndTimeouts = Pick<ProjectOptions, "environment" | "timeout" | "expectTimeout">;
4
6
  /** Flags and positional arguments of a command line; an unknown flag is an error */
5
7
  export declare function parseCommandLine(argv: string[]): {
6
8
  values: {
@@ -9,8 +11,10 @@ export declare function parseCommandLine(argv: string[]): {
9
11
  "expect-timeout"?: string | undefined;
10
12
  headless?: boolean | undefined;
11
13
  port?: string | undefined;
14
+ workers?: string | undefined;
12
15
  grep?: string | undefined;
13
16
  group?: string | undefined;
17
+ browser?: string[] | undefined;
14
18
  file?: string[] | undefined;
15
19
  json?: boolean | undefined;
16
20
  all?: boolean | undefined;
@@ -26,16 +30,24 @@ export declare function parseCommandLine(argv: string[]): {
26
30
  };
27
31
  positionals: string[];
28
32
  };
33
+ /** What `parseCommandLine` returns */
34
+ export type ParsedCommandLine = ReturnType<typeof parseCommandLine>;
35
+ /** The flag values of a command line, parsed once by the caller or here from `argv` */
36
+ export type CommandValues = ParsedCommandLine["values"];
29
37
  /** How the command line says tests are selected and the browser starts */
30
38
  export interface RunnerFlags {
31
39
  headless?: boolean;
32
40
  port?: number;
41
+ /** `--workers`: a whole number or a percentage like `50%`, as typed */
42
+ workers?: string;
33
43
  grep?: string;
34
44
  group?: string;
35
45
  files?: string[];
46
+ /** `--browser`, repeated for more than one; replaces the config's `browsers` for this run */
47
+ browsers?: SupportedBrowser[];
36
48
  }
37
- /** Reads `--headless`, `--port <n>`, `--grep <text>`, `--group <name>` and `--file <spec>` (repeatable) from `argv` */
38
- export declare function parseRunnerFlags(argv: string[]): RunnerFlags;
39
- /** Reads `--env`, `--timeout` and `--expect-timeout` from `argv`; the environment falls back to SAMURAI_ENV */
40
- export declare function parseRunOverrides(argv: string[], env?: Partial<NodeJS.ProcessEnv>): RunOverrides;
49
+ /** Reads `--headless`, `--port <n>`, `--workers <n|n%>`, `--grep <text>`, `--group <name>`, `--file <spec>` and `--browser <name>` (both repeatable) from `argv`, or from values that were parsed already */
50
+ export declare function parseRunnerFlags(source: string[] | CommandValues): RunnerFlags;
51
+ /** Reads `--env`, `--timeout` and `--expect-timeout` from `argv` (or values parsed already); the environment falls back to SAMURAI_ENV */
52
+ export declare function parseRunOverrides(source: string[] | CommandValues, env?: Partial<NodeJS.ProcessEnv>): EnvAndTimeouts;
41
53
  export {};
package/dist/cli/args.js CHANGED
@@ -1,12 +1,15 @@
1
1
  import { parseArgs } from "node:util";
2
+ import { checkBrowserList, parseWorkers, } from "../runner/run.js";
2
3
  const FLAGS = {
3
4
  env: { type: "string" },
4
5
  timeout: { type: "string" },
5
6
  "expect-timeout": { type: "string" },
6
7
  headless: { type: "boolean" },
7
8
  port: { type: "string" },
9
+ workers: { type: "string" },
8
10
  grep: { type: "string" },
9
11
  group: { type: "string" },
12
+ browser: { type: "string", multiple: true },
10
13
  file: { type: "string", multiple: true },
11
14
  json: { type: "boolean" },
12
15
  all: { type: "boolean" },
@@ -30,12 +33,10 @@ export function parseCommandLine(argv) {
30
33
  allowPositionals: true,
31
34
  });
32
35
  }
33
- function parseFlags(argv) {
34
- return parseCommandLine(argv).values;
35
- }
36
- /** Reads `--headless`, `--port <n>`, `--grep <text>`, `--group <name>` and `--file <spec>` (repeatable) from `argv` */
37
- export function parseRunnerFlags(argv) {
38
- const values = parseFlags(argv);
36
+ const valuesOf = (source) => Array.isArray(source) ? parseCommandLine(source).values : source;
37
+ /** Reads `--headless`, `--port <n>`, `--workers <n|n%>`, `--grep <text>`, `--group <name>`, `--file <spec>` and `--browser <name>` (both repeatable) from `argv`, or from values that were parsed already */
38
+ export function parseRunnerFlags(source) {
39
+ const values = valuesOf(source);
39
40
  const flags = {};
40
41
  if (values.headless !== undefined)
41
42
  flags.headless = values.headless;
@@ -46,17 +47,25 @@ export function parseRunnerFlags(argv) {
46
47
  }
47
48
  flags.port = port;
48
49
  }
50
+ if (values.workers !== undefined) {
51
+ parseWorkers(values.workers, "--workers");
52
+ flags.workers = values.workers;
53
+ }
49
54
  if (values.grep !== undefined)
50
55
  flags.grep = values.grep;
51
56
  if (values.group !== undefined)
52
57
  flags.group = values.group;
53
58
  if (values.file?.length)
54
59
  flags.files = values.file;
60
+ if (values.browser?.length) {
61
+ checkBrowserList(values.browser, "--browser");
62
+ flags.browsers = values.browser;
63
+ }
55
64
  return flags;
56
65
  }
57
- /** Reads `--env`, `--timeout` and `--expect-timeout` from `argv`; the environment falls back to SAMURAI_ENV */
58
- export function parseRunOverrides(argv, env = {}) {
59
- const values = parseFlags(argv);
66
+ /** Reads `--env`, `--timeout` and `--expect-timeout` from `argv` (or values parsed already); the environment falls back to SAMURAI_ENV */
67
+ export function parseRunOverrides(source, env = {}) {
68
+ const values = valuesOf(source);
60
69
  const overrides = {};
61
70
  const environment = values.env ?? env.SAMURAI_ENV;
62
71
  if (environment)
@@ -1,6 +1,5 @@
1
1
  import { type StoreOptions } from "../browsers/index.js";
2
- import type { Io } from "./main.js";
3
- export declare const BROWSERS_USAGE = "Usage:\n samurai browsers [list] The pinned browsers and every build in the cache\n samurai browsers install [firefox|chrome|all] Download the pinned builds (default: all)\n samurai browsers uninstall <name> [--build <id>]\n Remove a build (default: the pinned one)\n samurai browsers uninstall --unused Remove builds this framework version doesn't pin\n";
2
+ import type { Io } from "./io.js";
4
3
  export declare function formatBytes(n: number): string;
5
4
  /** `samurai browsers …`; `options` is for tests. The cache folder follows `io.env` */
6
5
  export declare function browsersCommand(argv: string[], io: Io, options?: StoreOptions): Promise<number>;
@@ -1,6 +1,6 @@
1
1
  import { browsersCacheDir, browserStatus, installBrowser, isManagedBrowser, listInstalled, MANAGED_BROWSERS, PINNED, uninstallBrowser, } from "../browsers/index.js";
2
2
  import { parseCommandLine } from "./args.js";
3
- export const BROWSERS_USAGE = `Usage:
3
+ const BROWSERS_USAGE = `Usage:
4
4
  samurai browsers [list] The pinned browsers and every build in the cache
5
5
  samurai browsers install [firefox|chrome|all] Download the pinned builds (default: all)
6
6
  samurai browsers uninstall <name> [--build <id>]
@@ -96,7 +96,7 @@ export async function browsersCommand(argv, io, options = {}) {
96
96
  return 0;
97
97
  }
98
98
  if (target === undefined || target === "all") {
99
- io.err(`Which browser? \n\n${BROWSERS_USAGE}`);
99
+ io.err(`Which browser?\n\n${BROWSERS_USAGE}`);
100
100
  return 2;
101
101
  }
102
102
  const [name] = names(target);
@@ -8,8 +8,12 @@ export interface ConsoleReporterOptions {
8
8
  export declare class ConsoleReporter {
9
9
  private readonly write;
10
10
  private readonly paint;
11
+ /** Whether the run has more than one browser: only then is each test labelled with its browser */
12
+ private labelled;
11
13
  constructor({ write, color }: ConsoleReporterOptions);
12
14
  handle(event: RunEvent): void;
15
+ /** ` [chrome]` in a run with more than one browser, else nothing */
16
+ private tag;
13
17
  private line;
14
18
  private summary;
15
19
  }
@@ -1,17 +1,21 @@
1
+ import { painter } from "./paint.js";
1
2
  const seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
2
3
  /** Prints a run for a person: one line per test as it ends, then every failure in full and a count */
3
4
  export class ConsoleReporter {
4
5
  constructor({ write, color }) {
6
+ /** Whether the run has more than one browser: only then is each test labelled with its browser */
7
+ this.labelled = false;
5
8
  this.write = write;
6
- this.paint = (code, text) => color ? `\u001b[${code}m${text}\u001b[0m` : text;
9
+ this.paint = painter(color);
7
10
  }
8
11
  handle(event) {
9
12
  switch (event.type) {
10
13
  case "run-start":
11
- this.write(`Running ${event.total} ${event.total === 1 ? "test" : "tests"} against ${event.environment}\n`);
14
+ this.labelled = event.browsers.length > 1;
15
+ this.write(`Running ${event.total} ${event.total === 1 ? "test" : "tests"}${event.workers ? ` with ${event.workers} workers` : ""} against ${event.environment}${this.labelled ? ` in ${event.browsers.join(", ")}` : ""}\n`);
12
16
  return;
13
17
  case "test-end":
14
- this.write(` ${this.line(event.name, event.result)}\n`);
18
+ this.write(` ${this.line(event.name, event.result, event.browser)}\n`);
15
19
  return;
16
20
  case "run-end":
17
21
  this.summary(event.summary);
@@ -20,13 +24,17 @@ export class ConsoleReporter {
20
24
  return;
21
25
  }
22
26
  }
23
- line(name, result) {
27
+ /** ` [chrome]` in a run with more than one browser, else nothing */
28
+ tag(browser) {
29
+ return this.labelled ? ` ${this.paint(90, `[${browser}]`)}` : "";
30
+ }
31
+ line(name, result, browser) {
24
32
  const took = "duration" in result
25
33
  ? ` ${this.paint(90, `(${seconds(result.duration)})`)}`
26
34
  : "";
27
35
  return result.status === "success"
28
- ? `${this.paint(32, "✔")} ${name}${took}`
29
- : `${this.paint(31, "✖")} ${name}${took}`;
36
+ ? `${this.paint(32, "✔")} ${name}${this.tag(browser)}${took}`
37
+ : `${this.paint(31, "✖")} ${name}${this.tag(browser)}${took}`;
30
38
  }
31
39
  summary(summary) {
32
40
  const failed = summary.tests.filter((test) => test.status !== "success");
@@ -34,7 +42,7 @@ export class ConsoleReporter {
34
42
  if (!("name" in test))
35
43
  continue;
36
44
  const message = "message" in test ? test.message : "did not finish";
37
- this.write(`\n${this.paint(31, `${test.name}`)}\n`);
45
+ this.write(`\n${this.paint(31, `${test.name}`)}${"browser" in test ? this.tag(test.browser) : ""}\n`);
38
46
  this.write(`${message
39
47
  .split("\n")
40
48
  .map((line) => ` ${line}`)
@@ -1,6 +1,7 @@
1
1
  import { nodeDef, } from "../flows/core/index.js";
2
2
  import { maskSecrets as maskText, } from "../flows/run/index.js";
3
3
  import { STEP_NODES } from "../flows/steps/index.js";
4
+ import { painter } from "./paint.js";
4
5
  const took = (ms) => ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(1)}s`;
5
6
  const SKIPPED_BY_SHAPE = new Set(["start", "end"]);
6
7
  /** Prints a flow run for a person: one line per node as it ends, then a count. Start and End are not listed. */
@@ -10,7 +11,7 @@ export class FlowReporter {
10
11
  this.steps = steps ?? STEP_NODES;
11
12
  this.flow = flow;
12
13
  this.write = write;
13
- this.paint = (code, text) => color ? `\u001b[${code}m${text}\u001b[0m` : text;
14
+ this.paint = painter(color);
14
15
  this.nodes = new Map(flow.nodes.map((n) => [n.id, n]));
15
16
  }
16
17
  shapeOf(node) {
@@ -1,4 +1,3 @@
1
- import type { Io } from "./main.js";
2
- export declare const FLOW_USAGE = "Usage:\n samurai flow list [--json] List the flows in ./flows\n samurai flow check [<flow>...] [options] Check flows without running them (all flows when none is given)\n samurai flow run <flow>... [options] Run flows, in the order given\n samurai flow run --all [options] Run every flow in ./flows, in file name order\n\nA flow is an id (flows/<id>.flow.json) or a path to a .flow.json file.\n\nOptions for check and run:\n --env <name> Environment to use (or SAMURAI_ENV)\n --allow-unsupported Skip nodes whose kind has no handler instead of refusing the flow (every built-in kind runs)\n --json Machine-readable output: JSON lines for run, one array for check and 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 --port <n> Browser debugging port (default 9223)\n\nEach run writes result/flows/<id>.json.\nExit code: 0 when every node that should run passed, 1 when a node failed or the run was cancelled,\n2 for a usage, configuration or flow-file error, or a flow that has errors (nothing ran).\n";
1
+ import type { Io } from "./io.js";
3
2
  /** `samurai flow ...`: `argv` is what follows `flow`. Returns the exit code */
4
3
  export declare function flowCommand(argv: string[], io: Io): Promise<number>;
package/dist/cli/flow.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import path from "node:path";
2
2
  import { parseCommandLine, parseRunnerFlags, parseRunOverrides, } from "./args.js";
3
3
  import { FlowReporter } from "./flow-reporter.js";
4
+ import { painter } from "./paint.js";
4
5
  import { projectSteps } from "../flows/steps/index.js";
5
6
  import {} from "../flows/core/index.js";
6
7
  import { FLOW_SUFFIX, FlowCheckError, FlowReportBuilder, checkFlowInProject, flowsDir, listFlows, readFlow, readFlowPath, runFlowInProject, writeFlowReport, } from "../flows/run/index.js";
7
- export const FLOW_USAGE = `Usage:
8
+ import { DEFAULT_PORT } from "../runner/run.js";
9
+ const FLOW_USAGE = `Usage:
8
10
  samurai flow list [--json] List the flows in ./flows
9
11
  samurai flow check [<flow>...] [options] Check flows without running them (all flows when none is given)
10
12
  samurai flow run <flow>... [options] Run flows, in the order given
@@ -21,7 +23,8 @@ Options for run:
21
23
  --timeout <ms> Time one test may take
22
24
  --expect-timeout <ms> Time actions and assertions retry
23
25
  --headless, --no-headless Run the browser without a window, or with one
24
- --port <n> Browser debugging port (default 9223)
26
+ --port <n> Browser debugging port (default ${DEFAULT_PORT})
27
+ --workers <n|n%> Worker processes the tests share (or SAMURAI_WORKERS; default half the CPUs)
25
28
 
26
29
  Each run writes result/flows/<id>.json.
27
30
  Exit code: 0 when every node that should run passed, 1 when a node failed or the run was cancelled,
@@ -38,11 +41,8 @@ const describe = (flow, p) => {
38
41
  ...(node?.title && { title: node.title }),
39
42
  };
40
43
  };
41
- function paintWith(io) {
42
- return (code, text) => io.color ? `\u001b[${code}m${text}\u001b[0m` : text;
43
- }
44
44
  function problemLines(io, problems) {
45
- const paint = paintWith(io);
45
+ const paint = painter(io.color);
46
46
  return problems
47
47
  .map((p) => {
48
48
  const who = p.key
@@ -89,6 +89,15 @@ async function list(io, json) {
89
89
  : `${row.id} ${row.name} (${row.nodes} ${row.nodes === 1 ? "node" : "nodes"})\n`);
90
90
  return 0;
91
91
  }
92
+ /** Checks one flow of the project, the same way for `check` and for the refusal before a run */
93
+ function checkOne(projectDir, flow, options) {
94
+ return checkFlowInProject({
95
+ projectDir,
96
+ flow,
97
+ unsupported: options.unsupported,
98
+ ...(options.environment && { environment: options.environment }),
99
+ });
100
+ }
92
101
  async function check(refs, io, options) {
93
102
  const projectDir = process.cwd();
94
103
  const targets = refs.length ? refs : await everyFlow(projectDir);
@@ -104,12 +113,7 @@ async function check(refs, io, options) {
104
113
  code = 2;
105
114
  continue;
106
115
  }
107
- const result = await checkFlowInProject({
108
- projectDir,
109
- flow,
110
- unsupported: options.unsupported,
111
- ...(options.environment && { environment: options.environment }),
112
- });
116
+ const result = await checkOne(projectDir, flow, options);
113
117
  const problems = result.problems.map((p) => describe(result.flow, p));
114
118
  all.push(...problems);
115
119
  if (code === 0 && problems.some((p) => p.level === "error"))
@@ -141,19 +145,14 @@ async function run(refs, io, options) {
141
145
  // Several flows: refuse them all before the first one runs
142
146
  if (flows.length > 1) {
143
147
  for (const flow of flows) {
144
- const { problems } = await checkFlowInProject({
145
- projectDir,
146
- flow,
147
- unsupported: options.unsupported,
148
- ...(options.environment && { environment: options.environment }),
149
- });
148
+ const { problems } = await checkOne(projectDir, flow, options);
150
149
  const errors = problems.filter((p) => p.level === "error");
151
150
  if (errors.length)
152
151
  return refuse(errors, flow);
153
152
  }
154
153
  }
155
154
  let code = 0;
156
- for (const flow of flows) {
155
+ for (const [index, flow] of flows.entries()) {
157
156
  if (io.signal?.aborted) {
158
157
  code = 1;
159
158
  break;
@@ -162,7 +161,7 @@ async function run(refs, io, options) {
162
161
  const human = options.json
163
162
  ? undefined
164
163
  : new FlowReporter({ flow, write: io.out, color: io.color, steps });
165
- if (flows.indexOf(flow) > 0 && !options.json)
164
+ if (index > 0 && !options.json)
166
165
  io.out("\n");
167
166
  let summary;
168
167
  try {
@@ -200,7 +199,7 @@ export async function flowCommand(argv, io) {
200
199
  const [sub, ...refs] = positionals;
201
200
  const unsupported = values["allow-unsupported"] ? "skip" : "fail";
202
201
  const json = values.json === true;
203
- const overrides = parseRunOverrides(argv, io.env);
202
+ const overrides = parseRunOverrides(values, io.env);
204
203
  try {
205
204
  if (sub === "list" && refs.length === 0)
206
205
  return await list(io, json);
@@ -215,7 +214,7 @@ export async function flowCommand(argv, io) {
215
214
  throw new UsageError("Give flows or --all, not both.");
216
215
  if (!values.all && refs.length === 0)
217
216
  throw new UsageError("Which flow? Give a flow or --all.");
218
- const flags = parseRunnerFlags(argv);
217
+ const flags = parseRunnerFlags(values);
219
218
  const targets = values.all ? await everyFlow(process.cwd()) : refs;
220
219
  return await run(targets, io, {
221
220
  json,
@@ -225,6 +224,10 @@ export async function flowCommand(argv, io) {
225
224
  ...overrides,
226
225
  ...(flags.headless !== undefined && { headless: flags.headless }),
227
226
  ...(flags.port !== undefined && { port: flags.port }),
227
+ ...(flags.workers !== undefined && { workers: flags.workers }),
228
+ ...(io.env.SAMURAI_WORKERS && {
229
+ workersEnv: io.env.SAMURAI_WORKERS,
230
+ }),
228
231
  },
229
232
  });
230
233
  }
package/dist/cli/init.js CHANGED
@@ -1,12 +1,7 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
+ import { frameworkVersion } from "./version.js";
3
4
  const PACKAGE = "@itmetsam/samurai-framework";
4
- /** The version of this package, so a new project asks for the release that scaffolded it */
5
- function frameworkVersion() {
6
- const manifest = new URL("../../package.json", import.meta.url);
7
- return JSON.parse(readFileSync(manifest, "utf8"))
8
- .version;
9
- }
10
5
  /** The files `samurai init` writes, by path relative to the project folder */
11
6
  export function scaffold(name) {
12
7
  return {
@@ -23,7 +18,7 @@ export function scaffold(name) {
23
18
 
24
19
  export default defineConfig({
25
20
  srcDir: "./tests",
26
- browser: "firefox",
21
+ browsers: ["firefox"],
27
22
  timeout: 30000,
28
23
  environments: {
29
24
  local: { baseURL: "http://localhost:3000" },
@@ -0,0 +1,10 @@
1
+ /** What the command line reads from and writes to; the process supplies it, tests fake it */
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
+ }
package/dist/cli/io.js ADDED
@@ -0,0 +1 @@
1
+ export {};