@itmetsam/samurai-framework 0.10.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/README.md +3 -4
  2. package/dist/assert/assertion-error.d.ts +1 -1
  3. package/dist/assert/expect.d.ts +5 -50
  4. package/dist/assert/expect.js +5 -182
  5. package/dist/assert/locator-assertions.d.ts +22 -0
  6. package/dist/assert/locator-assertions.js +61 -0
  7. package/dist/assert/pending-assertions.d.ts +7 -0
  8. package/dist/assert/pending-assertions.js +40 -0
  9. package/dist/assert/text-match.d.ts +4 -0
  10. package/dist/assert/text-match.js +11 -0
  11. package/dist/assert/value-assertions.d.ts +23 -0
  12. package/dist/assert/value-assertions.js +82 -0
  13. package/dist/browser/browser-context.d.ts +0 -1
  14. package/dist/browser/browser-context.js +13 -5
  15. package/dist/browser/browser-finder.js +5 -9
  16. package/dist/browser/browser.d.ts +3 -31
  17. package/dist/browser/browser.js +36 -219
  18. package/dist/browser/chrome-launch.d.ts +2 -1
  19. package/dist/browser/dialog-handling.d.ts +32 -0
  20. package/dist/browser/dialog-handling.js +85 -0
  21. package/dist/browser/document-watcher.d.ts +44 -0
  22. package/dist/browser/document-watcher.js +160 -0
  23. package/dist/browser/download-folders.d.ts +0 -1
  24. package/dist/browser/download-folders.js +7 -15
  25. package/dist/browser/download.js +5 -10
  26. package/dist/browser/downloads-dir.js +2 -2
  27. package/dist/browser/emit.d.ts +7 -0
  28. package/dist/browser/emit.js +16 -0
  29. package/dist/browser/emulation.d.ts +1 -1
  30. package/dist/browser/emulation.js +54 -18
  31. package/dist/browser/file-chooser.js +4 -11
  32. package/dist/browser/history.d.ts +6 -0
  33. package/dist/browser/history.js +89 -0
  34. package/dist/browser/index.d.ts +2 -0
  35. package/dist/browser/index.js +2 -0
  36. package/dist/browser/init-script.d.ts +2 -0
  37. package/dist/browser/init-script.js +16 -0
  38. package/dist/browser/launch-firefox.d.ts +20 -0
  39. package/dist/browser/launch-firefox.js +66 -0
  40. package/dist/browser/launch-flags.d.ts +18 -0
  41. package/dist/browser/launch-flags.js +100 -0
  42. package/dist/browser/lazy.d.ts +21 -0
  43. package/dist/browser/lazy.js +44 -0
  44. package/dist/browser/page-events.d.ts +44 -0
  45. package/dist/browser/page-events.js +58 -0
  46. package/dist/browser/page-logs.js +2 -9
  47. package/dist/browser/page-navigation.d.ts +58 -0
  48. package/dist/browser/page-navigation.js +108 -0
  49. package/dist/browser/page-network.d.ts +69 -0
  50. package/dist/browser/page-network.js +163 -0
  51. package/dist/browser/page-settings.d.ts +35 -0
  52. package/dist/browser/page-settings.js +51 -0
  53. package/dist/browser/page-transfers.d.ts +36 -0
  54. package/dist/browser/page-transfers.js +50 -0
  55. package/dist/browser/page.d.ts +39 -154
  56. package/dist/browser/page.js +134 -758
  57. package/dist/browser/pdf.d.ts +1 -1
  58. package/dist/browser/session.d.ts +16 -0
  59. package/dist/browser/session.js +32 -0
  60. package/dist/browser/viewport.d.ts +19 -0
  61. package/dist/browser/viewport.js +72 -0
  62. package/dist/browser/warm-up.d.ts +1 -1
  63. package/dist/browser/warm-up.js +2 -1
  64. package/dist/browsers/index.d.ts +1 -1
  65. package/dist/browsers/index.js +1 -1
  66. package/dist/browsers/pins.d.ts +0 -2
  67. package/dist/browsers/pins.js +0 -2
  68. package/dist/browsers/store.d.ts +0 -2
  69. package/dist/browsers/store.js +1 -2
  70. package/dist/cli/args.d.ts +13 -6
  71. package/dist/cli/args.js +13 -10
  72. package/dist/cli/browsers.d.ts +1 -2
  73. package/dist/cli/browsers.js +2 -2
  74. package/dist/cli/console-reporter.js +3 -2
  75. package/dist/cli/flow-reporter.js +2 -1
  76. package/dist/cli/flow.d.ts +1 -2
  77. package/dist/cli/flow.js +25 -22
  78. package/dist/cli/init.js +2 -7
  79. package/dist/cli/io.d.ts +10 -0
  80. package/dist/cli/io.js +1 -0
  81. package/dist/cli/main.d.ts +3 -10
  82. package/dist/cli/main.js +10 -143
  83. package/dist/cli/paint.d.ts +2 -0
  84. package/dist/cli/paint.js +4 -0
  85. package/dist/cli/record.d.ts +4 -0
  86. package/dist/cli/record.js +93 -0
  87. package/dist/cli/usage.d.ts +1 -0
  88. package/dist/cli/usage.js +58 -0
  89. package/dist/cli/version.d.ts +2 -0
  90. package/dist/cli/version.js +7 -0
  91. package/dist/config/browsers.d.ts +11 -0
  92. package/dist/config/browsers.js +32 -0
  93. package/dist/config/config.js +4 -1
  94. package/dist/config/mask.d.ts +1 -1
  95. package/dist/config/mask.js +7 -2
  96. package/dist/config/run-settings.d.ts +1 -2
  97. package/dist/config/run-settings.js +1 -6
  98. package/dist/config/timeouts.d.ts +2 -0
  99. package/dist/config/timeouts.js +13 -0
  100. package/dist/config/workers.d.ts +14 -0
  101. package/dist/config/workers.js +29 -0
  102. package/dist/flows/run/branch-context.d.ts +14 -0
  103. package/dist/flows/run/branch-context.js +35 -0
  104. package/dist/flows/run/handlers/condition.d.ts +1 -1
  105. package/dist/flows/run/handlers/end.d.ts +1 -1
  106. package/dist/flows/run/handlers/group.d.ts +1 -1
  107. package/dist/flows/run/handlers/index.d.ts +2 -2
  108. package/dist/flows/run/handlers/parallel.d.ts +1 -1
  109. package/dist/flows/run/handlers/start.d.ts +1 -1
  110. package/dist/flows/run/handlers/support.d.ts +3 -27
  111. package/dist/flows/run/handlers/test.d.ts +1 -1
  112. package/dist/flows/run/handlers/types.d.ts +27 -0
  113. package/dist/flows/run/handlers/types.js +1 -0
  114. package/dist/flows/run/index.d.ts +1 -1
  115. package/dist/flows/run/prepare-flow.d.ts +14 -0
  116. package/dist/flows/run/prepare-flow.js +44 -0
  117. package/dist/flows/run/project.d.ts +33 -2
  118. package/dist/flows/run/project.js +139 -57
  119. package/dist/flows/run/run.d.ts +3 -4
  120. package/dist/flows/run/run.js +23 -193
  121. package/dist/flows/run/walker.d.ts +56 -0
  122. package/dist/flows/run/walker.js +225 -0
  123. package/dist/flows/steps/catalog.d.ts +1 -1
  124. package/dist/flows/steps/handler.d.ts +1 -1
  125. package/dist/flows/steps/set.d.ts +1 -1
  126. package/dist/index.js +1 -1
  127. package/dist/locator/actionability.d.ts +33 -0
  128. package/dist/locator/actionability.js +104 -0
  129. package/dist/locator/chain-resolver.d.ts +20 -0
  130. package/dist/locator/chain-resolver.js +102 -0
  131. package/dist/locator/document-swap.d.ts +1 -0
  132. package/dist/locator/document-swap.js +11 -0
  133. package/dist/locator/element-state.d.ts +3 -1
  134. package/dist/locator/element-state.js +11 -3
  135. package/dist/locator/file-actions.d.ts +35 -0
  136. package/dist/locator/file-actions.js +55 -0
  137. package/dist/{script/helpers.js → locator/helper-realm.js} +2 -2
  138. package/dist/locator/input-actions.d.ts +18 -0
  139. package/dist/locator/input-actions.js +80 -0
  140. package/dist/locator/label-locator.d.ts +1 -1
  141. package/dist/locator/label-locator.js +5 -12
  142. package/dist/locator/locator.d.ts +14 -37
  143. package/dist/locator/locator.js +68 -424
  144. package/dist/locator/page-scripts.d.ts +16 -0
  145. package/dist/locator/page-scripts.js +54 -0
  146. package/dist/locator/page-search.d.ts +10 -0
  147. package/dist/locator/page-search.js +19 -0
  148. package/dist/locator/selector-errors.d.ts +0 -4
  149. package/dist/locator/selector-errors.js +0 -7
  150. package/dist/locator/selector.d.ts +14 -13
  151. package/dist/locator/selector.js +10 -16
  152. package/dist/locator/text-locator.d.ts +1 -1
  153. package/dist/locator/text-locator.js +5 -9
  154. package/dist/logger/index.d.ts +6 -0
  155. package/dist/logger/index.js +14 -7
  156. package/dist/network/bidi-wire.d.ts +10 -0
  157. package/dist/network/bidi-wire.js +38 -0
  158. package/dist/network/intercept.d.ts +30 -0
  159. package/dist/network/intercept.js +130 -0
  160. package/dist/network/network-tracker.d.ts +10 -47
  161. package/dist/network/network-tracker.js +34 -68
  162. package/dist/network/network-types.d.ts +33 -0
  163. package/dist/network/network-types.js +1 -0
  164. package/dist/network/network-wait.d.ts +7 -2
  165. package/dist/network/network-wait.js +14 -4
  166. package/dist/network/response.d.ts +1 -1
  167. package/dist/network/response.js +1 -1
  168. package/dist/network/route.d.ts +92 -0
  169. package/dist/network/route.js +142 -0
  170. package/dist/network/router.d.ts +6 -91
  171. package/dist/network/router.js +25 -258
  172. package/dist/recorder/expectation.d.ts +5 -0
  173. package/dist/recorder/expectation.js +20 -0
  174. package/dist/recorder/files-recorder.d.ts +30 -0
  175. package/dist/recorder/files-recorder.js +88 -0
  176. package/dist/recorder/normalise.d.ts +17 -6
  177. package/dist/recorder/normalise.js +96 -91
  178. package/dist/recorder/options.d.ts +56 -0
  179. package/dist/recorder/options.js +1 -0
  180. package/dist/recorder/page-messages.d.ts +35 -0
  181. package/dist/recorder/page-messages.js +26 -0
  182. package/dist/recorder/ranking.js +1 -0
  183. package/dist/recorder/realms.d.ts +6 -0
  184. package/dist/recorder/realms.js +13 -0
  185. package/dist/recorder/recorder.d.ts +10 -74
  186. package/dist/recorder/recorder.js +39 -199
  187. package/dist/recorder/session-browser.d.ts +22 -0
  188. package/dist/recorder/session-browser.js +21 -0
  189. package/dist/recorder/session-secrets.d.ts +8 -0
  190. package/dist/recorder/session-secrets.js +45 -0
  191. package/dist/recorder/session.d.ts +5 -14
  192. package/dist/recorder/session.js +22 -120
  193. package/dist/recorder/spec-source.d.ts +39 -0
  194. package/dist/recorder/spec-source.js +84 -0
  195. package/dist/recorder/toolbar/action-guard.d.ts +21 -0
  196. package/dist/recorder/toolbar/action-guard.js +43 -0
  197. package/dist/recorder/toolbar/actions/assert.d.ts +4 -0
  198. package/dist/recorder/toolbar/actions/assert.js +52 -0
  199. package/dist/recorder/toolbar/actions/insert.d.ts +6 -0
  200. package/dist/recorder/toolbar/actions/insert.js +93 -0
  201. package/dist/recorder/toolbar/actions/pick.d.ts +8 -0
  202. package/dist/recorder/toolbar/actions/pick.js +26 -0
  203. package/dist/recorder/toolbar/actions/reply.d.ts +7 -0
  204. package/dist/recorder/toolbar/actions/reply.js +10 -0
  205. package/dist/recorder/toolbar/controller-fixture.d.ts +47 -0
  206. package/dist/recorder/toolbar/controller-fixture.js +129 -0
  207. package/dist/recorder/toolbar/controller.d.ts +12 -78
  208. package/dist/recorder/toolbar/controller.js +35 -247
  209. package/dist/recorder/toolbar/inspect.d.ts +1 -3
  210. package/dist/recorder/toolbar/inspect.js +2 -2
  211. package/dist/recorder/toolbar/page-scope.d.ts +19 -0
  212. package/dist/recorder/toolbar/page-scope.js +43 -0
  213. package/dist/recorder/toolbar/protocol.d.ts +2 -1
  214. package/dist/recorder/toolbar/source/attach.d.ts +2 -0
  215. package/dist/recorder/toolbar/source/attach.js +41 -0
  216. package/dist/recorder/toolbar/source/dom.d.ts +2 -0
  217. package/dist/recorder/toolbar/source/dom.js +47 -0
  218. package/dist/recorder/toolbar/source/drag.d.ts +2 -0
  219. package/dist/recorder/toolbar/source/drag.js +32 -0
  220. package/dist/recorder/toolbar/source/events-page.d.ts +2 -0
  221. package/dist/recorder/toolbar/source/events-page.js +67 -0
  222. package/dist/recorder/toolbar/source/events-toolbar.d.ts +2 -0
  223. package/dist/recorder/toolbar/source/events-toolbar.js +35 -0
  224. package/dist/recorder/toolbar/source/fields.d.ts +2 -0
  225. package/dist/recorder/toolbar/source/fields.js +44 -0
  226. package/dist/recorder/toolbar/source/handlers.d.ts +2 -0
  227. package/dist/recorder/toolbar/source/handlers.js +134 -0
  228. package/dist/recorder/toolbar/source/names.d.ts +2 -0
  229. package/dist/recorder/toolbar/source/names.js +12 -0
  230. package/dist/recorder/toolbar/source/picking.d.ts +2 -0
  231. package/dist/recorder/toolbar/source/picking.js +72 -0
  232. package/dist/recorder/toolbar/source/placement.d.ts +2 -0
  233. package/dist/recorder/toolbar/source/placement.js +45 -0
  234. package/dist/recorder/toolbar/source/recorder-side.d.ts +2 -0
  235. package/dist/recorder/toolbar/source/recorder-side.js +54 -0
  236. package/dist/recorder/toolbar/source/setup.d.ts +2 -0
  237. package/dist/recorder/toolbar/source/setup.js +50 -0
  238. package/dist/recorder/toolbar/source/views-panels.d.ts +2 -0
  239. package/dist/recorder/toolbar/source/views-panels.js +150 -0
  240. package/dist/recorder/toolbar/source/views-rail.d.ts +2 -0
  241. package/dist/recorder/toolbar/source/views-rail.js +40 -0
  242. package/dist/recorder/toolbar/source/views-render.d.ts +2 -0
  243. package/dist/recorder/toolbar/source/views-render.js +48 -0
  244. package/dist/recorder/toolbar/source.js +30 -856
  245. package/dist/recorder/toolbar/types.d.ts +70 -0
  246. package/dist/recorder/toolbar/types.js +1 -0
  247. package/dist/recorder/toolbar-bridge.d.ts +43 -0
  248. package/dist/recorder/toolbar-bridge.js +106 -0
  249. package/dist/runner/browsers.d.ts +0 -12
  250. package/dist/runner/browsers.js +1 -33
  251. package/dist/runner/options.d.ts +31 -0
  252. package/dist/runner/options.js +1 -0
  253. package/dist/runner/pooled-run.d.ts +43 -0
  254. package/dist/runner/pooled-run.js +75 -0
  255. package/dist/runner/ports.d.ts +2 -0
  256. package/dist/runner/ports.js +2 -0
  257. package/dist/runner/registry.d.ts +1 -1
  258. package/dist/runner/registry.js +1 -1
  259. package/dist/runner/reporter.d.ts +31 -10
  260. package/dist/runner/reporter.js +39 -6
  261. package/dist/runner/run-test.d.ts +16 -0
  262. package/dist/runner/run-test.js +178 -0
  263. package/dist/runner/run.d.ts +5 -3
  264. package/dist/runner/run.js +28 -29
  265. package/dist/runner/select.d.ts +24 -0
  266. package/dist/runner/select.js +77 -0
  267. package/dist/runner/test-dir.d.ts +13 -0
  268. package/dist/runner/test-dir.js +27 -0
  269. package/dist/runner/test-runner.d.ts +25 -56
  270. package/dist/runner/test-runner.js +81 -270
  271. package/dist/runner/worker-pool.d.ts +67 -0
  272. package/dist/runner/worker-pool.js +275 -0
  273. package/dist/runner/worker-process.d.ts +6 -0
  274. package/dist/runner/worker-process.js +54 -0
  275. package/dist/runner/worker-protocol.d.ts +67 -0
  276. package/dist/runner/worker-protocol.js +1 -0
  277. package/dist/runner/worker-session.d.ts +7 -0
  278. package/dist/runner/worker-session.js +60 -0
  279. package/dist/runner/worker.d.ts +26 -0
  280. package/dist/runner/worker.js +121 -0
  281. package/dist/runner/workers-option.d.ts +15 -0
  282. package/dist/runner/workers-option.js +22 -0
  283. package/dist/script/call-function.d.ts +1 -9
  284. package/dist/script/call-function.js +2 -19
  285. package/dist/script/element-handle.d.ts +1 -2
  286. package/dist/script/element-handle.js +1 -2
  287. package/dist/script/serialize.d.ts +3 -12
  288. package/dist/script/serialize.js +53 -62
  289. package/dist/steps/parse/call.d.ts +3 -0
  290. package/dist/steps/parse/call.js +81 -0
  291. package/dist/steps/parse/expectation.d.ts +5 -0
  292. package/dist/steps/parse/expectation.js +71 -0
  293. package/dist/steps/parse/file-entry.d.ts +4 -0
  294. package/dist/steps/parse/file-entry.js +74 -0
  295. package/dist/steps/parse/literals.d.ts +4 -0
  296. package/dist/steps/parse/literals.js +28 -0
  297. package/dist/steps/parse/step.d.ts +4 -0
  298. package/dist/steps/parse/step.js +81 -0
  299. package/dist/steps/parse.js +2 -323
  300. package/dist/transport/bidi-connection.d.ts +4 -15
  301. package/dist/transport/bidi-connection.js +9 -63
  302. package/dist/transport/log-redaction.d.ts +13 -0
  303. package/dist/transport/log-redaction.js +51 -0
  304. package/dist/transport/subscriptions.js +18 -13
  305. package/dist/transport/unsupported.d.ts +7 -0
  306. package/dist/transport/unsupported.js +13 -0
  307. package/dist/types/bidi-modules/browser.d.ts +1 -12
  308. package/dist/types/bidi-modules/browsing-context.d.ts +8 -18
  309. package/dist/types/bidi-modules/emulation.d.ts +29 -21
  310. package/dist/types/bidi-modules/input.d.ts +1 -9
  311. package/dist/types/bidi-modules/network.d.ts +29 -24
  312. package/dist/types/bidi-modules/script.d.ts +0 -9
  313. package/dist/types/bidi-modules/session.d.ts +0 -8
  314. package/dist/types/bidi-modules/storage.d.ts +0 -5
  315. package/dist/types/bidi.d.ts +7 -37
  316. package/dist/types/config.d.ts +12 -0
  317. package/dist/types/test.d.ts +8 -3
  318. package/dist/wait/wait-until.d.ts +1 -5
  319. package/dist/wait/wait-until.js +7 -16
  320. package/package.json +3 -2
  321. package/dist/config/default.d.ts +0 -2
  322. package/dist/config/default.js +0 -3
  323. package/dist/types/bidi-modules/web-extensions.d.ts +0 -9
  324. package/dist/types/bidi-protocols/command.d.ts +0 -7
  325. package/dist/types/bidi-protocols/event.d.ts +0 -17
  326. /package/dist/{script/helpers.d.ts → locator/helper-realm.d.ts} +0 -0
