@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
package/dist/cli/main.d.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
err: (text: string) => void;
|
|
5
|
-
/** Whether output goes to a terminal that shows colours */
|
|
6
|
-
color: boolean;
|
|
7
|
-
env: Partial<NodeJS.ProcessEnv>;
|
|
8
|
-
/** Aborts a recording (Ctrl+C) */
|
|
9
|
-
signal?: AbortSignal;
|
|
10
|
-
}
|
|
1
|
+
import type { Io } from "./io.js";
|
|
2
|
+
import { USAGE } from "./usage.js";
|
|
3
|
+
export { USAGE };
|
|
11
4
|
/** The command line as `samurai` runs it. Returns the exit code */
|
|
12
5
|
export declare function main(argv: string[], io: Io): Promise<number>;
|
package/dist/cli/main.js
CHANGED
|
@@ -1,68 +1,13 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { parseCommandLine, parseRunnerFlags, parseRunOverrides, } from "./args.js";
|
|
3
|
-
import { recordSpec } from "../recorder/index.js";
|
|
4
2
|
import { listTests, runTests } from "../runner/run.js";
|
|
5
|
-
import { stepToSource } from "../steps/index.js";
|
|
6
3
|
import { ConsoleReporter } from "./console-reporter.js";
|
|
7
4
|
import { browsersCommand } from "./browsers.js";
|
|
8
5
|
import { flowCommand } from "./flow.js";
|
|
9
6
|
import { initProject } from "./init.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
samurai list [options] List the tests without running them
|
|
15
|
-
samurai record <spec> Open a browser and record what you do into a spec file
|
|
16
|
-
samurai init [folder] Scaffold a project: config, an example test, .env.example
|
|
17
|
-
samurai flow <command> List, check and run the flows in ./flows (see below)
|
|
18
|
-
samurai browsers [command] List, install and remove the browsers SAMURAI launches
|
|
19
|
-
|
|
20
|
-
Options for run and list:
|
|
21
|
-
--env <name> Environment to run against (or SAMURAI_ENV)
|
|
22
|
-
--file <spec> Only this spec file; repeatable
|
|
23
|
-
--grep <text> Only tests whose full name contains the text
|
|
24
|
-
--group <name> Only the tests of this group of the config's groups
|
|
25
|
-
--json Machine-readable output: JSON lines for run, one array for list
|
|
26
|
-
|
|
27
|
-
Options for run:
|
|
28
|
-
--timeout <ms> Time one test may take
|
|
29
|
-
--expect-timeout <ms> Time actions and assertions retry
|
|
30
|
-
--headless, --no-headless Run the browser without a window, or with one.
|
|
31
|
-
Default: the config's use.headless, else headless when CI is set
|
|
32
|
-
--port <n> Browser debugging port (default 9223)
|
|
33
|
-
|
|
34
|
-
Options for record (also --env, --port, --json, --headless, --no-headless):
|
|
35
|
-
--new <title> Add a test with this title (creates the spec file if needed)
|
|
36
|
-
--test <name|index> Record into this test; not needed when the file has one
|
|
37
|
-
--at <n> Insert from step n (default: after the last step)
|
|
38
|
-
--url <url> Page to start on (default: the environment's baseURL)
|
|
39
|
-
Stop with Ctrl+C or by closing the browser window.
|
|
40
|
-
|
|
41
|
-
-h, --help Show this help
|
|
42
|
-
-v, --version Show the version
|
|
43
|
-
|
|
44
|
-
Exit code: 0 when every test passed, 1 when a test failed, 2 for a usage or configuration error.
|
|
45
|
-
|
|
46
|
-
samurai flow:
|
|
47
|
-
list The flows in ./flows
|
|
48
|
-
check [<flow>...] Check flows without running them
|
|
49
|
-
run <flow>... | --all Run flows; each writes result/flows/<id>.json
|
|
50
|
-
Options for run: --env, --headless, --timeout, --expect-timeout, --port, --json, --allow-unsupported
|
|
51
|
-
Run \`samurai flow\` alone for the details.
|
|
52
|
-
|
|
53
|
-
samurai browsers:
|
|
54
|
-
list The pinned browsers and every build in the cache (the default)
|
|
55
|
-
install [firefox|chrome|all]
|
|
56
|
-
Download the pinned builds (default: all)
|
|
57
|
-
uninstall <name> [--build <id>] | --unused
|
|
58
|
-
|
|
59
|
-
Run it in the project folder, the one holding samurai.config.ts.
|
|
60
|
-
`;
|
|
61
|
-
function version() {
|
|
62
|
-
const manifest = new URL("../../package.json", import.meta.url);
|
|
63
|
-
return JSON.parse(readFileSync(manifest, "utf8"))
|
|
64
|
-
.version;
|
|
65
|
-
}
|
|
7
|
+
import { recordCommand } from "./record.js";
|
|
8
|
+
import { USAGE } from "./usage.js";
|
|
9
|
+
import { frameworkVersion } from "./version.js";
|
|
10
|
+
export { USAGE };
|
|
66
11
|
/** The command line as `samurai` runs it. Returns the exit code */
|
|
67
12
|
export async function main(argv, io) {
|
|
68
13
|
try {
|
|
@@ -75,7 +20,7 @@ export async function main(argv, io) {
|
|
|
75
20
|
return 0;
|
|
76
21
|
}
|
|
77
22
|
if (values.version) {
|
|
78
|
-
io.out(`${
|
|
23
|
+
io.out(`${frameworkVersion()}\n`);
|
|
79
24
|
return 0;
|
|
80
25
|
}
|
|
81
26
|
if (command === "flow")
|
|
@@ -91,93 +36,22 @@ export async function main(argv, io) {
|
|
|
91
36
|
io.out("\nNext: install the dependencies, then `samurai browsers install firefox`, then `samurai run`.\n");
|
|
92
37
|
return 0;
|
|
93
38
|
}
|
|
94
|
-
if (command === "record")
|
|
95
|
-
|
|
96
|
-
if (!file)
|
|
97
|
-
throw new Error("Which spec? Usage: samurai record <spec> [--new <title>]");
|
|
98
|
-
const at = values.at === undefined ? undefined : Number(values.at);
|
|
99
|
-
if (at !== undefined && (!Number.isInteger(at) || at < 0))
|
|
100
|
-
throw new Error(`--at must be a step number, got "${values.at}"`);
|
|
101
|
-
const flags = parseRunnerFlags(rest);
|
|
102
|
-
const test = values.test === undefined
|
|
103
|
-
? undefined
|
|
104
|
-
: /^\d+$/.test(values.test)
|
|
105
|
-
? Number(values.test)
|
|
106
|
-
: values.test;
|
|
107
|
-
let count = 0;
|
|
108
|
-
let state = "recording";
|
|
109
|
-
let toolbarWarned = false;
|
|
110
|
-
const result = await recordSpec({
|
|
111
|
-
...parseRunOverrides(rest, io.env),
|
|
112
|
-
file,
|
|
113
|
-
...(test !== undefined && { test }),
|
|
114
|
-
...(values.new !== undefined && { create: values.new }),
|
|
115
|
-
...(at !== undefined && { at }),
|
|
116
|
-
...(values.url !== undefined && { url: values.url }),
|
|
117
|
-
...(flags.headless !== undefined && { headless: flags.headless }),
|
|
118
|
-
...(flags.port !== undefined && { port: flags.port }),
|
|
119
|
-
...(io.signal && { signal: io.signal }),
|
|
120
|
-
onStarted: () => {
|
|
121
|
-
if (!values.json)
|
|
122
|
-
io.out("Recording. Alt+click an element to assert on it. Press Stop in the toolbar, Ctrl+C or close the window to stop.\n");
|
|
123
|
-
},
|
|
124
|
-
onStatus: (status) => {
|
|
125
|
-
if (values.json)
|
|
126
|
-
return;
|
|
127
|
-
if (!status.toolbar && !toolbarWarned) {
|
|
128
|
-
toolbarWarned = true;
|
|
129
|
-
io.out("The toolbar couldn't load on this page. Alt+click still records assertions.\n");
|
|
130
|
-
}
|
|
131
|
-
if (status.state !== state) {
|
|
132
|
-
state = status.state;
|
|
133
|
-
io.out(state === "paused" ? "Paused\n" : "Recording\n");
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
onMissingFile: (_file, text) => {
|
|
137
|
-
if (!values.json)
|
|
138
|
-
io.out(`${text}\n`);
|
|
139
|
-
},
|
|
140
|
-
onEvent: (event) => {
|
|
141
|
-
if (values.json)
|
|
142
|
-
return void io.out(`${JSON.stringify(event)}\n`);
|
|
143
|
-
if (event.op === "remove") {
|
|
144
|
-
count--;
|
|
145
|
-
return void io.out(` - step ${event.index + 1} (the click that opened the file picker)\n`);
|
|
146
|
-
}
|
|
147
|
-
io.out(` ${event.op === "insert" ? "+" : "~"} ${stepToSource(event.step)}\n`);
|
|
148
|
-
if (event.op === "insert")
|
|
149
|
-
count++;
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
if (!values.json) {
|
|
153
|
-
io.out(`\nRecorded ${count} ${count === 1 ? "step" : "steps"} into "${result.test}" in ${result.file}\n`);
|
|
154
|
-
if (result.unsetSecrets.length > 0) {
|
|
155
|
-
io.out(`Set ${result.unsetSecrets.map((name) => `SAMURAI_SECRET_${name}`).join(", ")} (environment variable or .env.<environment>) before running it.\n`);
|
|
156
|
-
}
|
|
157
|
-
if (result.missingFiles.length > 0) {
|
|
158
|
-
io.out(`Add ${result.missingFiles.map((f) => f.path).join(", ")} before running it (or use generatedFile).\n`);
|
|
159
|
-
}
|
|
160
|
-
const hasFiles = result.files.length + result.missingFiles.length > 0;
|
|
161
|
-
const folder = result.filesFolder === "" ? "." : result.filesFolder;
|
|
162
|
-
if (hasFiles && result.filesIgnored)
|
|
163
|
-
io.out(`${folder}/ is ignored by git; CI won't have these files.\n`);
|
|
164
|
-
if (hasFiles && result.firstFiles)
|
|
165
|
-
io.out(`Recorded files are stored in ${folder}/ and committed with your tests.\n`);
|
|
166
|
-
}
|
|
167
|
-
return 0;
|
|
168
|
-
}
|
|
39
|
+
if (command === "record")
|
|
40
|
+
return await recordCommand({ values, positionals }, io);
|
|
169
41
|
if (command !== "run" && command !== "list") {
|
|
170
42
|
io.err(`Unknown command "${command}"\n\n${USAGE}`);
|
|
171
43
|
return 2;
|
|
172
44
|
}
|
|
173
|
-
const flags = parseRunnerFlags(
|
|
45
|
+
const flags = parseRunnerFlags(values);
|
|
174
46
|
const options = {
|
|
175
|
-
...parseRunOverrides(
|
|
47
|
+
...parseRunOverrides(values, io.env),
|
|
176
48
|
...(flags.headless !== undefined && { headless: flags.headless }),
|
|
177
49
|
...(flags.port !== undefined && { port: flags.port }),
|
|
50
|
+
...(flags.workers !== undefined && { workers: flags.workers }),
|
|
178
51
|
...(flags.grep !== undefined && { grep: flags.grep }),
|
|
179
52
|
...(flags.group !== undefined && { group: flags.group }),
|
|
180
53
|
...(flags.files && { files: flags.files }),
|
|
54
|
+
...(flags.browsers && { browsers: flags.browsers }),
|
|
181
55
|
};
|
|
182
56
|
if (command === "list") {
|
|
183
57
|
const tests = await listTests(options);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ParsedCommandLine } from "./args.js";
|
|
2
|
+
import type { Io } from "./io.js";
|
|
3
|
+
/** `samurai record <spec>`: records into a spec file and prints what happened. Returns the exit code */
|
|
4
|
+
export declare function recordCommand({ values, positionals }: ParsedCommandLine, io: Io): Promise<number>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { recordSpec } from "../recorder/index.js";
|
|
2
|
+
import { stepToSource } from "../steps/index.js";
|
|
3
|
+
import { parseRunnerFlags, parseRunOverrides, } from "./args.js";
|
|
4
|
+
/** Prints a recording for a person, or as JSON lines, while it happens; counts the steps written */
|
|
5
|
+
function recordingPrinter(io, json) {
|
|
6
|
+
let count = 0;
|
|
7
|
+
let state = "recording";
|
|
8
|
+
let toolbarWarned = false;
|
|
9
|
+
const handlers = {
|
|
10
|
+
onStarted: () => {
|
|
11
|
+
if (!json)
|
|
12
|
+
io.out("Recording. Alt+click an element to assert on it. Press Stop in the toolbar, Ctrl+C or close the window to stop.\n");
|
|
13
|
+
},
|
|
14
|
+
onStatus: (status) => {
|
|
15
|
+
if (json)
|
|
16
|
+
return;
|
|
17
|
+
if (!status.toolbar && !toolbarWarned) {
|
|
18
|
+
toolbarWarned = true;
|
|
19
|
+
io.out("The toolbar couldn't load on this page. Alt+click still records assertions.\n");
|
|
20
|
+
}
|
|
21
|
+
if (status.state !== state) {
|
|
22
|
+
state = status.state;
|
|
23
|
+
io.out(state === "paused" ? "Paused\n" : "Recording\n");
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
onMissingFile: (_file, text) => {
|
|
27
|
+
if (!json)
|
|
28
|
+
io.out(`${text}\n`);
|
|
29
|
+
},
|
|
30
|
+
onEvent: (event) => {
|
|
31
|
+
if (json)
|
|
32
|
+
return void io.out(`${JSON.stringify(event)}\n`);
|
|
33
|
+
if (event.op === "remove") {
|
|
34
|
+
count--;
|
|
35
|
+
return void io.out(` - step ${event.index + 1} (the click that opened the file picker)\n`);
|
|
36
|
+
}
|
|
37
|
+
io.out(` ${event.op === "insert" ? "+" : "~"} ${stepToSource(event.step)}\n`);
|
|
38
|
+
if (event.op === "insert")
|
|
39
|
+
count++;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
return { handlers, steps: () => count };
|
|
43
|
+
}
|
|
44
|
+
/** What is left to do after a recording: the steps written, secrets to set, files to add, notes on the files folder */
|
|
45
|
+
function summarise(io, result, steps) {
|
|
46
|
+
io.out(`\nRecorded ${steps} ${steps === 1 ? "step" : "steps"} into "${result.test}" in ${result.file}\n`);
|
|
47
|
+
if (result.unsetSecrets.length > 0) {
|
|
48
|
+
io.out(`Set ${result.unsetSecrets.map((name) => `SAMURAI_SECRET_${name}`).join(", ")} (environment variable or .env.<environment>) before running it.\n`);
|
|
49
|
+
}
|
|
50
|
+
if (result.missingFiles.length > 0) {
|
|
51
|
+
io.out(`Add ${result.missingFiles.map((f) => f.path).join(", ")} before running it (or use generatedFile).\n`);
|
|
52
|
+
}
|
|
53
|
+
const hasFiles = result.files.length + result.missingFiles.length > 0;
|
|
54
|
+
const folder = result.filesFolder === "" ? "." : result.filesFolder;
|
|
55
|
+
if (hasFiles && result.filesIgnored)
|
|
56
|
+
io.out(`${folder}/ is ignored by git; CI won't have these files.\n`);
|
|
57
|
+
if (hasFiles && result.firstFiles)
|
|
58
|
+
io.out(`Recorded files are stored in ${folder}/ and committed with your tests.\n`);
|
|
59
|
+
}
|
|
60
|
+
/** `samurai record <spec>`: records into a spec file and prints what happened. Returns the exit code */
|
|
61
|
+
export async function recordCommand({ values, positionals }, io) {
|
|
62
|
+
const file = positionals[0];
|
|
63
|
+
if (!file)
|
|
64
|
+
throw new Error("Which spec? Usage: samurai record <spec> [--new <title>]");
|
|
65
|
+
const at = values.at === undefined ? undefined : Number(values.at);
|
|
66
|
+
if (at !== undefined && (!Number.isInteger(at) || at < 0))
|
|
67
|
+
throw new Error(`--at must be a step number, got "${values.at}"`);
|
|
68
|
+
const flags = parseRunnerFlags(values);
|
|
69
|
+
if (flags.browsers && flags.browsers.length > 1)
|
|
70
|
+
throw new Error("--browser: a recording uses one browser");
|
|
71
|
+
const test = values.test === undefined
|
|
72
|
+
? undefined
|
|
73
|
+
: /^\d+$/.test(values.test)
|
|
74
|
+
? Number(values.test)
|
|
75
|
+
: values.test;
|
|
76
|
+
const printer = recordingPrinter(io, values.json === true);
|
|
77
|
+
const result = await recordSpec({
|
|
78
|
+
...parseRunOverrides(values, io.env),
|
|
79
|
+
file,
|
|
80
|
+
...(test !== undefined && { test }),
|
|
81
|
+
...(values.new !== undefined && { create: values.new }),
|
|
82
|
+
...(at !== undefined && { at }),
|
|
83
|
+
...(values.url !== undefined && { url: values.url }),
|
|
84
|
+
...(flags.headless !== undefined && { headless: flags.headless }),
|
|
85
|
+
...(flags.port !== undefined && { port: flags.port }),
|
|
86
|
+
...(flags.browsers && { browser: flags.browsers[0] }),
|
|
87
|
+
...(io.signal && { signal: io.signal }),
|
|
88
|
+
...printer.handlers,
|
|
89
|
+
});
|
|
90
|
+
if (!values.json)
|
|
91
|
+
summarise(io, result, printer.steps());
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const USAGE = "samurai: browser tests over WebDriver BiDi\n\nUsage:\n samurai run [options] Run the tests (the default command)\n samurai list [options] List the tests without running them\n samurai record <spec> Open a browser and record what you do into a spec file\n samurai init [folder] Scaffold a project: config, an example test, .env.example\n samurai flow <command> List, check and run the flows in ./flows (see below)\n samurai browsers [command] List, install and remove the browsers SAMURAI launches\n\nOptions for run and list:\n --env <name> Environment to run against (or SAMURAI_ENV)\n --file <spec> Only this spec file; repeatable\n --grep <text> Only tests whose full name contains the text\n --group <name> Only the tests of this group of the config's groups\n --browser <name> Run in this browser (firefox or chrome) instead of the config's\n browsers; repeat it for more than one\n --json Machine-readable output: JSON lines for run, one array for list\n\nOptions for run:\n --timeout <ms> Time one test may take\n --expect-timeout <ms> Time actions and assertions retry\n --headless, --no-headless Run the browser without a window, or with one.\n Default: the config's use.headless, else headless when CI is set\n --port <n> Browser debugging port (default 9223)\n --workers <n|n%> Tests that run at the same time, each in a worker process: a number\n from 1, or a share of the CPUs like 50%. Default: the config's\n workers (or SAMURAI_WORKERS), else half the CPUs\n\nOptions for record (also --env, --port, --json, --headless, --no-headless):\n --browser <name> Record in this browser (default: the first of the config's browsers)\n --new <title> Add a test with this title (creates the spec file if needed)\n --test <name|index> Record into this test; not needed when the file has one\n --at <n> Insert from step n (default: after the last step)\n --url <url> Page to start on (default: the environment's baseURL)\n Stop with Ctrl+C or by closing the browser window.\n\n -h, --help Show this help\n -v, --version Show the version\n\nExit code: 0 when every test passed, 1 when a test failed, 2 for a usage or configuration error.\n\nsamurai flow:\n list The flows in ./flows\n check [<flow>...] Check flows without running them\n run <flow>... | --all Run flows; each writes result/flows/<id>.json\n Options for run: --env, --headless, --timeout, --expect-timeout, --port, --json, --allow-unsupported\n Run `samurai flow` alone for the details.\n\nsamurai browsers:\n list The pinned browsers and every build in the cache (the default)\n install [firefox|chrome|all]\n Download the pinned builds (default: all)\n uninstall <name> [--build <id>] | --unused\n\nRun it in the project folder, the one holding samurai.config.ts.\n";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DEFAULT_PORT } from "../runner/run.js";
|
|
2
|
+
export const USAGE = `samurai: browser tests over WebDriver BiDi
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
samurai run [options] Run the tests (the default command)
|
|
6
|
+
samurai list [options] List the tests without running them
|
|
7
|
+
samurai record <spec> Open a browser and record what you do into a spec file
|
|
8
|
+
samurai init [folder] Scaffold a project: config, an example test, .env.example
|
|
9
|
+
samurai flow <command> List, check and run the flows in ./flows (see below)
|
|
10
|
+
samurai browsers [command] List, install and remove the browsers SAMURAI launches
|
|
11
|
+
|
|
12
|
+
Options for run and list:
|
|
13
|
+
--env <name> Environment to run against (or SAMURAI_ENV)
|
|
14
|
+
--file <spec> Only this spec file; repeatable
|
|
15
|
+
--grep <text> Only tests whose full name contains the text
|
|
16
|
+
--group <name> Only the tests of this group of the config's groups
|
|
17
|
+
--browser <name> Run in this browser (firefox or chrome) instead of the config's
|
|
18
|
+
browsers; repeat it for more than one
|
|
19
|
+
--json Machine-readable output: JSON lines for run, one array for list
|
|
20
|
+
|
|
21
|
+
Options for run:
|
|
22
|
+
--timeout <ms> Time one test may take
|
|
23
|
+
--expect-timeout <ms> Time actions and assertions retry
|
|
24
|
+
--headless, --no-headless Run the browser without a window, or with one.
|
|
25
|
+
Default: the config's use.headless, else headless when CI is set
|
|
26
|
+
--port <n> Browser debugging port (default ${DEFAULT_PORT})
|
|
27
|
+
--workers <n|n%> Tests that run at the same time, each in a worker process: a number
|
|
28
|
+
from 1, or a share of the CPUs like 50%. Default: the config's
|
|
29
|
+
workers (or SAMURAI_WORKERS), else half the CPUs
|
|
30
|
+
|
|
31
|
+
Options for record (also --env, --port, --json, --headless, --no-headless):
|
|
32
|
+
--browser <name> Record in this browser (default: the first of the config's browsers)
|
|
33
|
+
--new <title> Add a test with this title (creates the spec file if needed)
|
|
34
|
+
--test <name|index> Record into this test; not needed when the file has one
|
|
35
|
+
--at <n> Insert from step n (default: after the last step)
|
|
36
|
+
--url <url> Page to start on (default: the environment's baseURL)
|
|
37
|
+
Stop with Ctrl+C or by closing the browser window.
|
|
38
|
+
|
|
39
|
+
-h, --help Show this help
|
|
40
|
+
-v, --version Show the version
|
|
41
|
+
|
|
42
|
+
Exit code: 0 when every test passed, 1 when a test failed, 2 for a usage or configuration error.
|
|
43
|
+
|
|
44
|
+
samurai flow:
|
|
45
|
+
list The flows in ./flows
|
|
46
|
+
check [<flow>...] Check flows without running them
|
|
47
|
+
run <flow>... | --all Run flows; each writes result/flows/<id>.json
|
|
48
|
+
Options for run: --env, --headless, --timeout, --expect-timeout, --port, --json, --allow-unsupported
|
|
49
|
+
Run \`samurai flow\` alone for the details.
|
|
50
|
+
|
|
51
|
+
samurai browsers:
|
|
52
|
+
list The pinned browsers and every build in the cache (the default)
|
|
53
|
+
install [firefox|chrome|all]
|
|
54
|
+
Download the pinned builds (default: all)
|
|
55
|
+
uninstall <name> [--build <id>] | --unused
|
|
56
|
+
|
|
57
|
+
Run it in the project folder, the one holding samurai.config.ts.
|
|
58
|
+
`;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SupportedBrowser } from "../types/browser.js";
|
|
2
|
+
import type { SamuraiTestConfig } from "../types/config.js";
|
|
3
|
+
/** Every browser a config may name, in the order the messages list them */
|
|
4
|
+
export declare const BROWSER_NAMES: readonly SupportedBrowser[];
|
|
5
|
+
/** Throws when `list` is not a non-empty list of distinct browser names; `where` names the key in the message */
|
|
6
|
+
export declare function checkBrowserList(list: unknown, where: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Checks the browser keys of a config: the removed `browser`, and `browsers` on the project and on each group.
|
|
9
|
+
* Throws an Error that says what to write instead.
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateBrowserConfig(config: SamuraiTestConfig): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Every browser a config may name, in the order the messages list them */
|
|
2
|
+
export const BROWSER_NAMES = ["firefox", "chrome"];
|
|
3
|
+
const isBrowser = (value) => typeof value === "string" &&
|
|
4
|
+
BROWSER_NAMES.includes(value);
|
|
5
|
+
/** Throws when `list` is not a non-empty list of distinct browser names; `where` names the key in the message */
|
|
6
|
+
export function checkBrowserList(list, where) {
|
|
7
|
+
if (!Array.isArray(list) || list.length === 0)
|
|
8
|
+
throw new Error(`${where} must list at least one browser.`);
|
|
9
|
+
const seen = new Set();
|
|
10
|
+
for (const name of list) {
|
|
11
|
+
if (!isBrowser(name))
|
|
12
|
+
throw new Error(`Unknown browser "${String(name)}" in ${where}. Choose ${BROWSER_NAMES.join(" or ")}.`);
|
|
13
|
+
if (seen.has(name))
|
|
14
|
+
throw new Error(`Browser "${name}" is listed twice in ${where}.`);
|
|
15
|
+
seen.add(name);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Checks the browser keys of a config: the removed `browser`, and `browsers` on the project and on each group.
|
|
20
|
+
* Throws an Error that says what to write instead.
|
|
21
|
+
*/
|
|
22
|
+
export function validateBrowserConfig(config) {
|
|
23
|
+
const legacy = config.browser;
|
|
24
|
+
if (legacy !== undefined)
|
|
25
|
+
throw new Error(`"browser" was replaced by "browsers": use browsers: [${JSON.stringify(legacy)}]`);
|
|
26
|
+
if (config.browsers !== undefined)
|
|
27
|
+
checkBrowserList(config.browsers, "browsers");
|
|
28
|
+
config.groups?.forEach((group, index) => {
|
|
29
|
+
if (group.browsers !== undefined)
|
|
30
|
+
checkBrowserList(group.browsers, `groups[${index}].browsers`);
|
|
31
|
+
});
|
|
32
|
+
}
|
package/dist/config/config.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import logger from "../logger/index.js";
|
|
3
3
|
import { pathToFileURL } from "url";
|
|
4
|
+
import { validateBrowserConfig } from "./browsers.js";
|
|
5
|
+
import { validateWorkersConfig } from "./workers.js";
|
|
4
6
|
let activeProject;
|
|
5
7
|
/** Makes `project` the one config, secrets, reports and downloads are resolved for; `undefined` ends it */
|
|
6
8
|
export function setActiveProject(project) {
|
|
@@ -22,14 +24,19 @@ export function browsersDir() {
|
|
|
22
24
|
}
|
|
23
25
|
/** The whole `samurai.config.ts` of the project (the working directory unless a project is active) */
|
|
24
26
|
export async function loadConfig() {
|
|
25
|
-
if (activeProject?.config)
|
|
27
|
+
if (activeProject?.config) {
|
|
28
|
+
validateBrowserConfig(activeProject.config);
|
|
29
|
+
validateWorkersConfig(activeProject.config);
|
|
26
30
|
return activeProject.config;
|
|
31
|
+
}
|
|
27
32
|
const configUrl = path.join(projectDir(), "samurai.config.ts");
|
|
28
33
|
logger.debug("Reading config from: %s", configUrl);
|
|
29
34
|
const config = (await import(pathToFileURL(configUrl).href));
|
|
30
35
|
if (!config.default) {
|
|
31
36
|
throw new Error("samurai.config.ts does not export defineConfig as default!");
|
|
32
37
|
}
|
|
38
|
+
validateBrowserConfig(config.default);
|
|
39
|
+
validateWorkersConfig(config.default);
|
|
33
40
|
return config.default;
|
|
34
41
|
}
|
|
35
42
|
/** A project's config without keeping the project active (`withProject` is one at a time): the given one, else its samurai.config.ts */
|
package/dist/config/mask.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const MASK = "\u2022\u2022\u2022\u2022";
|
|
2
2
|
/** Registers a secret value; every later maskText/maskDeep/log line hides it */
|
|
3
3
|
export declare function registerSecret(name: string, value: string): void;
|
|
4
|
-
/** Forgets every registered secret
|
|
4
|
+
/** Forgets every registered secret: a run starts with none */
|
|
5
5
|
export declare function resetSecrets(): void;
|
|
6
6
|
export declare function isShortSecret(value: string): boolean;
|
|
7
7
|
export declare function maskText(text: string): string;
|
package/dist/config/mask.js
CHANGED
|
@@ -12,7 +12,7 @@ export function registerSecret(name, value) {
|
|
|
12
12
|
secrets.set(name, value);
|
|
13
13
|
rebuild();
|
|
14
14
|
}
|
|
15
|
-
/** Forgets every registered secret
|
|
15
|
+
/** Forgets every registered secret: a run starts with none */
|
|
16
16
|
export function resetSecrets() {
|
|
17
17
|
secrets.clear();
|
|
18
18
|
patterns = [];
|
|
@@ -25,7 +25,12 @@ function escapeRegExp(value) {
|
|
|
25
25
|
}
|
|
26
26
|
/** A value plus its JSON-escaped, inspect-escaped and percent-encoded spellings */
|
|
27
27
|
function variants(value) {
|
|
28
|
-
const forms = [
|
|
28
|
+
const forms = [
|
|
29
|
+
value,
|
|
30
|
+
JSON.stringify(value).slice(1, -1),
|
|
31
|
+
inspect(value).slice(1, -1),
|
|
32
|
+
encodeURIComponent(value),
|
|
33
|
+
];
|
|
29
34
|
return forms.filter((form) => form !== "");
|
|
30
35
|
}
|
|
31
36
|
function rebuild() {
|
|
@@ -23,9 +23,9 @@ export interface RunSettings {
|
|
|
23
23
|
export declare class UnknownEnvironmentError extends Error {
|
|
24
24
|
constructor(message: string);
|
|
25
25
|
}
|
|
26
|
-
/** Picks the environment and applies precedence: run override > environment > project > built-in default */
|
|
27
26
|
/** True when the `CI` environment variable says this is a CI run (set, and not "", "0" or "false") */
|
|
28
27
|
export declare function isCI(env?: Partial<NodeJS.ProcessEnv>): boolean;
|
|
28
|
+
/** Picks the environment and applies precedence: run override > environment > project > built-in default */
|
|
29
29
|
export declare function resolveRunSettings(config: SamuraiTestConfig, overrides?: RunOverrides,
|
|
30
30
|
/** Where `CI` is read from */
|
|
31
31
|
env?: Partial<NodeJS.ProcessEnv>): RunSettings;
|
|
@@ -33,4 +33,3 @@ env?: Partial<NodeJS.ProcessEnv>): RunSettings;
|
|
|
33
33
|
export declare function setRunSettings(settings: RunSettings | undefined): void;
|
|
34
34
|
/** The active run's settings, or undefined outside a run (unit and browser tests) */
|
|
35
35
|
export declare function peekRunSettings(): RunSettings | undefined;
|
|
36
|
-
export declare function getRunSettings(): RunSettings;
|
|
@@ -8,12 +8,12 @@ export class UnknownEnvironmentError extends Error {
|
|
|
8
8
|
this.name = "UnknownEnvironmentError";
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
/** Picks the environment and applies precedence: run override > environment > project > built-in default */
|
|
12
11
|
/** True when the `CI` environment variable says this is a CI run (set, and not "", "0" or "false") */
|
|
13
12
|
export function isCI(env = process.env) {
|
|
14
13
|
const value = env.CI?.trim().toLowerCase();
|
|
15
14
|
return (value !== undefined && value !== "" && value !== "0" && value !== "false");
|
|
16
15
|
}
|
|
16
|
+
/** Picks the environment and applies precedence: run override > environment > project > built-in default */
|
|
17
17
|
export function resolveRunSettings(config, overrides = {},
|
|
18
18
|
/** Where `CI` is read from */
|
|
19
19
|
env = process.env) {
|
|
@@ -65,8 +65,3 @@ export function setRunSettings(settings) {
|
|
|
65
65
|
export function peekRunSettings() {
|
|
66
66
|
return active;
|
|
67
67
|
}
|
|
68
|
-
export function getRunSettings() {
|
|
69
|
-
if (!active)
|
|
70
|
-
throw new Error("No test run is active; run settings are set when the runner starts");
|
|
71
|
-
return active;
|
|
72
|
-
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { readConfig } from "./config.js";
|
|
2
|
+
import { peekRunSettings } from "./run-settings.js";
|
|
3
|
+
/** The retry time of actions and assertions when nothing sets one (ms) */
|
|
4
|
+
const DEFAULT_EXPECT_TIMEOUT = 5000;
|
|
5
|
+
/** Per-call timeout, else the active run's expect timeout, else config `expect.timeout`, else 5000 */
|
|
6
|
+
export async function resolveTimeout(perCall) {
|
|
7
|
+
if (perCall !== undefined)
|
|
8
|
+
return perCall;
|
|
9
|
+
const run = peekRunSettings();
|
|
10
|
+
if (run)
|
|
11
|
+
return run.expectTimeout;
|
|
12
|
+
return (await readConfig("expect"))?.timeout ?? DEFAULT_EXPECT_TIMEOUT;
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SamuraiTestConfig } from "../types/config.js";
|
|
2
|
+
/** A number of workers, or a share of the CPUs */
|
|
3
|
+
export type ParsedWorkers = {
|
|
4
|
+
count: number;
|
|
5
|
+
} | {
|
|
6
|
+
percent: number;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Reads a `workers` value: a whole number from 1, as a number or as text, or a percentage like `50%` when
|
|
10
|
+
* `percentages` is on. `where` names the source (`--workers`, `SAMURAI_WORKERS`, a config key) in the error.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseWorkers(value: unknown, where: string, percentages?: boolean): ParsedWorkers;
|
|
13
|
+
/** Checks the `workers` keys of a config: the project's and each group's. Throws an Error that names the key */
|
|
14
|
+
export declare function validateWorkersConfig(config: SamuraiTestConfig): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a `workers` value: a whole number from 1, as a number or as text, or a percentage like `50%` when
|
|
3
|
+
* `percentages` is on. `where` names the source (`--workers`, `SAMURAI_WORKERS`, a config key) in the error.
|
|
4
|
+
*/
|
|
5
|
+
export function parseWorkers(value, where, percentages = true) {
|
|
6
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 1)
|
|
7
|
+
return { count: value };
|
|
8
|
+
if (typeof value === "string") {
|
|
9
|
+
const text = value.trim();
|
|
10
|
+
if (/^\d+$/.test(text) && Number(text) >= 1)
|
|
11
|
+
return { count: Number(text) };
|
|
12
|
+
const share = /^(\d+)%$/.exec(text);
|
|
13
|
+
if (percentages && share) {
|
|
14
|
+
const percent = Number(share[1]);
|
|
15
|
+
if (percent >= 1 && percent <= 100)
|
|
16
|
+
return { percent };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
throw new Error(`${where} must be a whole number from 1${percentages ? " or a percentage like 50%" : ""}, got "${String(value)}"`);
|
|
20
|
+
}
|
|
21
|
+
/** Checks the `workers` keys of a config: the project's and each group's. Throws an Error that names the key */
|
|
22
|
+
export function validateWorkersConfig(config) {
|
|
23
|
+
if (config.workers !== undefined)
|
|
24
|
+
parseWorkers(config.workers, "workers");
|
|
25
|
+
config.groups?.forEach((group, index) => {
|
|
26
|
+
if (group.workers !== undefined)
|
|
27
|
+
parseWorkers(group.workers, `groups[${index}].workers`, false);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ExpressionContext } from "../core/index.js";
|
|
2
|
+
/** A branch of a Parallel node: its own copy of the variables and node outputs, and the variables as they were when it forked */
|
|
3
|
+
export interface Fork {
|
|
4
|
+
context: ExpressionContext;
|
|
5
|
+
base: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export declare function forkContext(context: ExpressionContext): Fork;
|
|
8
|
+
/**
|
|
9
|
+
* Takes what the branches wrote into `into`: every node output, and every variable a branch changed. A variable
|
|
10
|
+
* two branches changed to different values is a clash and is not taken; the first clash is returned.
|
|
11
|
+
*/
|
|
12
|
+
export declare function mergeForks(into: ExpressionContext, forks: readonly Fork[]): {
|
|
13
|
+
clash?: string;
|
|
14
|
+
};
|