@itmetsam/samurai-framework 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/README.md +3 -4
  2. package/dist/assert/assertion-error.d.ts +1 -1
  3. package/dist/assert/expect.d.ts +5 -50
  4. package/dist/assert/expect.js +5 -182
  5. package/dist/assert/locator-assertions.d.ts +22 -0
  6. package/dist/assert/locator-assertions.js +61 -0
  7. package/dist/assert/pending-assertions.d.ts +7 -0
  8. package/dist/assert/pending-assertions.js +40 -0
  9. package/dist/assert/text-match.d.ts +4 -0
  10. package/dist/assert/text-match.js +11 -0
  11. package/dist/assert/value-assertions.d.ts +23 -0
  12. package/dist/assert/value-assertions.js +82 -0
  13. package/dist/browser/browser-context.d.ts +0 -1
  14. package/dist/browser/browser-context.js +13 -5
  15. package/dist/browser/browser-finder.js +5 -9
  16. package/dist/browser/browser.d.ts +3 -31
  17. package/dist/browser/browser.js +36 -219
  18. package/dist/browser/chrome-launch.d.ts +2 -1
  19. package/dist/browser/dialog-handling.d.ts +32 -0
  20. package/dist/browser/dialog-handling.js +85 -0
  21. package/dist/browser/document-watcher.d.ts +44 -0
  22. package/dist/browser/document-watcher.js +160 -0
  23. package/dist/browser/download-folders.d.ts +0 -1
  24. package/dist/browser/download-folders.js +7 -15
  25. package/dist/browser/download.js +5 -10
  26. package/dist/browser/downloads-dir.js +2 -2
  27. package/dist/browser/emit.d.ts +7 -0
  28. package/dist/browser/emit.js +16 -0
  29. package/dist/browser/emulation.d.ts +1 -1
  30. package/dist/browser/emulation.js +54 -18
  31. package/dist/browser/file-chooser.js +4 -11
  32. package/dist/browser/history.d.ts +6 -0
  33. package/dist/browser/history.js +89 -0
  34. package/dist/browser/index.d.ts +2 -0
  35. package/dist/browser/index.js +2 -0
  36. package/dist/browser/init-script.d.ts +2 -0
  37. package/dist/browser/init-script.js +16 -0
  38. package/dist/browser/launch-firefox.d.ts +20 -0
  39. package/dist/browser/launch-firefox.js +66 -0
  40. package/dist/browser/launch-flags.d.ts +18 -0
  41. package/dist/browser/launch-flags.js +100 -0
  42. package/dist/browser/lazy.d.ts +21 -0
  43. package/dist/browser/lazy.js +44 -0
  44. package/dist/browser/page-events.d.ts +44 -0
  45. package/dist/browser/page-events.js +58 -0
  46. package/dist/browser/page-logs.js +2 -9
  47. package/dist/browser/page-navigation.d.ts +58 -0
  48. package/dist/browser/page-navigation.js +108 -0
  49. package/dist/browser/page-network.d.ts +69 -0
  50. package/dist/browser/page-network.js +163 -0
  51. package/dist/browser/page-settings.d.ts +35 -0
  52. package/dist/browser/page-settings.js +51 -0
  53. package/dist/browser/page-transfers.d.ts +36 -0
  54. package/dist/browser/page-transfers.js +50 -0
  55. package/dist/browser/page.d.ts +39 -154
  56. package/dist/browser/page.js +134 -758
  57. package/dist/browser/pdf.d.ts +1 -1
  58. package/dist/browser/session.d.ts +16 -0
  59. package/dist/browser/session.js +32 -0
  60. package/dist/browser/viewport.d.ts +19 -0
  61. package/dist/browser/viewport.js +72 -0
  62. package/dist/browser/warm-up.d.ts +1 -1
  63. package/dist/browser/warm-up.js +2 -1
  64. package/dist/browsers/index.d.ts +1 -1
  65. package/dist/browsers/index.js +1 -1
  66. package/dist/browsers/pins.d.ts +0 -2
  67. package/dist/browsers/pins.js +0 -2
  68. package/dist/browsers/store.d.ts +0 -2
  69. package/dist/browsers/store.js +1 -2
  70. package/dist/cli/args.d.ts +13 -6
  71. package/dist/cli/args.js +13 -10
  72. package/dist/cli/browsers.d.ts +1 -2
  73. package/dist/cli/browsers.js +2 -2
  74. package/dist/cli/console-reporter.js +3 -2
  75. package/dist/cli/flow-reporter.js +2 -1
  76. package/dist/cli/flow.d.ts +1 -2
  77. package/dist/cli/flow.js +25 -22
  78. package/dist/cli/init.js +2 -7
  79. package/dist/cli/io.d.ts +10 -0
  80. package/dist/cli/io.js +1 -0
  81. package/dist/cli/main.d.ts +3 -10
  82. package/dist/cli/main.js +10 -143
  83. package/dist/cli/paint.d.ts +2 -0
  84. package/dist/cli/paint.js +4 -0
  85. package/dist/cli/record.d.ts +4 -0
  86. package/dist/cli/record.js +93 -0
  87. package/dist/cli/usage.d.ts +1 -0
  88. package/dist/cli/usage.js +58 -0
  89. package/dist/cli/version.d.ts +2 -0
  90. package/dist/cli/version.js +7 -0
  91. package/dist/config/browsers.d.ts +11 -0
  92. package/dist/config/browsers.js +32 -0
  93. package/dist/config/config.js +4 -1
  94. package/dist/config/mask.d.ts +1 -1
  95. package/dist/config/mask.js +7 -2
  96. package/dist/config/run-settings.d.ts +1 -2
  97. package/dist/config/run-settings.js +1 -6
  98. package/dist/config/timeouts.d.ts +2 -0
  99. package/dist/config/timeouts.js +13 -0
  100. package/dist/config/workers.d.ts +14 -0
  101. package/dist/config/workers.js +29 -0
  102. package/dist/flows/run/branch-context.d.ts +14 -0
  103. package/dist/flows/run/branch-context.js +35 -0
  104. package/dist/flows/run/handlers/condition.d.ts +1 -1
  105. package/dist/flows/run/handlers/end.d.ts +1 -1
  106. package/dist/flows/run/handlers/group.d.ts +1 -1
  107. package/dist/flows/run/handlers/index.d.ts +2 -2
  108. package/dist/flows/run/handlers/parallel.d.ts +1 -1
  109. package/dist/flows/run/handlers/start.d.ts +1 -1
  110. package/dist/flows/run/handlers/support.d.ts +3 -27
  111. package/dist/flows/run/handlers/test.d.ts +1 -1
  112. package/dist/flows/run/handlers/types.d.ts +27 -0
  113. package/dist/flows/run/handlers/types.js +1 -0
  114. package/dist/flows/run/index.d.ts +1 -1
  115. package/dist/flows/run/prepare-flow.d.ts +14 -0
  116. package/dist/flows/run/prepare-flow.js +44 -0
  117. package/dist/flows/run/project.d.ts +33 -2
  118. package/dist/flows/run/project.js +139 -57
  119. package/dist/flows/run/run.d.ts +3 -4
  120. package/dist/flows/run/run.js +23 -193
  121. package/dist/flows/run/walker.d.ts +56 -0
  122. package/dist/flows/run/walker.js +225 -0
  123. package/dist/flows/steps/catalog.d.ts +1 -1
  124. package/dist/flows/steps/handler.d.ts +1 -1
  125. package/dist/flows/steps/set.d.ts +1 -1
  126. package/dist/index.js +1 -1
  127. package/dist/locator/actionability.d.ts +33 -0
  128. package/dist/locator/actionability.js +104 -0
  129. package/dist/locator/chain-resolver.d.ts +20 -0
  130. package/dist/locator/chain-resolver.js +102 -0
  131. package/dist/locator/document-swap.d.ts +1 -0
  132. package/dist/locator/document-swap.js +11 -0
  133. package/dist/locator/element-state.d.ts +3 -1
  134. package/dist/locator/element-state.js +11 -3
  135. package/dist/locator/file-actions.d.ts +35 -0
  136. package/dist/locator/file-actions.js +55 -0
  137. package/dist/{script/helpers.js → locator/helper-realm.js} +2 -2
  138. package/dist/locator/input-actions.d.ts +18 -0
  139. package/dist/locator/input-actions.js +80 -0
  140. package/dist/locator/label-locator.d.ts +1 -1
  141. package/dist/locator/label-locator.js +5 -12
  142. package/dist/locator/locator.d.ts +14 -37
  143. package/dist/locator/locator.js +68 -424
  144. package/dist/locator/page-scripts.d.ts +16 -0
  145. package/dist/locator/page-scripts.js +54 -0
  146. package/dist/locator/page-search.d.ts +10 -0
  147. package/dist/locator/page-search.js +19 -0
  148. package/dist/locator/selector-errors.d.ts +0 -4
  149. package/dist/locator/selector-errors.js +0 -7
  150. package/dist/locator/selector.d.ts +14 -13
  151. package/dist/locator/selector.js +10 -16
  152. package/dist/locator/text-locator.d.ts +1 -1
  153. package/dist/locator/text-locator.js +5 -9
  154. package/dist/logger/index.d.ts +6 -0
  155. package/dist/logger/index.js +14 -7
  156. package/dist/network/bidi-wire.d.ts +10 -0
  157. package/dist/network/bidi-wire.js +38 -0
  158. package/dist/network/intercept.d.ts +30 -0
  159. package/dist/network/intercept.js +130 -0
  160. package/dist/network/network-tracker.d.ts +10 -47
  161. package/dist/network/network-tracker.js +34 -68
  162. package/dist/network/network-types.d.ts +33 -0
  163. package/dist/network/network-types.js +1 -0
  164. package/dist/network/network-wait.d.ts +7 -2
  165. package/dist/network/network-wait.js +14 -4
  166. package/dist/network/response.d.ts +1 -1
  167. package/dist/network/response.js +1 -1
  168. package/dist/network/route.d.ts +92 -0
  169. package/dist/network/route.js +142 -0
  170. package/dist/network/router.d.ts +6 -91
  171. package/dist/network/router.js +25 -258
  172. package/dist/recorder/expectation.d.ts +5 -0
  173. package/dist/recorder/expectation.js +20 -0
  174. package/dist/recorder/files-recorder.d.ts +30 -0
  175. package/dist/recorder/files-recorder.js +88 -0
  176. package/dist/recorder/normalise.d.ts +17 -6
  177. package/dist/recorder/normalise.js +96 -91
  178. package/dist/recorder/options.d.ts +56 -0
  179. package/dist/recorder/options.js +1 -0
  180. package/dist/recorder/page-messages.d.ts +35 -0
  181. package/dist/recorder/page-messages.js +26 -0
  182. package/dist/recorder/ranking.js +1 -0
  183. package/dist/recorder/realms.d.ts +6 -0
  184. package/dist/recorder/realms.js +13 -0
  185. package/dist/recorder/recorder.d.ts +10 -74
  186. package/dist/recorder/recorder.js +39 -199
  187. package/dist/recorder/session-browser.d.ts +22 -0
  188. package/dist/recorder/session-browser.js +21 -0
  189. package/dist/recorder/session-secrets.d.ts +8 -0
  190. package/dist/recorder/session-secrets.js +45 -0
  191. package/dist/recorder/session.d.ts +5 -14
  192. package/dist/recorder/session.js +22 -120
  193. package/dist/recorder/spec-source.d.ts +39 -0
  194. package/dist/recorder/spec-source.js +84 -0
  195. package/dist/recorder/toolbar/action-guard.d.ts +21 -0
  196. package/dist/recorder/toolbar/action-guard.js +43 -0
  197. package/dist/recorder/toolbar/actions/assert.d.ts +4 -0
  198. package/dist/recorder/toolbar/actions/assert.js +52 -0
  199. package/dist/recorder/toolbar/actions/insert.d.ts +6 -0
  200. package/dist/recorder/toolbar/actions/insert.js +93 -0
  201. package/dist/recorder/toolbar/actions/pick.d.ts +8 -0
  202. package/dist/recorder/toolbar/actions/pick.js +26 -0
  203. package/dist/recorder/toolbar/actions/reply.d.ts +7 -0
  204. package/dist/recorder/toolbar/actions/reply.js +10 -0
  205. package/dist/recorder/toolbar/controller-fixture.d.ts +47 -0
  206. package/dist/recorder/toolbar/controller-fixture.js +129 -0
  207. package/dist/recorder/toolbar/controller.d.ts +12 -78
  208. package/dist/recorder/toolbar/controller.js +35 -247
  209. package/dist/recorder/toolbar/inspect.d.ts +1 -3
  210. package/dist/recorder/toolbar/inspect.js +2 -2
  211. package/dist/recorder/toolbar/page-scope.d.ts +19 -0
  212. package/dist/recorder/toolbar/page-scope.js +43 -0
  213. package/dist/recorder/toolbar/protocol.d.ts +2 -1
  214. package/dist/recorder/toolbar/source/attach.d.ts +2 -0
  215. package/dist/recorder/toolbar/source/attach.js +41 -0
  216. package/dist/recorder/toolbar/source/dom.d.ts +2 -0
  217. package/dist/recorder/toolbar/source/dom.js +47 -0
  218. package/dist/recorder/toolbar/source/drag.d.ts +2 -0
  219. package/dist/recorder/toolbar/source/drag.js +32 -0
  220. package/dist/recorder/toolbar/source/events-page.d.ts +2 -0
  221. package/dist/recorder/toolbar/source/events-page.js +67 -0
  222. package/dist/recorder/toolbar/source/events-toolbar.d.ts +2 -0
  223. package/dist/recorder/toolbar/source/events-toolbar.js +35 -0
  224. package/dist/recorder/toolbar/source/fields.d.ts +2 -0
  225. package/dist/recorder/toolbar/source/fields.js +44 -0
  226. package/dist/recorder/toolbar/source/handlers.d.ts +2 -0
  227. package/dist/recorder/toolbar/source/handlers.js +134 -0
  228. package/dist/recorder/toolbar/source/names.d.ts +2 -0
  229. package/dist/recorder/toolbar/source/names.js +12 -0
  230. package/dist/recorder/toolbar/source/picking.d.ts +2 -0
  231. package/dist/recorder/toolbar/source/picking.js +72 -0
  232. package/dist/recorder/toolbar/source/placement.d.ts +2 -0
  233. package/dist/recorder/toolbar/source/placement.js +45 -0
  234. package/dist/recorder/toolbar/source/recorder-side.d.ts +2 -0
  235. package/dist/recorder/toolbar/source/recorder-side.js +54 -0
  236. package/dist/recorder/toolbar/source/setup.d.ts +2 -0
  237. package/dist/recorder/toolbar/source/setup.js +50 -0
  238. package/dist/recorder/toolbar/source/views-panels.d.ts +2 -0
  239. package/dist/recorder/toolbar/source/views-panels.js +150 -0
  240. package/dist/recorder/toolbar/source/views-rail.d.ts +2 -0
  241. package/dist/recorder/toolbar/source/views-rail.js +40 -0
  242. package/dist/recorder/toolbar/source/views-render.d.ts +2 -0
  243. package/dist/recorder/toolbar/source/views-render.js +48 -0
  244. package/dist/recorder/toolbar/source.js +30 -856
  245. package/dist/recorder/toolbar/types.d.ts +70 -0
  246. package/dist/recorder/toolbar/types.js +1 -0
  247. package/dist/recorder/toolbar-bridge.d.ts +43 -0
  248. package/dist/recorder/toolbar-bridge.js +106 -0
  249. package/dist/runner/browsers.d.ts +0 -12
  250. package/dist/runner/browsers.js +1 -33
  251. package/dist/runner/options.d.ts +31 -0
  252. package/dist/runner/options.js +1 -0
  253. package/dist/runner/pooled-run.d.ts +43 -0
  254. package/dist/runner/pooled-run.js +75 -0
  255. package/dist/runner/ports.d.ts +2 -0
  256. package/dist/runner/ports.js +2 -0
  257. package/dist/runner/registry.d.ts +1 -1
  258. package/dist/runner/registry.js +1 -1
  259. package/dist/runner/reporter.d.ts +31 -10
  260. package/dist/runner/reporter.js +39 -6
  261. package/dist/runner/run-test.d.ts +16 -0
  262. package/dist/runner/run-test.js +178 -0
  263. package/dist/runner/run.d.ts +5 -3
  264. package/dist/runner/run.js +28 -29
  265. package/dist/runner/select.d.ts +24 -0
  266. package/dist/runner/select.js +77 -0
  267. package/dist/runner/test-dir.d.ts +13 -0
  268. package/dist/runner/test-dir.js +27 -0
  269. package/dist/runner/test-runner.d.ts +25 -56
  270. package/dist/runner/test-runner.js +81 -270
  271. package/dist/runner/worker-pool.d.ts +67 -0
  272. package/dist/runner/worker-pool.js +275 -0
  273. package/dist/runner/worker-process.d.ts +6 -0
  274. package/dist/runner/worker-process.js +54 -0
  275. package/dist/runner/worker-protocol.d.ts +67 -0
  276. package/dist/runner/worker-protocol.js +1 -0
  277. package/dist/runner/worker-session.d.ts +7 -0
  278. package/dist/runner/worker-session.js +60 -0
  279. package/dist/runner/worker.d.ts +26 -0
  280. package/dist/runner/worker.js +121 -0
  281. package/dist/runner/workers-option.d.ts +15 -0
  282. package/dist/runner/workers-option.js +22 -0
  283. package/dist/script/call-function.d.ts +1 -9
  284. package/dist/script/call-function.js +2 -19
  285. package/dist/script/element-handle.d.ts +1 -2
  286. package/dist/script/element-handle.js +1 -2
  287. package/dist/script/serialize.d.ts +3 -12
  288. package/dist/script/serialize.js +53 -62
  289. package/dist/steps/parse/call.d.ts +3 -0
  290. package/dist/steps/parse/call.js +81 -0
  291. package/dist/steps/parse/expectation.d.ts +5 -0
  292. package/dist/steps/parse/expectation.js +71 -0
  293. package/dist/steps/parse/file-entry.d.ts +4 -0
  294. package/dist/steps/parse/file-entry.js +74 -0
  295. package/dist/steps/parse/literals.d.ts +4 -0
  296. package/dist/steps/parse/literals.js +28 -0
  297. package/dist/steps/parse/step.d.ts +4 -0
  298. package/dist/steps/parse/step.js +81 -0
  299. package/dist/steps/parse.js +2 -323
  300. package/dist/transport/bidi-connection.d.ts +4 -15
  301. package/dist/transport/bidi-connection.js +9 -63
  302. package/dist/transport/log-redaction.d.ts +13 -0
  303. package/dist/transport/log-redaction.js +51 -0
  304. package/dist/transport/subscriptions.js +18 -13
  305. package/dist/transport/unsupported.d.ts +7 -0
  306. package/dist/transport/unsupported.js +13 -0
  307. package/dist/types/bidi-modules/browser.d.ts +1 -12
  308. package/dist/types/bidi-modules/browsing-context.d.ts +8 -18
  309. package/dist/types/bidi-modules/emulation.d.ts +29 -21
  310. package/dist/types/bidi-modules/input.d.ts +1 -9
  311. package/dist/types/bidi-modules/network.d.ts +29 -24
  312. package/dist/types/bidi-modules/script.d.ts +0 -9
  313. package/dist/types/bidi-modules/session.d.ts +0 -8
  314. package/dist/types/bidi-modules/storage.d.ts +0 -5
  315. package/dist/types/bidi.d.ts +7 -37
  316. package/dist/types/config.d.ts +12 -0
  317. package/dist/types/test.d.ts +8 -3
  318. package/dist/wait/wait-until.d.ts +1 -5
  319. package/dist/wait/wait-until.js +7 -16
  320. package/package.json +3 -2
  321. package/dist/config/default.d.ts +0 -2
  322. package/dist/config/default.js +0 -3
  323. package/dist/types/bidi-modules/web-extensions.d.ts +0 -9
  324. package/dist/types/bidi-protocols/command.d.ts +0 -7
  325. package/dist/types/bidi-protocols/event.d.ts +0 -17
  326. /package/dist/{script/helpers.d.ts → locator/helper-realm.d.ts} +0 -0