@@ -0,0 +1,178 @@
1
+ import path from "path";
2
+ import { takePendingAssertions } from "../assert/expect.js";
3
+ import { Browser } from "../browser/browser.js";
4
+ import { Evidence } from "../browser/evidence.js";
5
+ import { projectDir, readConfig } from "../config/config.js";
6
+ import { createSecretsFixture } from "../config/secrets.js";
7
+ import { createEnvFixture } from "../config/variables.js";
8
+ import logger from "../logger/index.js";
9
+ import { ConnectionClosedError } from "../transport/bidi-connection.js";
10
+ import { judgePageLogs } from "./page-logs-report.js";
11
+ import { DEFAULT_PORT } from "./ports.js";
12
+ import { testDirOf } from "./test-dir.js";
13
+ import { toTestError } from "./test-error.js";
14
+ /**
15
+ * The state of one test run. A test ends once, whichever of finishing, failing, timing out, an abort or a
16
+ * closed browser comes first: that one is reported and the rest are ignored.
17
+ */
18
+ class TestOutcome {
19
+ constructor(reporter, test, browser) {
20
+ this.reporter = reporter;
21
+ this.test = test;
22
+ this.browser = browser;
23
+ /** The test has been reported: later calls to `finish` do nothing */
24
+ this.ended = false;
25
+ /** The runner itself is closing the browser: its exit is expected and is not a failure */
26
+ this.closing = false;
27
+ /** A person closed the browser, or it crashed, while the test ran */
28
+ this.browserClosed = false;
29
+ }
30
+ get isEnded() {
31
+ return this.ended;
32
+ }
33
+ finish(error, logs) {
34
+ if (this.ended)
35
+ return;
36
+ this.ended = true;
37
+ this.reporter.onTestEnd(this.test, this.browser, error, {
38
+ ...logs,
39
+ screenshots: this.evidence?.screenshots,
40
+ });
41
+ }
42
+ }
43
+ /** Runs one test in one browser and reports it: launch, run the body, judge the page's logs, close the browser */
44
+ export async function runOneTest(test, browserName, run, options, srcDir, reporter, resources = {}) {
45
+ logger.verbose("Starting test: %s in %s", test.name, browserName);
46
+ reporter.onTestStart(test, browserName);
47
+ const outcome = new TestOutcome(reporter, test, browserName);
48
+ const { settings, secrets } = run;
49
+ const timeout = settings.timeout;
50
+ const { reportPath } = options;
51
+ const resultDir = typeof reportPath === "string"
52
+ ? path.dirname(reportPath)
53
+ : path.join(projectDir(), "result");
54
+ let browser;
55
+ let page;
56
+ let timeoutTimer;
57
+ let onRunAborted;
58
+ const timeoutController = new AbortController();
59
+ let browserGone = () => { };
60
+ /** Resolves when the time is up or the run is aborted; the test is reported failed then */
61
+ const limit = new Promise((resolve) => {
62
+ timeoutTimer = setTimeout(() => {
63
+ outcome.finish({
64
+ message: `Test timed out after ${timeout / 1000} seconds`,
65
+ type: "timeout",
66
+ });
67
+ timeoutController.abort(new Error("Test timed out"));
68
+ resolve();
69
+ }, timeout);
70
+ onRunAborted = () => {
71
+ if (timeoutController.signal.aborted)
72
+ return;
73
+ outcome.finish({ message: "Run aborted", type: "error" });
74
+ timeoutController.abort(new Error("Run aborted"));
75
+ resolve();
76
+ };
77
+ options.signal?.addEventListener("abort", onRunAborted, { once: true });
78
+ });
79
+ /** Resolves when the browser exits on its own while the test runs */
80
+ const gone = new Promise((resolve) => (browserGone = resolve));
81
+ /** Launches the browser, runs the test body and reports how it went */
82
+ const runBody = async () => {
83
+ try {
84
+ const launched = await Browser.launch(browserName, {
85
+ port: resources.port ?? options.port ?? DEFAULT_PORT,
86
+ ...(resources.profileDir && { profileDir: resources.profileDir }),
87
+ headless: options.headless ?? settings.headless,
88
+ }, timeoutController.signal);
89
+ browser = launched.browser;
90
+ // A person closing the window, or the browser crashing: end the test now instead of waiting for a
91
+ // command to time out
92
+ void launched.browser.exited.then(() => {
93
+ if (outcome.isEnded || outcome.closing)
94
+ return;
95
+ outcome.browserClosed = true;
96
+ outcome.finish({
97
+ message: "The browser was closed while the test was running",
98
+ type: "error",
99
+ });
100
+ timeoutController.abort(new Error("Browser closed"));
101
+ browserGone();
102
+ });
103
+ page = launched.page;
104
+ outcome.evidence = new Evidence(resultDir, `${browserName}/${testDirOf(test, srcDir)}`);
105
+ launched.page.setEvidence(outcome.evidence);
106
+ const fixtures = {
107
+ page: launched.page,
108
+ browser: launched.browser,
109
+ browserName,
110
+ workerIndex: resources.workerIndex ?? 0,
111
+ env: createEnvFixture(settings.environment, settings.variables),
112
+ secrets: createSecretsFixture(settings.environment, secrets),
113
+ };
114
+ await test.function(fixtures);
115
+ // Already reported as timed out
116
+ if (timeoutController.signal.aborted)
117
+ return;
118
+ const unawaited = takePendingAssertions();
119
+ if (unawaited.length > 0) {
120
+ outcome.finish({
121
+ message: unawaited
122
+ .map((matcher) => `expect(locator).${matcher}() was not awaited`)
123
+ .join("\n"),
124
+ type: "assertion",
125
+ }, await judgeLogs(page, true));
126
+ return;
127
+ }
128
+ const verdict = await judgeLogs(page, false);
129
+ outcome.finish(verdict.error, verdict);
130
+ }
131
+ catch (err) {
132
+ if (timeoutController.signal.aborted)
133
+ return;
134
+ outcome.finish(outcome.browserClosed || err instanceof ConnectionClosedError
135
+ ? {
136
+ message: "The browser was closed while the test was running",
137
+ type: "error",
138
+ }
139
+ : toTestError(err), await judgeLogs(page, true));
140
+ }
141
+ };
142
+ await Promise.race([limit, gone, runBody()]);
143
+ clearTimeout(timeoutTimer);
144
+ if (onRunAborted)
145
+ options.signal?.removeEventListener("abort", onRunAborted);
146
+ takePendingAssertions();
147
+ logger.verbose("Closing browser for test: %s", test.name);
148
+ outcome.closing = true;
149
+ await browser?.close();
150
+ }
151
+ /** Reads the page's log once the test body is done and applies config `logs` (see judgePageLogs) */
152
+ async function judgeLogs(page, testFailed) {
153
+ if (!page)
154
+ return {};
155
+ try {
156
+ // Events and command replies share one ordered connection: after this, everything logged so far is in
157
+ await page.syncLogs();
158
+ }
159
+ catch (err) {
160
+ logger.debug("Could not sync the page's logs", { err });
161
+ }
162
+ let config;
163
+ try {
164
+ config = await readConfig("logs");
165
+ }
166
+ catch (err) {
167
+ logger.debug("Could not read logs config, using defaults", { err });
168
+ }
169
+ return judgePageLogs({
170
+ config,
171
+ entries: page.getLogs(),
172
+ errors: page.pageErrors(),
173
+ dropped: page.logsDropped,
174
+ testFailed,
175
+ allowPageErrors: page.pageErrorsAllowed,
176
+ routeErrors: page.routeErrors(),
177
+ });
178
+ }
@@ -1,7 +1,7 @@
1
1
  import type { TestSummary } from "../types/test.js";
