@itmetsam/samurai-framework 0.9.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- 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 +14 -1
- package/dist/browser/chrome-launch.js +44 -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 +19 -7
- package/dist/cli/args.js +18 -9
- package/dist/cli/browsers.d.ts +1 -2
- package/dist/cli/browsers.js +2 -2
- package/dist/cli/console-reporter.d.ts +4 -0
- package/dist/cli/console-reporter.js +15 -7
- 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 +3 -8
- 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 +11 -137
- 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 +8 -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 -24
- package/dist/flows/run/handlers/support.js +1 -1
- 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 -49
- package/dist/flows/run/run.d.ts +8 -4
- package/dist/flows/run/run.js +30 -194
- 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 +7 -13
- package/dist/recorder/session.js +22 -117
- 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 +10 -0
- package/dist/runner/browsers.js +12 -0
- package/dist/runner/groups.d.ts +5 -1
- package/dist/runner/groups.js +9 -2
- 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 +40 -11
- package/dist/runner/reporter.js +69 -16
- package/dist/runner/run-test.d.ts +16 -0
- package/dist/runner/run-test.js +178 -0
- package/dist/runner/run.d.ts +5 -2
- package/dist/runner/run.js +28 -28
- 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 +27 -50
- package/dist/runner/test-runner.js +102 -264
- 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 -2
- package/dist/transport/bidi-connection.js +9 -43
- 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 +17 -3
- package/dist/types/test.d.ts +15 -2
- 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,307 +1,145 @@
|
|
|
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";
|
|
4
|
+
import { projectBrowser } from "../browsers/resolve.js";
|
|
7
5
|
import { loadConfig, projectDir, readConfig } from "../config/config.js";
|
|
8
6
|
import logger from "../logger/index.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
import { DEFAULT_SRC_DIR, findGroup, realPath, resolveGroups, testId, toPosix, } from "./groups.js";
|
|
20
|
-
import { judgePageLogs } from "./page-logs-report.js";
|
|
21
|
-
/** Whether a test with this full name is selected by the options */
|
|
22
|
-
export function isSelected(name, { grep, testNames }) {
|
|
23
|
-
if (testNames && !testNames.includes(name))
|
|
24
|
-
return false;
|
|
25
|
-
if (grep === undefined)
|
|
26
|
-
return true;
|
|
27
|
-
if (typeof grep === "string")
|
|
28
|
-
return name.includes(grep);
|
|
29
|
-
grep.lastIndex = 0;
|
|
30
|
-
return grep.test(name);
|
|
31
|
-
}
|
|
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 */
|
|
32
17
|
export default class TestRunner {
|
|
33
|
-
constructor(testFiles, run,
|
|
34
|
-
/** The real `srcDir` of this run, set by `start`; test screenshots are saved under paths relative to it */
|
|
35
|
-
this.srcDir = "";
|
|
18
|
+
constructor(testFiles, run, options = {}, project = {}) {
|
|
36
19
|
this.testFiles = testFiles;
|
|
37
20
|
this.run = run;
|
|
38
|
-
this.group = group;
|
|
39
21
|
this.options = options;
|
|
22
|
+
this.project = project;
|
|
40
23
|
}
|
|
41
|
-
static async init(run,
|
|
24
|
+
static async init(run, options = {}, project = {}) {
|
|
42
25
|
logger.verbose("Initializing test runner");
|
|
43
|
-
|
|
44
|
-
if (group && group.src) {
|
|
45
|
-
srcDir = group.src;
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
srcDir = (await readConfig("srcDir")) ?? "./src";
|
|
49
|
-
}
|
|
50
|
-
assert(srcDir, "No srcDir declared!");
|
|
26
|
+
const srcDir = (await readConfig("srcDir")) ?? DEFAULT_SRC_DIR;
|
|
51
27
|
const cwd = path.resolve(projectDir(), srcDir);
|
|
52
28
|
if (options.files) {
|
|
53
|
-
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);
|
|
54
30
|
}
|
|
55
31
|
logger.verbose("Reading test files from: %s", cwd);
|
|
56
32
|
const testFiles = [];
|
|
57
|
-
for await (const entry of glob(
|
|
33
|
+
for await (const entry of glob(DEFAULT_TEST_MATCH, {
|
|
58
34
|
cwd,
|
|
59
35
|
})) {
|
|
60
36
|
testFiles.push(entry);
|
|
61
37
|
}
|
|
62
38
|
logger.debug("Found %d tests in srcDir", testFiles.length);
|
|
63
|
-
return new TestRunner(testFiles.map((file) => path.join(cwd, file)), run,
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Imports the spec files and returns every test they register. A fresh registry and a fresh import of each
|
|
67
|
-
* file, so a second run in this process registers its tests again (and sees edited specs, for files loaded as
|
|
68
|
-
* ES modules; CommonJS files stay cached until the process ends)
|
|
69
|
-
*/
|
|
70
|
-
async registerAll() {
|
|
71
|
-
clearRegistry();
|
|
72
|
-
const stamp = `?run=${Date.now().toString(36)}`;
|
|
73
|
-
for (const file of this.testFiles)
|
|
74
|
-
assertModuleProject(file);
|
|
75
|
-
for (const file of this.testFiles) {
|
|
76
|
-
logger.verbose("Trying to register file: %s", file);
|
|
77
|
-
await import(pathToFileURL(file).href + stamp);
|
|
78
|
-
}
|
|
79
|
-
return [...registeredTests()];
|
|
80
|
-
}
|
|
81
|
-
/** Id of a registered test: its spec file relative to `srcDir`, then its name */
|
|
82
|
-
idOf(test, srcDir) {
|
|
83
|
-
return testId(toPosix(path.relative(srcDir, test.file)), test.name);
|
|
84
|
-
}
|
|
85
|
-
/** The config's groups, resolved against the tests the specs register */
|
|
86
|
-
async resolveAll(tests) {
|
|
87
|
-
const config = await loadConfig();
|
|
88
|
-
const { dir, srcDir } = realDirs(config.srcDir);
|
|
89
|
-
const groups = resolveGroups(config, tests.map(({ file, name }) => ({
|
|
90
|
-
file: toPosix(path.relative(srcDir, file)),
|
|
91
|
-
name,
|
|
92
|
-
})), dir, srcDir);
|
|
93
|
-
return { groups, srcDir };
|
|
39
|
+
return new TestRunner(testFiles.map((file) => path.join(cwd, file)), run, options, project);
|
|
94
40
|
}
|
|
95
41
|
/** The groups of the config with the tests each holds; see `listGroups` */
|
|
96
42
|
async groups() {
|
|
97
|
-
return (await
|
|
43
|
+
return (await loadSpecs(this.testFiles)).groups;
|
|
98
44
|
}
|
|
99
45
|
/** Imports the spec files and returns the tests the options select: by `group`, `testNames` and `grep` */
|
|
100
46
|
async register() {
|
|
101
|
-
|
|
102
|
-
const { group } = this.options;
|
|
103
|
-
let inGroup = () => true;
|
|
104
|
-
if (group !== undefined) {
|
|
105
|
-
const { groups, srcDir } = await this.resolveAll(all);
|
|
106
|
-
const members = new Set(findGroup(groups, group).testIds);
|
|
107
|
-
inGroup = (test) => members.has(this.idOf(test, srcDir));
|
|
108
|
-
}
|
|
109
|
-
return all.filter((test) => inGroup(test) && isSelected(test.name, this.options));
|
|
47
|
+
return selectTests(await loadSpecs(this.testFiles), this.options);
|
|
110
48
|
}
|
|
111
|
-
/**
|
|
49
|
+
/**
|
|
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.
|
|
52
|
+
*/
|
|
112
53
|
async start() {
|
|
113
54
|
const { reportPath, onEvent, signal } = this.options;
|
|
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);
|
|
114
59
|
const reporter = new TestReporter({
|
|
115
60
|
environment: this.run.settings.environment,
|
|
61
|
+
browsers,
|
|
116
62
|
...(reportPath !== undefined && { output: reportPath }),
|
|
117
63
|
...(onEvent && { onEvent }),
|
|
118
64
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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");
|
|
80
|
+
// A missing browser is one clear error before the run, not a failure per test, and before any browser has run a
|
|
81
|
+
// test; a fresh build's slow first start happens here too, outside any test's timeout
|
|
82
|
+
if (selected.length > 0) {
|
|
83
|
+
for (const browser of browsers)
|
|
84
|
+
await projectBrowser(browser);
|
|
85
|
+
for (const browser of browsers)
|
|
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();
|
|
131
97
|
}
|
|
132
|
-
|
|
98
|
+
return reporter.onEnd();
|
|
133
99
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const finish = (error, logs) => {
|
|
143
|
-
if (ended)
|
|
144
|
-
return;
|
|
145
|
-
ended = true;
|
|
146
|
-
reporter.onTestEnd(test, error, {
|
|
147
|
-
...logs,
|
|
148
|
-
screenshots: evidence?.screenshots,
|
|
149
|
-
});
|
|
150
|
-
};
|
|
151
|
-
let browserClosed = false;
|
|
152
|
-
/** Set once the runner itself closes the browser */
|
|
153
|
-
let closing = false;
|
|
154
|
-
const selectedBrowser = (await readConfig("browser")) ?? "firefox";
|
|
155
|
-
const { settings, secrets } = this.run;
|
|
156
|
-
const timeout = settings.timeout;
|
|
157
|
-
const { reportPath } = this.options;
|
|
158
|
-
const resultDir = typeof reportPath === "string"
|
|
159
|
-
? path.dirname(reportPath)
|
|
160
|
-
: path.join(projectDir(), "result");
|
|
161
|
-
let browser;
|
|
162
|
-
let page;
|
|
163
|
-
let timeoutTimer;
|
|
164
|
-
let onRunAborted;
|
|
165
|
-
const timeoutController = new AbortController();
|
|
166
|
-
await Promise.race([
|
|
167
|
-
new Promise((resolve) => {
|
|
168
|
-
timeoutTimer = setTimeout(() => {
|
|
169
|
-
finish({
|
|
170
|
-
message: `Test timed out after ${timeout / 1000} seconds`,
|
|
171
|
-
type: "timeout",
|
|
172
|
-
});
|
|
173
|
-
timeoutController.abort(new Error("Test timed out"));
|
|
174
|
-
resolve();
|
|
175
|
-
}, timeout);
|
|
176
|
-
onRunAborted = () => {
|
|
177
|
-
if (timeoutController.signal.aborted)
|
|
178
|
-
return;
|
|
179
|
-
finish({ message: "Run aborted", type: "error" });
|
|
180
|
-
timeoutController.abort(new Error("Run aborted"));
|
|
181
|
-
resolve();
|
|
182
|
-
};
|
|
183
|
-
this.options.signal?.addEventListener("abort", onRunAborted, {
|
|
184
|
-
once: true,
|
|
185
|
-
});
|
|
186
|
-
}),
|
|
187
|
-
new Promise(async (resolve, reject) => {
|
|
188
|
-
try {
|
|
189
|
-
const launched = await Browser.launch(selectedBrowser, {
|
|
190
|
-
port: this.options.port ?? 9223,
|
|
191
|
-
headless: this.options.headless ?? settings.headless,
|
|
192
|
-
}, timeoutController.signal);
|
|
193
|
-
browser = launched.browser;
|
|
194
|
-
// A person closing the window, or the browser crashing: end the test now instead of waiting for a
|
|
195
|
-
// command to time out
|
|
196
|
-
void launched.browser.exited.then(() => {
|
|
197
|
-
if (ended || closing)
|
|
198
|
-
return;
|
|
199
|
-
browserClosed = true;
|
|
200
|
-
finish({
|
|
201
|
-
message: "The browser was closed while the test was running",
|
|
202
|
-
type: "error",
|
|
203
|
-
});
|
|
204
|
-
timeoutController.abort(new Error("Browser closed"));
|
|
205
|
-
resolve();
|
|
100
|
+
reporter.onStart(selected.length * browsers.length);
|
|
101
|
+
for (const browser of browsers) {
|
|
102
|
+
for (const test of selected) {
|
|
103
|
+
if (signal?.aborted) {
|
|
104
|
+
reporter.onTestStart(test, browser);
|
|
105
|
+
reporter.onTestEnd(test, browser, {
|
|
106
|
+
message: "Run aborted",
|
|
107
|
+
type: "error",
|
|
206
108
|
});
|
|
207
|
-
|
|
208
|
-
evidence = new Evidence(resultDir, testDirOf(test, this.srcDir));
|
|
209
|
-
launched.page.setEvidence(evidence);
|
|
210
|
-
const fixtures = {
|
|
211
|
-
page: launched.page,
|
|
212
|
-
browser: launched.browser,
|
|
213
|
-
env: createEnvFixture(settings.environment, settings.variables),
|
|
214
|
-
secrets: createSecretsFixture(settings.environment, secrets),
|
|
215
|
-
};
|
|
216
|
-
await test.function(fixtures);
|
|
217
|
-
// Already reported as timed out
|
|
218
|
-
if (timeoutController.signal.aborted)
|
|
219
|
-
return resolve();
|
|
220
|
-
const unawaited = takePendingAssertions();
|
|
221
|
-
if (unawaited.length > 0) {
|
|
222
|
-
finish({
|
|
223
|
-
message: unawaited
|
|
224
|
-
.map((matcher) => `expect(locator).${matcher}() was not awaited`)
|
|
225
|
-
.join("\n"),
|
|
226
|
-
type: "assertion",
|
|
227
|
-
}, await judgeLogs(page, true));
|
|
228
|
-
return resolve();
|
|
229
|
-
}
|
|
230
|
-
const verdict = await judgeLogs(page, false);
|
|
231
|
-
finish(verdict.error, verdict);
|
|
232
|
-
resolve();
|
|
109
|
+
continue;
|
|
233
110
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
? {
|
|
239
|
-
message: "The browser was closed while the test was running",
|
|
240
|
-
type: "error",
|
|
241
|
-
}
|
|
242
|
-
: toTestError(err), await judgeLogs(page, true));
|
|
243
|
-
resolve();
|
|
244
|
-
}
|
|
245
|
-
}),
|
|
246
|
-
]);
|
|
247
|
-
clearTimeout(timeoutTimer);
|
|
248
|
-
if (onRunAborted)
|
|
249
|
-
this.options.signal?.removeEventListener("abort", onRunAborted);
|
|
250
|
-
takePendingAssertions();
|
|
251
|
-
logger.verbose("Closing browser for test: %s", test.name);
|
|
252
|
-
closing = true;
|
|
253
|
-
await browser?.close();
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
/** The project and source folders as real paths: registered test files come from stack traces, which resolve symlinks */
|
|
257
|
-
function realDirs(configuredSrcDir) {
|
|
258
|
-
const dir = realPath(projectDir());
|
|
259
|
-
const srcDir = realPath(path.resolve(dir, configuredSrcDir ?? DEFAULT_SRC_DIR));
|
|
260
|
-
return { dir, srcDir };
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* The folder of a test's screenshots below `screenshots/`: its spec file without extension, then its cleaned
|
|
264
|
-
* name. A spec outside srcDir loses the `..` (and a drive or root) its relative path starts with, so it stays
|
|
265
|
-
* below `screenshots/`.
|
|
266
|
-
* @internal Exported for tests
|
|
267
|
-
*/
|
|
268
|
-
export function testDirOf(test, srcDir) {
|
|
269
|
-
const file = toPosix(path.relative(srcDir, test.file))
|
|
270
|
-
.replace(/\.[cm]?[jt]sx?$/, "")
|
|
271
|
-
.split("/")
|
|
272
|
-
.filter((segment) => segment !== "" &&
|
|
273
|
-
segment !== "." &&
|
|
274
|
-
segment !== ".." &&
|
|
275
|
-
!/^[A-Za-z]:$/.test(segment))
|
|
276
|
-
.join("/");
|
|
277
|
-
return `${file}/${cleanName(test.name)}`;
|
|
278
|
-
}
|
|
279
|
-
/** Reads the page's log once the test body is done and applies config `logs` (see judgePageLogs) */
|
|
280
|
-
async function judgeLogs(page, testFailed) {
|
|
281
|
-
if (!page)
|
|
282
|
-
return {};
|
|
283
|
-
try {
|
|
284
|
-
// Events and command replies share one ordered connection: after this, everything logged so far is in
|
|
285
|
-
await page.syncLogs();
|
|
286
|
-
}
|
|
287
|
-
catch (err) {
|
|
288
|
-
logger.debug("Could not sync the page's logs", { err });
|
|
111
|
+
await runOneTest(test, browser, this.run, this.options, loaded.srcDir, reporter);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return reporter.onEnd();
|
|
289
115
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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);
|
|
293
121
|
}
|
|
294
|
-
|
|
295
|
-
|
|
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);
|
|
296
144
|
}
|
|
297
|
-
return judgePageLogs({
|
|
298
|
-
config,
|
|
299
|
-
entries: page.getLogs(),
|
|
300
|
-
errors: page.pageErrors(),
|
|
301
|
-
dropped: page.logsDropped,
|
|
302
|
-
testFailed,
|
|
303
|
-
allowPageErrors: page.pageErrorsAllowed,
|
|
304
|
-
routeErrors: page.routeErrors(),
|
|
305
|
-
});
|
|
306
145
|
}
|
|
307
|
-
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
|
+
}
|