@@ -1,148 +1,101 @@
1
- import assert from "assert";
2
1
  import { glob } from "node:fs/promises";
3
2
  import path from "path";
4
- import { pathToFileURL } from "url";
5
- import { Browser } from "../browser/browser.js";
6
3
  import { ensureWarm } from "../browser/warm-up.js";
7
4
  import { projectBrowser } from "../browsers/resolve.js";
8
- import { browsersFor } from "./browsers.js";
9
5
  import { loadConfig, projectDir, readConfig } from "../config/config.js";
10
6
  import logger from "../logger/index.js";
11
- import { assertModuleProject } from "./module-type.js";
12
- import { clearRegistry, registeredTests } from "./registry.js";
13
- import { createEnvFixture } from "../config/variables.js";
14
- import { createSecretsFixture } from "../config/secrets.js";
15
- import TestReporter, {} from "./reporter.js";
16
- import Page from "../browser/page.js";
17
- import { cleanName, Evidence } from "../browser/evidence.js";
18
- import { takePendingAssertions } from "../assert/expect.js";
19
- import { toTestError } from "./test-error.js";
20
- import { ConnectionClosedError } from "../transport/bidi-connection.js";
21
- import { DEFAULT_SRC_DIR, findGroup, realPath, resolveGroups, testId, toPosix, } from "./groups.js";
22
- import { judgePageLogs } from "./page-logs-report.js";
23
- /** Whether a test with this full name is selected by the options */
24
- export function isSelected(name, { grep, testNames }) {
25
- if (testNames && !testNames.includes(name))
26
- return false;
27
- if (grep === undefined)
28
- return true;
29
- if (typeof grep === "string")
30
- return name.includes(grep);
31
- grep.lastIndex = 0;
32
- return grep.test(name);
33
- }
7
+ import { DEFAULT_SRC_DIR, DEFAULT_TEST_MATCH } from "./groups.js";
8
+ import { DEFAULT_PORT } from "./ports.js";
9
+ import TestReporter from "./reporter.js";
10
+ import { planItems, poolMode, runPooled, serialKeyOf, } from "./pooled-run.js";
11
+ import { runOneTest } from "./run-test.js";
12
+ import { browsersOfRun, loadSpecs, selectTests } from "./select.js";
13
+ import { resolveWorkers } from "./workers-option.js";
14
+ import { WorkerPool } from "./worker-pool.js";
15
+ import { processWorkerFactory } from "./worker-process.js";
16
+ /** Finds the spec files, picks the tests and browsers of a run and runs each test in each browser */
34
17
  export default class TestRunner {
35
- constructor(testFiles, run, group, options = {}) {
36
- /** The real `srcDir` of this run, set by `start`; test screenshots are saved under paths relative to it */
37
- this.srcDir = "";
18
+ constructor(testFiles, run, options = {}, project = {}) {
38
19
  this.testFiles = testFiles;
39
20
  this.run = run;
40
- this.group = group;
41
21
  this.options = options;
22
+ this.project = project;
42
23
  }
43
- static async init(run, group, options = {}) {
24
+ static async init(run, options = {}, project = {}) {
44
25
  logger.verbose("Initializing test runner");
45
- let srcDir = "";
46
- if (group && group.src) {
47
- srcDir = group.src;
48
- }
49
- else {
50
- srcDir = (await readConfig("srcDir")) ?? "./src";
51
- }
52
- assert(srcDir, "No srcDir declared!");
26
+ const srcDir = (await readConfig("srcDir")) ?? DEFAULT_SRC_DIR;
53
27
  const cwd = path.resolve(projectDir(), srcDir);
54
28
  if (options.files) {
55
- return new TestRunner(options.files.map((file) => path.resolve(projectDir(), file)), run, group, options);
29
+ return new TestRunner(options.files.map((file) => path.resolve(projectDir(), file)), run, options, project);
56
30
  }
57
31
  logger.verbose("Reading test files from: %s", cwd);
58
32
  const testFiles = [];
59
- for await (const entry of glob("**/*.spec.ts", {
33
+ for await (const entry of glob(DEFAULT_TEST_MATCH, {
60
34
  cwd,
61
35
  })) {
62
36
  testFiles.push(entry);
63
37
  }
64
38
  logger.debug("Found %d tests in srcDir", testFiles.length);
65
- return new TestRunner(testFiles.map((file) => path.join(cwd, file)), run, group, options);
66
- }
67
- /**
68
- * Imports the spec files and returns every test they register. A fresh registry and a fresh import of each
69
- * file, so a second run in this process registers its tests again (and sees edited specs, for files loaded as
70
- * ES modules; CommonJS files stay cached until the process ends)
71
- */
72
- async registerAll() {
73
- clearRegistry();
74
- const stamp = `?run=${Date.now().toString(36)}`;
75
- for (const file of this.testFiles)
76
- assertModuleProject(file);
77
- for (const file of this.testFiles) {
78
- logger.verbose("Trying to register file: %s", file);
79
- await import(pathToFileURL(file).href + stamp);
80
- }
81
- return [...registeredTests()];
82
- }
83
- /** Id of a registered test: its spec file relative to `srcDir`, then its name */
84
- idOf(test, srcDir) {
85
- return testId(toPosix(path.relative(srcDir, test.file)), test.name);
86
- }
87
- /** The config's groups, resolved against the tests the specs register */
88
- async resolveAll(tests) {
89
- const config = await loadConfig();
90
- const { dir, srcDir } = realDirs(config.srcDir);
91
- const groups = resolveGroups(config, tests.map(({ file, name }) => ({
92
- file: toPosix(path.relative(srcDir, file)),
93
- name,
94
- })), dir, srcDir);
95
- return { groups, srcDir };
39
+ return new TestRunner(testFiles.map((file) => path.join(cwd, file)), run, options, project);
96
40
  }
97
41
  /** The groups of the config with the tests each holds; see `listGroups` */
98
42
  async groups() {
99
- return (await this.resolveAll(await this.registerAll())).groups;
43
+ return (await loadSpecs(this.testFiles)).groups;
100
44
  }
101
45
  /** Imports the spec files and returns the tests the options select: by `group`, `testNames` and `grep` */
102
46
  async register() {
103
- const all = await this.registerAll();
104
- const { group } = this.options;
105
- let inGroup = () => true;
106
- if (group !== undefined) {
107
- const { groups, srcDir } = await this.resolveAll(all);
108
- const members = new Set(findGroup(groups, group).testIds);
109
- inGroup = (test) => members.has(this.idOf(test, srcDir));
110
- }
111
- return all.filter((test) => inGroup(test) && isSelected(test.name, this.options));
47
+ return selectTests(await loadSpecs(this.testFiles), this.options);
112
48
  }
113
49
  /**
114
- * The browsers the run uses: the options' (`--browser`), else the selected group's, else the project's, else
115
- * Firefox. A group is only looked up when there is something to look up
50
+ * Runs the selected tests in every browser of the run, one browser after the other, and returns the summary that
51
+ * was also written to the report. With more than one worker the tests run at the same time in worker processes.
116
52
  */
117
- async browsersOfRun() {
118
- const { browsers: flag, group } = this.options;
119
- const config = await loadConfig();
120
- let ofGroup;
121
- if (group !== undefined && !(flag && flag.length > 0)) {
122
- const { groups } = await this.resolveAll(await this.registerAll());
123
- ofGroup = findGroup(groups, group).browsers;
124
- }
125
- return browsersFor({ flag, group: ofGroup, project: config.browsers });
126
- }
127
- /** Runs the selected tests in every browser of the run, one browser after the other, and returns the summary that was also written to the report */
128
53
  async start() {
129
54
  const { reportPath, onEvent, signal } = this.options;
130
- const selected = await this.register();
131
- const browsers = await this.browsersOfRun();
55
+ // The specs are imported once: their top-level code runs once per run
56
+ const loaded = await loadSpecs(this.testFiles);
57
+ const selected = selectTests(loaded, this.options);
58
+ const browsers = await browsersOfRun(loaded.groups, this.options);
132
59
  const reporter = new TestReporter({
133
60
  environment: this.run.settings.environment,
134
61
  browsers,
135
62
  ...(reportPath !== undefined && { output: reportPath }),
136
63
  ...(onEvent && { onEvent }),
137
64
  });
138
- this.srcDir = realDirs((await loadConfig()).srcDir).srcDir;
65
+ // How the run goes is settled first: a bad `workers` is an error before any browser starts
66
+ const config = await loadConfig();
67
+ const requested = resolveWorkers({
68
+ flag: this.options.workers,
69
+ env: process.env.SAMURAI_WORKERS,
70
+ config: config.workers,
71
+ });
72
+ const plan = planItems(selected, browsers, serialKeyOf(loaded.groups, config.groups, loaded.srcDir));
73
+ const mode = poolMode({
74
+ workers: requested,
75
+ items: plan.items.length,
76
+ inlineConfig: this.project.inlineConfig ?? false,
77
+ });
78
+ if (mode === "inline-config" && this.workersAsked(config.workers))
79
+ logger.warn("workers needs samurai.config.ts in the project folder; running on one worker");
139
80
  // A missing browser is one clear error before the run, not a failure per test, and before any browser has run a
140
81
  // test; a fresh build's slow first start happens here too, outside any test's timeout
141
82
  if (selected.length > 0) {
142
83
  for (const browser of browsers)
143
84
  await projectBrowser(browser);
144
85
  for (const browser of browsers)
145
- await ensureWarm(browser, { port: this.options.port ?? 9223 }, signal);
86
+ await ensureWarm(browser, { port: this.options.port ?? DEFAULT_PORT }, signal);
87
+ }
88
+ if (mode === "pool") {
89
+ const workers = Math.min(requested, plan.items.length);
90
+ reporter.onStart(plan.items.length, workers);
91
+ const pool = this.createPool(workers);
92
+ try {
93
+ await runPooled(pool, plan.planned, plan.items, reporter, signal);
94
+ }
95
+ finally {
96
+ await pool.dispose();
97
+ }
98
+ return reporter.onEnd();
146
99
  }
147
100
  reporter.onStart(selected.length * browsers.length);
148
101
  for (const browser of browsers) {
@@ -155,180 +108,38 @@ export default class TestRunner {
155
108
  });
156
109
  continue;
157
110
  }
158
- await this.executeTestCase(test, browser, reporter);
111
+ await runOneTest(test, browser, this.run, this.options, loaded.srcDir, reporter);
159
112
  }
160
113
  }
161
114
  return reporter.onEnd();
162
115
  }
163
- async executeTestCase(test, selectedBrowser, reporter) {
164
- logger.verbose("Starting test: %s in %s", test.name, selectedBrowser);
165
- reporter.onTestStart(test, selectedBrowser);
166
- // A test ends once, whichever of finishing, failing, timing out, an abort or a closed browser comes first
167
- let ended = false;
168
- let evidence;
169
- const finish = (error, logs) => {
170
- if (ended)
171
- return;
172
- ended = true;
173
- reporter.onTestEnd(test, selectedBrowser, error, {
174
- ...logs,
175
- screenshots: evidence?.screenshots,
176
- });
177
- };
178
- let browserClosed = false;
179
- /** Set once the runner itself closes the browser */
180
- let closing = false;
181
- const { settings, secrets } = this.run;
182
- const timeout = settings.timeout;
183
- const { reportPath } = this.options;
184
- const resultDir = typeof reportPath === "string"
185
- ? path.dirname(reportPath)
186
- : path.join(projectDir(), "result");
187
- let browser;
188
- let page;
189
- let timeoutTimer;
190
- let onRunAborted;
191
- const timeoutController = new AbortController();
192
- await Promise.race([
193
- new Promise((resolve) => {
194
- timeoutTimer = setTimeout(() => {
195
- finish({
196
- message: `Test timed out after ${timeout / 1000} seconds`,
197
- type: "timeout",
198
- });
199
- timeoutController.abort(new Error("Test timed out"));
200
- resolve();
201
- }, timeout);
202
- onRunAborted = () => {
203
- if (timeoutController.signal.aborted)
204
- return;
205
- finish({ message: "Run aborted", type: "error" });
206
- timeoutController.abort(new Error("Run aborted"));
207
- resolve();
208
- };
209
- this.options.signal?.addEventListener("abort", onRunAborted, {
210
- once: true,
211
- });
212
- }),
213
- new Promise(async (resolve, reject) => {
214
- try {
215
- const launched = await Browser.launch(selectedBrowser, {
216
- port: this.options.port ?? 9223,
217
- headless: this.options.headless ?? settings.headless,
218
- }, timeoutController.signal);
219
- browser = launched.browser;
220
- // A person closing the window, or the browser crashing: end the test now instead of waiting for a
221
- // command to time out
222
- void launched.browser.exited.then(() => {
223
- if (ended || closing)
224
- return;
225
- browserClosed = true;
226
- finish({
227
- message: "The browser was closed while the test was running",
228
- type: "error",
229
- });
230
- timeoutController.abort(new Error("Browser closed"));
231
- resolve();
232
- });
233
- page = launched.page;
234
- evidence = new Evidence(resultDir, `${selectedBrowser}/${testDirOf(test, this.srcDir)}`);
235
- launched.page.setEvidence(evidence);
236
- const fixtures = {
237
- page: launched.page,
238
- browser: launched.browser,
239
- browserName: selectedBrowser,
240
- env: createEnvFixture(settings.environment, settings.variables),
241
- secrets: createSecretsFixture(settings.environment, secrets),
242
- };
243
- await test.function(fixtures);
244
- // Already reported as timed out
245
- if (timeoutController.signal.aborted)
246
- return resolve();
247
- const unawaited = takePendingAssertions();
248
- if (unawaited.length > 0) {
249
- finish({
250
- message: unawaited
251
- .map((matcher) => `expect(locator).${matcher}() was not awaited`)
252
- .join("\n"),
253
- type: "assertion",
254
- }, await judgeLogs(page, true));
255
- return resolve();
256
- }
257
- const verdict = await judgeLogs(page, false);
258
- finish(verdict.error, verdict);
259
- resolve();
260
- }
261
- catch (err) {
262
- if (timeoutController.signal.aborted)
263
- return resolve();
264
- finish(browserClosed || err instanceof ConnectionClosedError
265
- ? {
266
- message: "The browser was closed while the test was running",
267
- type: "error",
268
- }
269
- : toTestError(err), await judgeLogs(page, true));
270
- resolve();
271
- }
272
- }),
273
- ]);
274
- clearTimeout(timeoutTimer);
275
- if (onRunAborted)
276
- this.options.signal?.removeEventListener("abort", onRunAborted);
277
- takePendingAssertions();
278
- logger.verbose("Closing browser for test: %s", test.name);
279
- closing = true;
280
- await browser?.close();
281
- }
282
- }
283
- /** The project and source folders as real paths: registered test files come from stack traces, which resolve symlinks */
284
- function realDirs(configuredSrcDir) {
285
- const dir = realPath(projectDir());
286
- const srcDir = realPath(path.resolve(dir, configuredSrcDir ?? DEFAULT_SRC_DIR));
287
- return { dir, srcDir };
288
- }
289
- /**
290
- * The folder of a test's screenshots below `screenshots/`: its spec file without extension, then its cleaned
291
- * name. A spec outside srcDir loses the `..` (and a drive or root) its relative path starts with, so it stays
292
- * below `screenshots/`.
293
- * @internal Exported for tests
294
- */
295
- export function testDirOf(test, srcDir) {
296
- const file = toPosix(path.relative(srcDir, test.file))
297
- .replace(/\.[cm]?[jt]sx?$/, "")
298
- .split("/")
299
- .filter((segment) => segment !== "" &&
300
- segment !== "." &&
301
- segment !== ".." &&
302
- !/^[A-Za-z]:$/.test(segment))
303
- .join("/");
304
- return `${file}/${cleanName(test.name)}`;
305
- }
306
- /** Reads the page's log once the test body is done and applies config `logs` (see judgePageLogs) */
307
- async function judgeLogs(page, testFailed) {
308
- if (!page)
309
- return {};
310
- try {
311
- // Events and command replies share one ordered connection: after this, everything logged so far is in
312
- await page.syncLogs();
313
- }
314
- catch (err) {
315
- logger.debug("Could not sync the page's logs", { err });
116
+ /** Whether anything but the default says how many workers to use */
117
+ workersAsked(fromConfig) {
118
+ return (this.options.workers !== undefined ||
119
+ Boolean(process.env.SAMURAI_WORKERS?.trim()) ||
120
+ fromConfig !== undefined);
316
121
  }
317
- let config;
318
- try {
319
- config = await readConfig("logs");
320
- }
321
- catch (err) {
322
- logger.debug("Could not read logs config, using defaults", { err });
122
+ /** The pool of a run: up to `workers` worker processes that read the same project and settings as this run */
123
+ createPool(workers) {
124
+ const { settings } = this.run;
125
+ const { reportPath, headless, port } = this.options;
126
+ const options = {
127
+ workers,
128
+ factory: processWorkerFactory(),
129
+ init: {
130
+ projectDir: projectDir(),
131
+ ...(this.project.dataDir !== undefined && {
132
+ dataDir: this.project.dataDir,
133
+ }),
134
+ environment: settings.environment,
135
+ timeout: settings.timeout,
136
+ expectTimeout: settings.expectTimeout,
137
+ ...(headless !== undefined && { headless }),
138
+ ...(typeof reportPath === "string" && { reportPath }),
139
+ files: this.testFiles,
140
+ basePort: port ?? DEFAULT_PORT,
141
+ },
142
+ };
143
+ return (this.project.createPool ?? ((o) => new WorkerPool(o)))(options);
323
144
  }
324
- return judgePageLogs({
325
- config,
326
- entries: page.getLogs(),
327
- errors: page.pageErrors(),
328
- dropped: page.logsDropped,
329
- testFailed,
330
- allowPageErrors: page.pageErrorsAllowed,
331
- routeErrors: page.routeErrors(),
332
- });
333
145
  }
334
- export { describe, test } from "./registry.js";
@@ -0,0 +1,67 @@
1
+ import type { TestResult } from "../types/test.js";
2
+ import type { WorkerFactory, WorkerInit, WorkItem } from "./worker-protocol.js";
3
+ export type PoolEvent = {
4
+ type: "test-start";
5
+ item: WorkItem;
6
+ worker: number;
7
+ } | {
8
+ type: "test-end";
9
+ item: WorkItem;
10
+ worker: number;
11
+ result: TestResult;
12
+ };
13
+ export interface PoolOptions {
14
+ /** The cap on workers running at the same time */
15
+ workers: number;
16
+ init: Omit<WorkerInit, "workerIndex">;
17
+ factory: WorkerFactory;
18
+ /** The abort grace period in ms; 2000 by default */
19
+ graceMs?: number;
20
+ }
21
+ export interface RunHooks {
22
+ onEvent?: (event: PoolEvent) => void;
23
+ signal?: AbortSignal;
24
+ }
25
+ /**
26
+ * A fixed cap of worker processes that take items from one shared queue.
27
+ * Workers start when there is work; `run()` can be called several times at
28
+ * once and all calls share the cap.
29
+ */
30
+ export declare class WorkerPool {
31
+ private readonly options;
32
+ private readonly queue;
33
+ private readonly slots;
34
+ private readonly calls;
35
+ private readonly timers;
36
+ private nextId;
37
+ /** Workers in a row that exited before they were ready */
38
+ private startupFailures;
39
+ private disposed;
40
+ constructor(options: PoolOptions);
41
+ /** Results in item order; may be called several times at once (flows): all calls share the cap */
42
+ run(items: WorkItem[], hooks?: RunHooks): Promise<TestResult[]>;
43
+ /** Tells every worker to exit and kills it; safe to call twice */
44
+ dispose(): Promise<void>;
45
+ /** Hands queued items to ready workers, then starts workers for what is left */
46
+ private pump;
47
+ private busyKeys;
48
+ /** The queue position of the first item whose serial key is not running */
49
+ private nextEligible;
50
+ /** How many queued items could run at the same time right now */
51
+ private eligibleCount;
52
+ private freeIndex;
53
+ private start;
54
+ private dispatch;
55
+ private onMessage;
56
+ private onExit;
57
+ /** Records an item's result, tells the listener, and gives the worker its next item */
58
+ private finish;
59
+ private settle;
60
+ private emit;
61
+ /** Fails the queued items of a call, asks its busy workers to stop, and kills the ones that do not */
62
+ private abort;
63
+ private fatal;
64
+ private rejectAll;
65
+ /** Forgets every worker, then kills it, so their exits are not taken for crashes */
66
+ private stopWorkers;
67
+ }