2
2
  import { type ProjectOptions } from "./project.js";
3
3
  import type { ResolvedGroup } from "./groups.js";
4
- import { type RunnerOptions } from "./test-runner.js";
4
+ import type { RunnerOptions } from "./options.js";
5
5
  export interface RunTestsOptions extends RunnerOptions, ProjectOptions {
6
6
  }
7
7
  /**
@@ -24,9 +24,11 @@ export interface FoundTest {
24
24
  export declare function listTests(options?: RunTestsOptions): Promise<FoundTest[]>;
25
25
  /** Imports the project's specs without running anything and lists the config's groups with the tests each holds */
26
26
  export declare function listGroups(options?: RunTestsOptions): Promise<ResolvedGroup[]>;
27
- export { BROWSER_NAMES, checkBrowserList } from "./browsers.js";
27
+ export { BROWSER_NAMES, checkBrowserList } from "../config/browsers.js";
28
+ export { parseWorkers } from "../config/workers.js";
28
29
  export type { RunEvent } from "./reporter.js";
29
30
  export type { TestError, TestResult, TestSummary } from "../types/test.js";
30
31
  export type { ResolvedGroup } from "./groups.js";
31
- export type { RunnerOptions } from "./test-runner.js";
32
+ export type { RunnerOptions } from "./options.js";
32
33
  export type { ProjectOptions } from "./project.js";
