@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.
- package/README.md +3 -4
- package/dist/assert/assertion-error.d.ts +1 -1
- package/dist/assert/expect.d.ts +5 -50
- package/dist/assert/expect.js +5 -182
- package/dist/assert/locator-assertions.d.ts +22 -0
- package/dist/assert/locator-assertions.js +61 -0
- package/dist/assert/pending-assertions.d.ts +7 -0
- package/dist/assert/pending-assertions.js +40 -0
- package/dist/assert/text-match.d.ts +4 -0
- package/dist/assert/text-match.js +11 -0
- package/dist/assert/value-assertions.d.ts +23 -0
- package/dist/assert/value-assertions.js +82 -0
- package/dist/browser/browser-context.d.ts +0 -1
- package/dist/browser/browser-context.js +13 -5
- package/dist/browser/browser-finder.js +5 -9
- package/dist/browser/browser.d.ts +3 -31
- package/dist/browser/browser.js +36 -219
- package/dist/browser/chrome-launch.d.ts +2 -1
- package/dist/browser/dialog-handling.d.ts +32 -0
- package/dist/browser/dialog-handling.js +85 -0
- package/dist/browser/document-watcher.d.ts +44 -0
- package/dist/browser/document-watcher.js +160 -0
- package/dist/browser/download-folders.d.ts +0 -1
- package/dist/browser/download-folders.js +7 -15
- package/dist/browser/download.js +5 -10
- package/dist/browser/downloads-dir.js +2 -2
- package/dist/browser/emit.d.ts +7 -0
- package/dist/browser/emit.js +16 -0
- package/dist/browser/emulation.d.ts +1 -1
- package/dist/browser/emulation.js +54 -18
- package/dist/browser/file-chooser.js +4 -11
- package/dist/browser/history.d.ts +6 -0
- package/dist/browser/history.js +89 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/init-script.d.ts +2 -0
- package/dist/browser/init-script.js +16 -0
- package/dist/browser/launch-firefox.d.ts +20 -0
- package/dist/browser/launch-firefox.js +66 -0
- package/dist/browser/launch-flags.d.ts +18 -0
- package/dist/browser/launch-flags.js +100 -0
- package/dist/browser/lazy.d.ts +21 -0
- package/dist/browser/lazy.js +44 -0
- package/dist/browser/page-events.d.ts +44 -0
- package/dist/browser/page-events.js +58 -0
- package/dist/browser/page-logs.js +2 -9
- package/dist/browser/page-navigation.d.ts +58 -0
- package/dist/browser/page-navigation.js +108 -0
- package/dist/browser/page-network.d.ts +69 -0
- package/dist/browser/page-network.js +163 -0
- package/dist/browser/page-settings.d.ts +35 -0
- package/dist/browser/page-settings.js +51 -0
- package/dist/browser/page-transfers.d.ts +36 -0
- package/dist/browser/page-transfers.js +50 -0
- package/dist/browser/page.d.ts +39 -154
- package/dist/browser/page.js +134 -758
- package/dist/browser/pdf.d.ts +1 -1
- package/dist/browser/session.d.ts +16 -0
- package/dist/browser/session.js +32 -0
- package/dist/browser/viewport.d.ts +19 -0
- package/dist/browser/viewport.js +72 -0
- package/dist/browser/warm-up.d.ts +1 -1
- package/dist/browser/warm-up.js +2 -1
- package/dist/browsers/index.d.ts +1 -1
- package/dist/browsers/index.js +1 -1
- package/dist/browsers/pins.d.ts +0 -2
- package/dist/browsers/pins.js +0 -2
- package/dist/browsers/store.d.ts +0 -2
- package/dist/browsers/store.js +1 -2
- package/dist/cli/args.d.ts +13 -6
- package/dist/cli/args.js +13 -10
- package/dist/cli/browsers.d.ts +1 -2
- package/dist/cli/browsers.js +2 -2
- package/dist/cli/console-reporter.js +3 -2
- package/dist/cli/flow-reporter.js +2 -1
- package/dist/cli/flow.d.ts +1 -2
- package/dist/cli/flow.js +25 -22
- package/dist/cli/init.js +2 -7
- package/dist/cli/io.d.ts +10 -0
- package/dist/cli/io.js +1 -0
- package/dist/cli/main.d.ts +3 -10
- package/dist/cli/main.js +10 -143
- package/dist/cli/paint.d.ts +2 -0
- package/dist/cli/paint.js +4 -0
- package/dist/cli/record.d.ts +4 -0
- package/dist/cli/record.js +93 -0
- package/dist/cli/usage.d.ts +1 -0
- package/dist/cli/usage.js +58 -0
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.js +7 -0
- package/dist/config/browsers.d.ts +11 -0
- package/dist/config/browsers.js +32 -0
- package/dist/config/config.js +4 -1
- package/dist/config/mask.d.ts +1 -1
- package/dist/config/mask.js +7 -2
- package/dist/config/run-settings.d.ts +1 -2
- package/dist/config/run-settings.js +1 -6
- package/dist/config/timeouts.d.ts +2 -0
- package/dist/config/timeouts.js +13 -0
- package/dist/config/workers.d.ts +14 -0
- package/dist/config/workers.js +29 -0
- package/dist/flows/run/branch-context.d.ts +14 -0
- package/dist/flows/run/branch-context.js +35 -0
- package/dist/flows/run/handlers/condition.d.ts +1 -1
- package/dist/flows/run/handlers/end.d.ts +1 -1
- package/dist/flows/run/handlers/group.d.ts +1 -1
- package/dist/flows/run/handlers/index.d.ts +2 -2
- package/dist/flows/run/handlers/parallel.d.ts +1 -1
- package/dist/flows/run/handlers/start.d.ts +1 -1
- package/dist/flows/run/handlers/support.d.ts +3 -27
- package/dist/flows/run/handlers/test.d.ts +1 -1
- package/dist/flows/run/handlers/types.d.ts +27 -0
- package/dist/flows/run/handlers/types.js +1 -0
- package/dist/flows/run/index.d.ts +1 -1
- package/dist/flows/run/prepare-flow.d.ts +14 -0
- package/dist/flows/run/prepare-flow.js +44 -0
- package/dist/flows/run/project.d.ts +33 -2
- package/dist/flows/run/project.js +139 -57
- package/dist/flows/run/run.d.ts +3 -4
- package/dist/flows/run/run.js +23 -193
- package/dist/flows/run/walker.d.ts +56 -0
- package/dist/flows/run/walker.js +225 -0
- package/dist/flows/steps/catalog.d.ts +1 -1
- package/dist/flows/steps/handler.d.ts +1 -1
- package/dist/flows/steps/set.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/locator/actionability.d.ts +33 -0
- package/dist/locator/actionability.js +104 -0
- package/dist/locator/chain-resolver.d.ts +20 -0
- package/dist/locator/chain-resolver.js +102 -0
- package/dist/locator/document-swap.d.ts +1 -0
- package/dist/locator/document-swap.js +11 -0
- package/dist/locator/element-state.d.ts +3 -1
- package/dist/locator/element-state.js +11 -3
- package/dist/locator/file-actions.d.ts +35 -0
- package/dist/locator/file-actions.js +55 -0
- package/dist/{script/helpers.js → locator/helper-realm.js} +2 -2
- package/dist/locator/input-actions.d.ts +18 -0
- package/dist/locator/input-actions.js +80 -0
- package/dist/locator/label-locator.d.ts +1 -1
- package/dist/locator/label-locator.js +5 -12
- package/dist/locator/locator.d.ts +14 -37
- package/dist/locator/locator.js +68 -424
- package/dist/locator/page-scripts.d.ts +16 -0
- package/dist/locator/page-scripts.js +54 -0
- package/dist/locator/page-search.d.ts +10 -0
- package/dist/locator/page-search.js +19 -0
- package/dist/locator/selector-errors.d.ts +0 -4
- package/dist/locator/selector-errors.js +0 -7
- package/dist/locator/selector.d.ts +14 -13
- package/dist/locator/selector.js +10 -16
- package/dist/locator/text-locator.d.ts +1 -1
- package/dist/locator/text-locator.js +5 -9
- package/dist/logger/index.d.ts +6 -0
- package/dist/logger/index.js +14 -7
- package/dist/network/bidi-wire.d.ts +10 -0
- package/dist/network/bidi-wire.js +38 -0
- package/dist/network/intercept.d.ts +30 -0
- package/dist/network/intercept.js +130 -0
- package/dist/network/network-tracker.d.ts +10 -47
- package/dist/network/network-tracker.js +34 -68
- package/dist/network/network-types.d.ts +33 -0
- package/dist/network/network-types.js +1 -0
- package/dist/network/network-wait.d.ts +7 -2
- package/dist/network/network-wait.js +14 -4
- package/dist/network/response.d.ts +1 -1
- package/dist/network/response.js +1 -1
- package/dist/network/route.d.ts +92 -0
- package/dist/network/route.js +142 -0
- package/dist/network/router.d.ts +6 -91
- package/dist/network/router.js +25 -258
- package/dist/recorder/expectation.d.ts +5 -0
- package/dist/recorder/expectation.js +20 -0
- package/dist/recorder/files-recorder.d.ts +30 -0
- package/dist/recorder/files-recorder.js +88 -0
- package/dist/recorder/normalise.d.ts +17 -6
- package/dist/recorder/normalise.js +96 -91
- package/dist/recorder/options.d.ts +56 -0
- package/dist/recorder/options.js +1 -0
- package/dist/recorder/page-messages.d.ts +35 -0
- package/dist/recorder/page-messages.js +26 -0
- package/dist/recorder/ranking.js +1 -0
- package/dist/recorder/realms.d.ts +6 -0
- package/dist/recorder/realms.js +13 -0
- package/dist/recorder/recorder.d.ts +10 -74
- package/dist/recorder/recorder.js +39 -199
- package/dist/recorder/session-browser.d.ts +22 -0
- package/dist/recorder/session-browser.js +21 -0
- package/dist/recorder/session-secrets.d.ts +8 -0
- package/dist/recorder/session-secrets.js +45 -0
- package/dist/recorder/session.d.ts +5 -14
- package/dist/recorder/session.js +22 -120
- package/dist/recorder/spec-source.d.ts +39 -0
- package/dist/recorder/spec-source.js +84 -0
- package/dist/recorder/toolbar/action-guard.d.ts +21 -0
- package/dist/recorder/toolbar/action-guard.js +43 -0
- package/dist/recorder/toolbar/actions/assert.d.ts +4 -0
- package/dist/recorder/toolbar/actions/assert.js +52 -0
- package/dist/recorder/toolbar/actions/insert.d.ts +6 -0
- package/dist/recorder/toolbar/actions/insert.js +93 -0
- package/dist/recorder/toolbar/actions/pick.d.ts +8 -0
- package/dist/recorder/toolbar/actions/pick.js +26 -0
- package/dist/recorder/toolbar/actions/reply.d.ts +7 -0
- package/dist/recorder/toolbar/actions/reply.js +10 -0
- package/dist/recorder/toolbar/controller-fixture.d.ts +47 -0
- package/dist/recorder/toolbar/controller-fixture.js +129 -0
- package/dist/recorder/toolbar/controller.d.ts +12 -78
- package/dist/recorder/toolbar/controller.js +35 -247
- package/dist/recorder/toolbar/inspect.d.ts +1 -3
- package/dist/recorder/toolbar/inspect.js +2 -2
- package/dist/recorder/toolbar/page-scope.d.ts +19 -0
- package/dist/recorder/toolbar/page-scope.js +43 -0
- package/dist/recorder/toolbar/protocol.d.ts +2 -1
- package/dist/recorder/toolbar/source/attach.d.ts +2 -0
- package/dist/recorder/toolbar/source/attach.js +41 -0
- package/dist/recorder/toolbar/source/dom.d.ts +2 -0
- package/dist/recorder/toolbar/source/dom.js +47 -0
- package/dist/recorder/toolbar/source/drag.d.ts +2 -0
- package/dist/recorder/toolbar/source/drag.js +32 -0
- package/dist/recorder/toolbar/source/events-page.d.ts +2 -0
- package/dist/recorder/toolbar/source/events-page.js +67 -0
- package/dist/recorder/toolbar/source/events-toolbar.d.ts +2 -0
- package/dist/recorder/toolbar/source/events-toolbar.js +35 -0
- package/dist/recorder/toolbar/source/fields.d.ts +2 -0
- package/dist/recorder/toolbar/source/fields.js +44 -0
- package/dist/recorder/toolbar/source/handlers.d.ts +2 -0
- package/dist/recorder/toolbar/source/handlers.js +134 -0
- package/dist/recorder/toolbar/source/names.d.ts +2 -0
- package/dist/recorder/toolbar/source/names.js +12 -0
- package/dist/recorder/toolbar/source/picking.d.ts +2 -0
- package/dist/recorder/toolbar/source/picking.js +72 -0
- package/dist/recorder/toolbar/source/placement.d.ts +2 -0
- package/dist/recorder/toolbar/source/placement.js +45 -0
- package/dist/recorder/toolbar/source/recorder-side.d.ts +2 -0
- package/dist/recorder/toolbar/source/recorder-side.js +54 -0
- package/dist/recorder/toolbar/source/setup.d.ts +2 -0
- package/dist/recorder/toolbar/source/setup.js +50 -0
- package/dist/recorder/toolbar/source/views-panels.d.ts +2 -0
- package/dist/recorder/toolbar/source/views-panels.js +150 -0
- package/dist/recorder/toolbar/source/views-rail.d.ts +2 -0
- package/dist/recorder/toolbar/source/views-rail.js +40 -0
- package/dist/recorder/toolbar/source/views-render.d.ts +2 -0
- package/dist/recorder/toolbar/source/views-render.js +48 -0
- package/dist/recorder/toolbar/source.js +30 -856
- package/dist/recorder/toolbar/types.d.ts +70 -0
- package/dist/recorder/toolbar/types.js +1 -0
- package/dist/recorder/toolbar-bridge.d.ts +43 -0
- package/dist/recorder/toolbar-bridge.js +106 -0
- package/dist/runner/browsers.d.ts +0 -12
- package/dist/runner/browsers.js +1 -33
- package/dist/runner/options.d.ts +31 -0
- package/dist/runner/options.js +1 -0
- package/dist/runner/pooled-run.d.ts +43 -0
- package/dist/runner/pooled-run.js +75 -0
- package/dist/runner/ports.d.ts +2 -0
- package/dist/runner/ports.js +2 -0
- package/dist/runner/registry.d.ts +1 -1
- package/dist/runner/registry.js +1 -1
- package/dist/runner/reporter.d.ts +31 -10
- package/dist/runner/reporter.js +39 -6
- package/dist/runner/run-test.d.ts +16 -0
- package/dist/runner/run-test.js +178 -0
- package/dist/runner/run.d.ts +5 -3
- package/dist/runner/run.js +28 -29
- package/dist/runner/select.d.ts +24 -0
- package/dist/runner/select.js +77 -0
- package/dist/runner/test-dir.d.ts +13 -0
- package/dist/runner/test-dir.js +27 -0
- package/dist/runner/test-runner.d.ts +25 -56
- package/dist/runner/test-runner.js +81 -270
- package/dist/runner/worker-pool.d.ts +67 -0
- package/dist/runner/worker-pool.js +275 -0
- package/dist/runner/worker-process.d.ts +6 -0
- package/dist/runner/worker-process.js +54 -0
- package/dist/runner/worker-protocol.d.ts +67 -0
- package/dist/runner/worker-protocol.js +1 -0
- package/dist/runner/worker-session.d.ts +7 -0
- package/dist/runner/worker-session.js +60 -0
- package/dist/runner/worker.d.ts +26 -0
- package/dist/runner/worker.js +121 -0
- package/dist/runner/workers-option.d.ts +15 -0
- package/dist/runner/workers-option.js +22 -0
- package/dist/script/call-function.d.ts +1 -9
- package/dist/script/call-function.js +2 -19
- package/dist/script/element-handle.d.ts +1 -2
- package/dist/script/element-handle.js +1 -2
- package/dist/script/serialize.d.ts +3 -12
- package/dist/script/serialize.js +53 -62
- package/dist/steps/parse/call.d.ts +3 -0
- package/dist/steps/parse/call.js +81 -0
- package/dist/steps/parse/expectation.d.ts +5 -0
- package/dist/steps/parse/expectation.js +71 -0
- package/dist/steps/parse/file-entry.d.ts +4 -0
- package/dist/steps/parse/file-entry.js +74 -0
- package/dist/steps/parse/literals.d.ts +4 -0
- package/dist/steps/parse/literals.js +28 -0
- package/dist/steps/parse/step.d.ts +4 -0
- package/dist/steps/parse/step.js +81 -0
- package/dist/steps/parse.js +2 -323
- package/dist/transport/bidi-connection.d.ts +4 -15
- package/dist/transport/bidi-connection.js +9 -63
- package/dist/transport/log-redaction.d.ts +13 -0
- package/dist/transport/log-redaction.js +51 -0
- package/dist/transport/subscriptions.js +18 -13
- package/dist/transport/unsupported.d.ts +7 -0
- package/dist/transport/unsupported.js +13 -0
- package/dist/types/bidi-modules/browser.d.ts +1 -12
- package/dist/types/bidi-modules/browsing-context.d.ts +8 -18
- package/dist/types/bidi-modules/emulation.d.ts +29 -21
- package/dist/types/bidi-modules/input.d.ts +1 -9
- package/dist/types/bidi-modules/network.d.ts +29 -24
- package/dist/types/bidi-modules/script.d.ts +0 -9
- package/dist/types/bidi-modules/session.d.ts +0 -8
- package/dist/types/bidi-modules/storage.d.ts +0 -5
- package/dist/types/bidi.d.ts +7 -37
- package/dist/types/config.d.ts +12 -0
- package/dist/types/test.d.ts +8 -3
- package/dist/wait/wait-until.d.ts +1 -5
- package/dist/wait/wait-until.js +7 -16
- package/package.json +3 -2
- package/dist/config/default.d.ts +0 -2
- package/dist/config/default.js +0 -3
- package/dist/types/bidi-modules/web-extensions.d.ts +0 -9
- package/dist/types/bidi-protocols/command.d.ts +0 -7
- package/dist/types/bidi-protocols/event.d.ts +0 -17
- /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 {
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
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,
|
|
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,
|
|
24
|
+
static async init(run, options = {}, project = {}) {
|
|
44
25
|
logger.verbose("Initializing test runner");
|
|
45
|
-
|
|
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,
|
|
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(
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
115
|
-
*
|
|
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
|
-
|
|
131
|
-
const
|
|
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
|
-
|
|
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 ??
|
|
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
|
|
111
|
+
await runOneTest(test, browser, this.run, this.options, loaded.srcDir, reporter);
|
|
159
112
|
}
|
|
160
113
|
}
|
|
161
114
|
return reporter.onEnd();
|
|
162
115
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
+
}
|