34
+ export { DEFAULT_PORT } from "./ports.js";
@@ -1,5 +1,26 @@
1
1
  import { withProject } from "./project.js";
2
- import TestRunner, {} from "./test-runner.js";
2
+ import TestRunner from "./test-runner.js";
3
+ /**
4
+ * Sets the project up (config, environment, secrets), builds a runner from the rest of the options and hands both to
5
+ * `work`. `headless` stays with the runner options on purpose: the runner reads it, and the project's run settings
6
+ * (`withProject`'s own `headless`) are left to the config.
7
+ */
8
+ function inProject(options, work) {
9
+ const { projectDir, config, dataDir, environment, timeout, expectTimeout, ...runner } = options;
10
+ return withProject(
11
+ // The cast is for `exactOptionalPropertyTypes`: these may be undefined, which the project options leave out
12
+ {
13
+ projectDir,
14
+ config,
15
+ dataDir,
16
+ environment,
17
+ timeout,
18
+ expectTimeout,
19
+ }, async (run) => work(await TestRunner.init(run, runner, {
20
+ inlineConfig: config !== undefined,
21
+ ...(dataDir !== undefined && { dataDir }),
22
+ })));
23
+ }
3
24
  /**
4
25
  * Runs the project's tests, one after the other, and resolves with the summary (also written to the report).
5
26
  * Spec files are TypeScript: the process must be able to import them (run under `tsx`, or with Node's own
@@ -10,38 +31,16 @@ import TestRunner, {} from "./test-runner.js";
10
31
  * onEvent: (event) => console.log(event.type) });
11
32
  */
12
33
  export async function runTests(options = {}) {
13
- const { projectDir, config, dataDir, environment, timeout, expectTimeout, ...runner } = options;
14
- return withProject({
15
- projectDir,
16
- config,
17
- dataDir,
18
- environment,
19
- timeout,
20
- expectTimeout,
21
- }, async (run) => (await TestRunner.init(run, undefined, runner)).start());
34
+ return inProject(options, (runner) => runner.start());
22
35
  }
23
36
  /** Imports the project's specs without running anything and lists the tests the options select */
24
37
  export async function listTests(options = {}) {
25
- const { projectDir, config, dataDir, environment, timeout, expectTimeout, ...runner } = options;
26
- return withProject({
27
- projectDir,
28
- config,
29
- dataDir,
30
- environment,
31
- timeout,
32
- expectTimeout,
33
- }, async (run) => (await (await TestRunner.init(run, undefined, runner)).register()).map(({ name, file }) => ({ name, file })));
38
+ return inProject(options, async (runner) => (await runner.register()).map(({ name, file }) => ({ name, file })));
34
39
  }
35
40
  /** Imports the project's specs without running anything and lists the config's groups with the tests each holds */
36
41
  export async function listGroups(options = {}) {
37
- const { projectDir, config, dataDir, environment, timeout, expectTimeout, ...runner } = options;
38
- return withProject({
39
- projectDir,
40
- config,
41
- dataDir,
42
- environment,
43
- timeout,
44
- expectTimeout,
45
- }, async (run) => (await TestRunner.init(run, undefined, runner)).groups());
42
+ return inProject(options, (runner) => runner.groups());
46
43
  }
47
- export { BROWSER_NAMES, checkBrowserList } from "./browsers.js";
44
+ export { BROWSER_NAMES, checkBrowserList } from "../config/browsers.js";
45
+ export { parseWorkers } from "../config/workers.js";
46
+ export { DEFAULT_PORT } from "./ports.js";
@@ -0,0 +1,24 @@
1
+ import type { SupportedBrowser } from "../types/browser.js";
2
+ import type { RegisteredTestCase } from "../types/test.js";
3
+ import { type ResolvedGroup } from "./groups.js";
4
+ import type { RunnerOptions } from "./options.js";
5
+ /** The specs a run imported, with the config's groups resolved against them */
6
+ export interface LoadedSpecs {
7
+ all: RegisteredTestCase[];
8
+ groups: ResolvedGroup[];
9
+ /** The real `srcDir` the groups were resolved against */
10
+ srcDir: string;
11
+ }
12
+ /** Whether a test with this full name is selected by the options */
13
+ export declare function isSelected(name: string, { grep, testNames }: Pick<RunnerOptions, "grep" | "testNames">): boolean;
14
+ /** Id of a registered test: its spec file relative to `srcDir`, then its name */
15
+ export declare function idOf(test: RegisteredTestCase, srcDir: string): string;
16
+ /** Imports the spec files once and resolves the config's groups against what they registered */
17
+ export declare function loadSpecs(testFiles: string[]): Promise<LoadedSpecs>;
18
+ /** The tests the options select: by `group`, `testNames` and `grep` */
19
+ export declare function selectTests({ all, groups, srcDir }: LoadedSpecs, options: RunnerOptions): RegisteredTestCase[];
20
+ /**
21
+ * The browsers the run uses: the options' (`--browser`), else the selected group's, else the project's, else
22
+ * Firefox
23
+ */
24
+ export declare function browsersOfRun(groups: ResolvedGroup[], options: RunnerOptions): Promise<SupportedBrowser[]>;
@@ -0,0 +1,77 @@
1
+ import path from "path";
2
+ import { pathToFileURL } from "url";
3
+ import { loadConfig } from "../config/config.js";
4
+ import logger from "../logger/index.js";
5
+ import { browsersFor } from "./browsers.js";
6
+ import { findGroup, resolveGroups, testId, toPosix, } from "./groups.js";
7
+ import { assertModuleProject } from "./module-type.js";
8
+ import { clearRegistry, registeredTests } from "./registry.js";
9
+ import { realDirs } from "./test-dir.js";
10
+ /** Whether a test with this full name is selected by the options */
11
+ export function isSelected(name, { grep, testNames }) {
12
+ if (testNames && !testNames.includes(name))
13
+ return false;
14
+ if (grep === undefined)
15
+ return true;
16
+ if (typeof grep === "string")
17
+ return name.includes(grep);
18
+ grep.lastIndex = 0;
19
+ return grep.test(name);
20
+ }
21
+ /**
22
+ * Imports the spec files and returns every test they register. A fresh registry and a fresh import of each
23
+ * file, so a second run in this process registers its tests again (and sees edited specs, for files loaded as
24
+ * ES modules; CommonJS files stay cached until the process ends)
25
+ */
26
+ async function registerAll(testFiles) {
27
+ clearRegistry();
28
+ const stamp = `?run=${Date.now().toString(36)}`;
29
+ for (const file of testFiles)
30
+ assertModuleProject(file);
31
+ for (const file of testFiles) {
32
+ logger.verbose("Trying to register file: %s", file);
33
+ await import(pathToFileURL(file).href + stamp);
34
+ }
35
+ return [...registeredTests()];
36
+ }
37
+ /** Id of a registered test: its spec file relative to `srcDir`, then its name */
38
+ export function idOf(test, srcDir) {
39
+ return testId(toPosix(path.relative(srcDir, test.file)), test.name);
40
+ }
41
+ /** The config's groups, resolved against the tests the specs register */
42
+ async function resolveAll(tests) {
43
+ const config = await loadConfig();
44
+ const { dir, srcDir } = realDirs(config.srcDir);
45
+ const groups = resolveGroups(config, tests.map(({ file, name }) => ({
46
+ file: toPosix(path.relative(srcDir, file)),
47
+ name,
48
+ })), dir, srcDir);
49
+ return { groups, srcDir };
50
+ }
51
+ /** Imports the spec files once and resolves the config's groups against what they registered */
52
+ export async function loadSpecs(testFiles) {
53
+ const all = await registerAll(testFiles);
54
+ return { all, ...(await resolveAll(all)) };
55
+ }
56
+ /** The tests the options select: by `group`, `testNames` and `grep` */
57
+ export function selectTests({ all, groups, srcDir }, options) {
58
+ const { group } = options;
59
+ let inGroup = () => true;
60
+ if (group !== undefined) {
61
+ const members = new Set(findGroup(groups, group).testIds);
62
+ inGroup = (test) => members.has(idOf(test, srcDir));
63
+ }
64
+ return all.filter((test) => inGroup(test) && isSelected(test.name, options));
65
+ }
66
+ /**
67
+ * The browsers the run uses: the options' (`--browser`), else the selected group's, else the project's, else
68
+ * Firefox
69
+ */
70
+ export async function browsersOfRun(groups, options) {
71
+ const { browsers: flag, group } = options;
72
+ const config = await loadConfig();
73
+ const ofGroup = group !== undefined && !(flag && flag.length > 0)
74
+ ? findGroup(groups, group).browsers
75
+ : undefined;
76
+ return browsersFor({ flag, group: ofGroup, project: config.browsers });
77
+ }
@@ -0,0 +1,13 @@
1
+ import type { RegisteredTestCase } from "../types/test.js";
2
+ /** The project and source folders as real paths: registered test files come from stack traces, which resolve symlinks */
3
+ export declare function realDirs(configuredSrcDir: string | undefined): {
4
+ dir: string;
5
+ srcDir: string;
6
+ };
7
+ /**
8
+ * The folder of a test's screenshots below `screenshots/`: its spec file without extension, then its cleaned
9
+ * name. A spec outside srcDir loses the `..` (and a drive or root) its relative path starts with, so it stays
10
+ * below `screenshots/`.
11
+ * @internal Exported for tests
12
+ */
13
+ export declare function testDirOf(test: RegisteredTestCase, srcDir: string): string;
@@ -0,0 +1,27 @@
1
+ import path from "path";
2
+ import { projectDir } from "../config/config.js";
3
+ import { cleanName } from "../browser/evidence.js";
4
+ import { DEFAULT_SRC_DIR, realPath, toPosix } from "./groups.js";
5
+ /** The project and source folders as real paths: registered test files come from stack traces, which resolve symlinks */
6
+ export function realDirs(configuredSrcDir) {
7
+ const dir = realPath(projectDir());
8
+ const srcDir = realPath(path.resolve(dir, configuredSrcDir ?? DEFAULT_SRC_DIR));
9
+ return { dir, srcDir };
10
+ }
11
+ /**
12
+ * The folder of a test's screenshots below `screenshots/`: its spec file without extension, then its cleaned
13
+ * name. A spec outside srcDir loses the `..` (and a drive or root) its relative path starts with, so it stays
14
+ * below `screenshots/`.
15
+ * @internal Exported for tests
16
+ */
17
+ export function testDirOf(test, srcDir) {
18
+ const file = toPosix(path.relative(srcDir, test.file))
19
+ .replace(/\.[cm]?[jt]sx?$/, "")
20
+ .split("/")
21
+ .filter((segment) => segment !== "" &&
22
+ segment !== "." &&
23
+ segment !== ".." &&
24
+ !/^[A-Za-z]:$/.test(segment))
25
+ .join("/");
26
+ return `${file}/${cleanName(test.name)}`;
27
+ }
@@ -1,71 +1,40 @@
1
- import type { SupportedBrowser } from "../types/browser.js";
2
1
  import type { RegisteredTestCase } from "../types/test.js";
3
- import type { PreparedRun } from "./prepare-run.js";
4
- import { type RunEvent } from "./reporter.js";
5
- import type { SamuraiGroup } from "../types/config.js";
6
2
  import type { ResolvedGroup } from "./groups.js";
7
- /** What decides which tests of the found files run, and how the browser starts */
8
- export interface RunnerOptions {
9
- /** Spec files to run, absolute or relative to the project folder. Default: every `*.spec.ts` under `srcDir` */
10
- files?: string[];
11
- /** Only tests whose full name matches (a string must be contained in it) */
12
- grep?: string | RegExp;
13
- /** Only tests with exactly one of these full names (`describe` titles joined with " > ") */
14
- testNames?: string[];
15
- /** Only the tests of this group of the config's `groups` (unknown name: an error). Combines with the other filters */
16
- group?: string;
17
- /** The browsers to run in, instead of the group's or the project's `browsers` (the command line's `--browser`) */
18
- browsers?: SupportedBrowser[];
19
- /** Run the browser without a window. @default false */
20
- headless?: boolean;
21
- /** Remote debugging port of the browser. @default 9223 */
22
- port?: number;
23
- /** Where the report is written; `false` writes none. @default `<project>/result/report.json` */
24
- reportPath?: string | false;
25
- /** Receives start and end of the run and of every test, as they happen */
26
- onEvent?: (event: RunEvent) => void;
27
- /** Aborting stops the running test (its browser is closed), skips the rest and reports the run as failed */
28
- signal?: AbortSignal;
3
+ import type { RunnerOptions } from "./options.js";
4
+ import type { PreparedRun } from "./prepare-run.js";
5
+ import { type PooledRunner } from "./pooled-run.js";
6
+ import { type PoolOptions } from "./worker-pool.js";
7
+ export type { RunnerOptions } from "./options.js";
8
+ /** What a runner knows about the project it runs in, besides the options of the run */
9
+ export interface ProjectInfo {
10
+ /** The config was given as an object, not read from `samurai.config.ts`: a worker process cannot get it */
11
+ inlineConfig?: boolean;
12
+ /** Where the framework keeps browser profiles; a worker needs the same folder */
13
+ dataDir?: string;
14
+ /** Makes the pool of a run with more than one worker; tests give a fake */
15
+ createPool?: (options: PoolOptions) => PooledRunner & {
16
+ dispose(): Promise<void>;
17
+ };
29
18
  }
30
- /** Whether a test with this full name is selected by the options */
31
- export declare function isSelected(name: string, { grep, testNames }: Pick<RunnerOptions, "grep" | "testNames">): boolean;
19
+ /** Finds the spec files, picks the tests and browsers of a run and runs each test in each browser */
32
20
  export default class TestRunner {
33
21
  private testFiles;
34
22
  private run;
35
- private group?;
36
23
  private options;
37
- /** The real `srcDir` of this run, set by `start`; test screenshots are saved under paths relative to it */
38
- private srcDir;
39
- constructor(testFiles: string[], run: PreparedRun, group?: SamuraiGroup, options?: RunnerOptions);
40
- static init(run: PreparedRun, group?: SamuraiGroup, options?: RunnerOptions): Promise<TestRunner>;
41
- /**
42
- * Imports the spec files and returns every test they register. A fresh registry and a fresh import of each
43
- * file, so a second run in this process registers its tests again (and sees edited specs, for files loaded as
44
- * ES modules; CommonJS files stay cached until the process ends)
45
- */
46
- private registerAll;
47
- /** Id of a registered test: its spec file relative to `srcDir`, then its name */
48
- private idOf;
49
- /** The config's groups, resolved against the tests the specs register */
50
- private resolveAll;
24
+ private project;
25
+ constructor(testFiles: string[], run: PreparedRun, options?: RunnerOptions, project?: ProjectInfo);
26
+ static init(run: PreparedRun, options?: RunnerOptions, project?: ProjectInfo): Promise<TestRunner>;
51
27
  /** The groups of the config with the tests each holds; see `listGroups` */
52
28
  groups(): Promise<ResolvedGroup[]>;
53
29
  /** Imports the spec files and returns the tests the options select: by `group`, `testNames` and `grep` */
54
30
  register(): Promise<RegisteredTestCase[]>;
55
31
  /**
56
- * The browsers the run uses: the options' (`--browser`), else the selected group's, else the project's, else
57
- * Firefox. A group is only looked up when there is something to look up
32
+ * Runs the selected tests in every browser of the run, one browser after the other, and returns the summary that
33
+ * was also written to the report. With more than one worker the tests run at the same time in worker processes.
58
34
  */
59
- private browsersOfRun;
60
- /** 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 */
61
35
  start(): Promise<import("../types/test.js").TestSummary>;
62
- private executeTestCase;
36
+ /** Whether anything but the default says how many workers to use */
37
+ private workersAsked;
38
+ /** The pool of a run: up to `workers` worker processes that read the same project and settings as this run */
39
+ private createPool;
63
40
  }
64
- /**
65
- * The folder of a test's screenshots below `screenshots/`: its spec file without extension, then its cleaned
66
- * name. A spec outside srcDir loses the `..` (and a drive or root) its relative path starts with, so it stays
67
- * below `screenshots/`.
68
- * @internal Exported for tests
69
- */
70
- export declare function testDirOf(test: RegisteredTestCase, srcDir: string): string;
71
- export { describe, test } from "./registry.js";