@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,26 +1,19 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { mkdtemp, open, rm } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
2
|
import logger from "../logger/index.js";
|
|
6
3
|
import { addPreload, runInLoadedContexts } from "../script/preload.js";
|
|
7
|
-
import { callFunction } from "../script/call-function.js";
|
|
8
4
|
import { ChannelArg, fromRemoteValue } from "../script/serialize.js";
|
|
9
|
-
import { ElementHandle } from "../script/element-handle.js";
|
|
10
|
-
import { locatorFromSpec } from "../steps/build.js";
|
|
11
|
-
import { addVariable, cleanFileName, filesFolder, RECORD_LIMIT, setSecret, storeFile, } from "../project/index.js";
|
|
12
|
-
import { humanSize } from "../steps/files.js";
|
|
13
5
|
import { CAPTURE_MUTE, CAPTURE_OFF, CAPTURE_SOURCE, CAPTURE_UNMUTE, } from "./capture.js";
|
|
14
6
|
import { FILES_SANDBOX, filesCaptureSource } from "./files-capture.js";
|
|
15
|
-
import { disposeFilesCapture,
|
|
16
|
-
import {
|
|
7
|
+
import { disposeFilesCapture, releasePageFile } from "./files-reader.js";
|
|
8
|
+
import { FilesRecorder, missingFileText } from "./files-recorder.js";
|
|
9
|
+
import { Normaliser } from "./normalise.js";
|
|
10
|
+
import { observationFor, } from "./page-messages.js";
|
|
11
|
+
import { listSandboxRealms } from "./realms.js";
|
|
17
12
|
import { rankLocator } from "./ranking.js";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { TOOLBAR_KEY, TOOLBAR_SANDBOX, } from "./toolbar/protocol.js";
|
|
13
|
+
import { TOOLBAR_HOOK, ToolbarBridge } from "./toolbar-bridge.js";
|
|
14
|
+
import { TOOLBAR_SANDBOX } from "./toolbar/protocol.js";
|
|
21
15
|
import { TOOLBAR_SOURCE } from "./toolbar/source.js";
|
|
22
|
-
|
|
23
|
-
const HOOK = `globalThis[Symbol.for(${JSON.stringify(TOOLBAR_KEY)})]`;
|
|
16
|
+
export { missingFileText };
|
|
24
17
|
/**
|
|
25
18
|
* Watches the tester use the page and reports steps as they happen. A preload script reports clicks,
|
|
26
19
|
* typing and Alt+click assertions over a `script.message` channel; each element is given locators that
|
|
@@ -47,13 +40,6 @@ export class Recorder {
|
|
|
47
40
|
/** The files capture script's channel, handed only to its sandbox */
|
|
48
41
|
this.filesChannel = `samurai-files-${randomUUID()}`;
|
|
49
42
|
this.filesRealms = new Set();
|
|
50
|
-
this.stored = new Set();
|
|
51
|
-
this.missing = [];
|
|
52
|
-
/** The context's `samurai-toolbar` sandbox realms, as last listed: the only realms toolbar messages are taken from */
|
|
53
|
-
this.toolbarRealms = new Set();
|
|
54
|
-
/** Toolbar messages waiting for a realm lookup, and those that arrived behind them (kept in order) */
|
|
55
|
-
this.toolbarBacklog = Promise.resolve();
|
|
56
|
-
this.toolbarWaiting = 0;
|
|
57
43
|
/**
|
|
58
44
|
* Page messages come only on the capture channel; toolbar messages only on the toolbar channel and
|
|
59
45
|
* only from the toolbar's sandbox realm. Neither channel is reachable from page script (each stays in
|
|
@@ -86,28 +72,9 @@ export class Recorder {
|
|
|
86
72
|
});
|
|
87
73
|
return;
|
|
88
74
|
}
|
|
89
|
-
if (params.channel !== this.toolbarChannel || !this.
|
|
75
|
+
if (params.channel !== this.toolbarChannel || !this.toolbar)
|
|
90
76
|
return;
|
|
91
|
-
|
|
92
|
-
if (this.toolbarWaiting === 0 && this.toolbarRealms.has(realm)) {
|
|
93
|
-
this.fromToolbar(params.data);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
// A sandbox not seen yet (a new document's toolbar): look it up, and keep later messages behind it
|
|
97
|
-
this.toolbarWaiting++;
|
|
98
|
-
this.toolbarBacklog = this.toolbarBacklog
|
|
99
|
-
.then(async () => {
|
|
100
|
-
if (!this.toolbarRealms.has(realm))
|
|
101
|
-
await this.listToolbarRealms();
|
|
102
|
-
if (this.toolbarRealms.has(realm))
|
|
103
|
-
this.fromToolbar(params.data);
|
|
104
|
-
else
|
|
105
|
-
logger.warn("Recorder ignored a toolbar message from outside the toolbar's sandbox");
|
|
106
|
-
})
|
|
107
|
-
.catch((err) => {
|
|
108
|
-
logger.debug("Could not check a toolbar message's realm", { err });
|
|
109
|
-
})
|
|
110
|
-
.finally(() => this.toolbarWaiting--);
|
|
77
|
+
this.toolbar.receive(params.source.realm ?? "", params.data);
|
|
111
78
|
};
|
|
112
79
|
this.onLoad = (params) => {
|
|
113
80
|
if (params.context === this.context && params.url !== "about:blank")
|
|
@@ -126,13 +93,17 @@ export class Recorder {
|
|
|
126
93
|
});
|
|
127
94
|
};
|
|
128
95
|
}
|
|
96
|
+
/** The toolbar's controller, when the recording has a toolbar */
|
|
97
|
+
get controller() {
|
|
98
|
+
return this.toolbar?.controller;
|
|
99
|
+
}
|
|
129
100
|
/** Files stored or reused in the files folder by this recording, project-relative */
|
|
130
101
|
get storedFiles() {
|
|
131
|
-
return
|
|
102
|
+
return this.files?.storedFiles ?? [];
|
|
132
103
|
}
|
|
133
104
|
/** Files whose steps point at a path that wasn't written */
|
|
134
105
|
get missingFiles() {
|
|
135
|
-
return
|
|
106
|
+
return this.files?.missingFiles ?? [];
|
|
136
107
|
}
|
|
137
108
|
static async start(target, options) {
|
|
138
109
|
const at = options.at ?? 0;
|
|
@@ -147,42 +118,13 @@ export class Recorder {
|
|
|
147
118
|
return recorder;
|
|
148
119
|
}
|
|
149
120
|
async listFilesRealms() {
|
|
150
|
-
|
|
151
|
-
context: this.context,
|
|
152
|
-
type: "window",
|
|
153
|
-
});
|
|
154
|
-
this.filesRealms = new Set(realms
|
|
155
|
-
.filter((info) => info.type === "window" && info.sandbox === FILES_SANDBOX)
|
|
156
|
-
.map((info) => info.realm));
|
|
157
|
-
}
|
|
158
|
-
async listToolbarRealms() {
|
|
159
|
-
const { realms } = await this.connector.send("script.getRealms", {
|
|
160
|
-
context: this.context,
|
|
161
|
-
type: "window",
|
|
162
|
-
});
|
|
163
|
-
this.toolbarRealms = new Set(realms
|
|
164
|
-
.filter((info) => info.type === "window" && info.sandbox === TOOLBAR_SANDBOX)
|
|
165
|
-
.map((info) => info.realm));
|
|
166
|
-
}
|
|
167
|
-
/** A message from the toolbar's own sandbox */
|
|
168
|
-
fromToolbar(data) {
|
|
169
|
-
const message = fromRemoteValue(data);
|
|
170
|
-
const controller = this.controller;
|
|
171
|
-
if (!controller || message.type !== "toolbar" || !("action" in message))
|
|
172
|
-
return;
|
|
173
|
-
if (message.action === "ready") {
|
|
174
|
-
// Not behind a slow insert: the ready watchdog is waiting for it
|
|
175
|
-
void controller.handle(message);
|
|
176
|
-
}
|
|
177
|
-
else if (controller.accept(message)) {
|
|
178
|
-
// Decided as it arrives: a double click's second copy would only run after the first had finished
|
|
179
|
-
this.enqueue(async () => controller.handle(message));
|
|
180
|
-
}
|
|
121
|
+
this.filesRealms = await listSandboxRealms(this.connector, this.context, FILES_SANDBOX);
|
|
181
122
|
}
|
|
182
123
|
async begin(initialGoto) {
|
|
183
124
|
const toolbar = this.options.toolbar;
|
|
184
125
|
if (toolbar)
|
|
185
|
-
this.
|
|
126
|
+
this.toolbar = this.createToolbar(toolbar);
|
|
127
|
+
this.files = this.createFiles();
|
|
186
128
|
this.connector.onEvent("script.message", this.onMessage);
|
|
187
129
|
this.connector.onEvent("browsingContext.navigationStarted", this.onNavigation);
|
|
188
130
|
if (toolbar)
|
|
@@ -215,7 +157,7 @@ export class Recorder {
|
|
|
215
157
|
onRunError: "log",
|
|
216
158
|
});
|
|
217
159
|
}
|
|
218
|
-
if (this.
|
|
160
|
+
if (this.toolbar) {
|
|
219
161
|
this.toolbarPreload = await addPreload(this.connector, {
|
|
220
162
|
source: TOOLBAR_SOURCE,
|
|
221
163
|
sandbox: TOOLBAR_SANDBOX,
|
|
@@ -224,7 +166,7 @@ export class Recorder {
|
|
|
224
166
|
onRunError: "log",
|
|
225
167
|
});
|
|
226
168
|
// The document that is loaded now gets its state and its ready watchdog as a new one would
|
|
227
|
-
this.controller.loaded();
|
|
169
|
+
this.toolbar.controller.loaded();
|
|
228
170
|
}
|
|
229
171
|
}
|
|
230
172
|
catch (err) {
|
|
@@ -233,43 +175,26 @@ export class Recorder {
|
|
|
233
175
|
throw err;
|
|
234
176
|
}
|
|
235
177
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
currentUrl: () => this.page.url(),
|
|
242
|
-
rank: (target, candidates) => rankLocator(this.page, target, candidates),
|
|
243
|
-
inspect: (target, locator, candidates) => inspectElement(this.page, this.connector, this.context, target, locator, candidates),
|
|
244
|
-
type: async (locator, value) => {
|
|
245
|
-
await this.mute();
|
|
246
|
-
try {
|
|
247
|
-
await locatorFromSpec(this.page, locator).fill(value, {
|
|
248
|
-
timeout: 5000,
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
finally {
|
|
252
|
-
await this.unmute();
|
|
253
|
-
}
|
|
254
|
-
},
|
|
178
|
+
createToolbar(toolbar) {
|
|
179
|
+
return new ToolbarBridge({
|
|
180
|
+
page: this.page,
|
|
181
|
+
connector: this.connector,
|
|
182
|
+
context: this.context,
|
|
255
183
|
mute: () => this.mute(),
|
|
256
184
|
unmute: () => this.unmute(),
|
|
257
|
-
addVariable: (name, value) => addVariable(projectDir, environment, name, value),
|
|
258
|
-
setSecret: (name, value) => setSecret(projectDir, environment, name, value),
|
|
259
|
-
secretAdded: (name) => onSecretAdded?.(name),
|
|
260
185
|
observe: (observation) => this.emit(this.normaliser.observe(observation, Date.now())),
|
|
261
|
-
stop: () =>
|
|
262
|
-
|
|
263
|
-
onStop();
|
|
264
|
-
else
|
|
265
|
-
void this.stop();
|
|
266
|
-
},
|
|
267
|
-
onStatus: (status) => onStatus?.(status),
|
|
186
|
+
stop: () => void this.stop(),
|
|
187
|
+
enqueue: (work) => this.enqueue(work),
|
|
268
188
|
}, toolbar);
|
|
269
189
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
190
|
+
createFiles() {
|
|
191
|
+
const recording = this.options.files;
|
|
192
|
+
if (!recording)
|
|
193
|
+
return undefined;
|
|
194
|
+
return new FilesRecorder(this.connector, this.context, recording, {
|
|
195
|
+
warn: (text) => this.controller?.warn(text),
|
|
196
|
+
saving: (name) => this.controller?.saving(name),
|
|
197
|
+
});
|
|
273
198
|
}
|
|
274
199
|
enqueue(work) {
|
|
275
200
|
this.queue.tail = this.queue.tail.then(work).catch((err) => {
|
|
@@ -304,29 +229,7 @@ export class Recorder {
|
|
|
304
229
|
logger.warn("Recorder found no locator that matches only the element the tester used; the %s was skipped", message.type);
|
|
305
230
|
return;
|
|
306
231
|
}
|
|
307
|
-
const
|
|
308
|
-
const observation = message.type === "click"
|
|
309
|
-
? { type: "click", key, locator, textEntry: message.textEntry === true }
|
|
310
|
-
: message.type === "press"
|
|
311
|
-
? { type: "press", key, locator, pressed: message.pressed ?? "" }
|
|
312
|
-
: message.type === "assert"
|
|
313
|
-
? {
|
|
314
|
-
type: "assert",
|
|
315
|
-
key,
|
|
316
|
-
locator,
|
|
317
|
-
text: message.text ?? "",
|
|
318
|
-
...(message.value !== undefined && { value: message.value }),
|
|
319
|
-
}
|
|
320
|
-
: {
|
|
321
|
-
type: "input",
|
|
322
|
-
key,
|
|
323
|
-
locator,
|
|
324
|
-
...(message.value !== undefined && { value: message.value }),
|
|
325
|
-
...(message.secret && { secret: true }),
|
|
326
|
-
...(message.secretName !== undefined && {
|
|
327
|
-
secretName: message.secretName,
|
|
328
|
-
}),
|
|
329
|
-
};
|
|
232
|
+
const observation = observationFor(message, locator);
|
|
330
233
|
this.emit(this.normaliser.observe(observation, Date.now()));
|
|
331
234
|
}
|
|
332
235
|
async fromFiles(message) {
|
|
@@ -352,7 +255,7 @@ export class Recorder {
|
|
|
352
255
|
const files = [];
|
|
353
256
|
for (const file of message.files) {
|
|
354
257
|
started++;
|
|
355
|
-
files.push({ path: await this.
|
|
258
|
+
files.push({ path: await this.files.store(file) });
|
|
356
259
|
}
|
|
357
260
|
this.emit(this.normaliser.observe({ type: "files", via: message.via, locator, files }, now));
|
|
358
261
|
}
|
|
@@ -360,62 +263,6 @@ export class Recorder {
|
|
|
360
263
|
await release(started);
|
|
361
264
|
}
|
|
362
265
|
}
|
|
363
|
-
/** Saves one kept file to the files folder; returns the path its step gets */
|
|
364
|
-
async storePageFile(file) {
|
|
365
|
-
const recording = this.options.files;
|
|
366
|
-
const folder = filesFolder(recording.projectDir, recording.filesDir);
|
|
367
|
-
const notSaved = (reason) => {
|
|
368
|
-
const missing = {
|
|
369
|
-
path: folder.relative
|
|
370
|
-
? `${folder.relative}/${cleanFileName(file.name)}`
|
|
371
|
-
: cleanFileName(file.name),
|
|
372
|
-
name: file.name,
|
|
373
|
-
size: file.size,
|
|
374
|
-
reason,
|
|
375
|
-
};
|
|
376
|
-
this.missing.push(missing);
|
|
377
|
-
recording.onMissing?.(missing);
|
|
378
|
-
this.controller?.warn(missingFileText(missing, folder.relative));
|
|
379
|
-
return missing.path;
|
|
380
|
-
};
|
|
381
|
-
if (file.size > RECORD_LIMIT) {
|
|
382
|
-
await releasePageFile(this.connector, this.context, file.id).catch(() => undefined);
|
|
383
|
-
return notSaved("too-big");
|
|
384
|
-
}
|
|
385
|
-
this.controller?.saving(file.name);
|
|
386
|
-
let temp;
|
|
387
|
-
try {
|
|
388
|
-
temp = await mkdtemp(path.join(tmpdir(), "samurai-upload-"));
|
|
389
|
-
const content = path.join(temp, "content");
|
|
390
|
-
const out = await open(content, "w");
|
|
391
|
-
try {
|
|
392
|
-
await readPageFile(this.connector, this.context, file.id, file.size, (bytes) => out.write(bytes));
|
|
393
|
-
}
|
|
394
|
-
finally {
|
|
395
|
-
await out.close();
|
|
396
|
-
}
|
|
397
|
-
const stored = await storeFile({
|
|
398
|
-
projectDir: recording.projectDir,
|
|
399
|
-
...(recording.filesDir !== undefined && {
|
|
400
|
-
filesDir: recording.filesDir,
|
|
401
|
-
}),
|
|
402
|
-
name: file.name,
|
|
403
|
-
content,
|
|
404
|
-
});
|
|
405
|
-
this.stored.add(stored.path);
|
|
406
|
-
return stored.path;
|
|
407
|
-
}
|
|
408
|
-
catch (err) {
|
|
409
|
-
logger.warn("Recorder could not save %s", file.name, { err });
|
|
410
|
-
return notSaved("unreadable");
|
|
411
|
-
}
|
|
412
|
-
finally {
|
|
413
|
-
this.controller?.saving(undefined);
|
|
414
|
-
if (temp)
|
|
415
|
-
await rm(temp, { recursive: true, force: true }).catch(() => undefined);
|
|
416
|
-
await releasePageFile(this.connector, this.context, file.id).catch(() => undefined);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
266
|
async release() {
|
|
420
267
|
this.connector.offEvent("script.message", this.onMessage);
|
|
421
268
|
this.connector.offEvent("browsingContext.navigationStarted", this.onNavigation);
|
|
@@ -466,7 +313,7 @@ export class Recorder {
|
|
|
466
313
|
if (this.toolbarPreload) {
|
|
467
314
|
// The window may stay open: take the toolbar and its listeners off the page
|
|
468
315
|
await runInLoadedContexts(this.connector, {
|
|
469
|
-
source: `() => { ${
|
|
316
|
+
source: `() => { ${TOOLBAR_HOOK}?.dispose(); }`,
|
|
470
317
|
contexts: [this.context],
|
|
471
318
|
sandbox: TOOLBAR_SANDBOX,
|
|
472
319
|
onRunError: "log",
|
|
@@ -480,10 +327,3 @@ export class Recorder {
|
|
|
480
327
|
await this.release();
|
|
481
328
|
}
|
|
482
329
|
}
|
|
483
|
-
/** The warning for a file that isn't saved, as the toolbar and the CLI show it */
|
|
484
|
-
export function missingFileText(file, folder) {
|
|
485
|
-
const where = folder === "" ? "the project folder" : `${folder}/`;
|
|
486
|
-
return file.reason === "too-big"
|
|
487
|
-
? `${file.name} is ${humanSize(file.size)}. Files over 50 MB aren't saved: add it to ${where} yourself, or use a generated file.`
|
|
488
|
-
: `${file.name} couldn't be read, so it isn't saved: add it to ${where} yourself, or use a generated file.`;
|
|
489
|
-
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Browser } from "../browser/browser.js";
|
|
2
|
+
import type Page from "../browser/page.js";
|
|
3
|
+
import type { SupportedBrowser } from "../types/browser.js";
|
|
4
|
+
interface BrowserChoice {
|
|
5
|
+
browser?: SupportedBrowser | undefined;
|
|
6
|
+
port?: number | undefined;
|
|
7
|
+
signal?: AbortSignal | undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The browser a recording uses: the one asked for, else the first of the project's (recording in two browsers at
|
|
11
|
+
* once makes no sense). A fresh build is started once first, so a missing browser or a slow first start stops
|
|
12
|
+
* the recording before the spec is touched.
|
|
13
|
+
*/
|
|
14
|
+
export declare function prepareRecordingBrowser(options: BrowserChoice): Promise<SupportedBrowser>;
|
|
15
|
+
/** Opens the browser of `prepareRecordingBrowser` */
|
|
16
|
+
export declare function launchRecordingBrowser(name: SupportedBrowser, options: BrowserChoice & {
|
|
17
|
+
headless: boolean;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
browser: Browser;
|
|
20
|
+
page: Page;
|
|
21
|
+
}>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Browser } from "../browser/browser.js";
|
|
2
|
+
import { ensureWarm } from "../browser/warm-up.js";
|
|
3
|
+
import { readConfig } from "../config/config.js";
|
|
4
|
+
import { browsersFor } from "../runner/browsers.js";
|
|
5
|
+
import { DEFAULT_PORT } from "../runner/ports.js";
|
|
6
|
+
/**
|
|
7
|
+
* The browser a recording uses: the one asked for, else the first of the project's (recording in two browsers at
|
|
8
|
+
* once makes no sense). A fresh build is started once first, so a missing browser or a slow first start stops
|
|
9
|
+
* the recording before the spec is touched.
|
|
10
|
+
*/
|
|
11
|
+
export async function prepareRecordingBrowser(options) {
|
|
12
|
+
const name = options.browser ??
|
|
13
|
+
browsersFor({ project: await readConfig("browsers") })[0];
|
|
14
|
+
await ensureWarm(name, { port: options.port ?? DEFAULT_PORT }, options.signal);
|
|
15
|
+
options.signal?.throwIfAborted();
|
|
16
|
+
return name;
|
|
17
|
+
}
|
|
18
|
+
/** Opens the browser of `prepareRecordingBrowser` */
|
|
19
|
+
export function launchRecordingBrowser(name, options) {
|
|
20
|
+
return Browser.launch(name, { port: options.port ?? DEFAULT_PORT, headless: options.headless }, options.signal);
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PreparedRun } from "../runner/prepare-run.js";
|
|
2
|
+
import type { SamuraiTestConfig } from "../types/config.js";
|
|
3
|
+
import type { RecorderEvent } from "./normalise.js";
|
|
4
|
+
import type { ControllerOptions } from "./toolbar/types.js";
|
|
5
|
+
/** What the toolbar shows of the project: its environment, variables, secrets and the test's screenshots */
|
|
6
|
+
export declare function toolbarOptions(run: Pick<PreparedRun, "settings" | "secrets">, source: string, dir: string, config: Pick<SamuraiTestConfig, "environments">, testIndex: number): Promise<ControllerOptions>;
|
|
7
|
+
/** The secrets a recording needs set: those its steps read, then those added from the panel, once each */
|
|
8
|
+
export declare function resultSecrets(events: readonly RecorderEvent[], added: Iterable<string>): string[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { isGitIgnored } from "../project/index.js";
|
|
2
|
+
import { parseSpec } from "../steps/parse.js";
|
|
3
|
+
/** Every `secrets.<NAME>` a parsed value contains, at any depth */
|
|
4
|
+
function secretNames(node, found) {
|
|
5
|
+
if (Array.isArray(node))
|
|
6
|
+
node.forEach((item) => secretNames(item, found));
|
|
7
|
+
else if (node !== null && typeof node === "object") {
|
|
8
|
+
const record = node;
|
|
9
|
+
if (record.kind === "secret" && typeof record.name === "string")
|
|
10
|
+
found.add(record.name);
|
|
11
|
+
Object.values(record).forEach((item) => secretNames(item, found));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** What the toolbar shows of the project: its environment, variables, secrets and the test's screenshots */
|
|
15
|
+
export async function toolbarOptions(run, source, dir, config, testIndex) {
|
|
16
|
+
const tests = parseSpec(source);
|
|
17
|
+
const names = new Set(run.secrets.keys());
|
|
18
|
+
secretNames(tests, names);
|
|
19
|
+
const secrets = {};
|
|
20
|
+
for (const name of names)
|
|
21
|
+
secrets[name] = run.secrets.get(name);
|
|
22
|
+
const { environment } = run.settings;
|
|
23
|
+
return {
|
|
24
|
+
environment,
|
|
25
|
+
canAddVariables: typeof config.environments === "object",
|
|
26
|
+
variables: run.settings.variables,
|
|
27
|
+
secrets,
|
|
28
|
+
secretsIgnored: await isGitIgnored(dir, `.env.${environment}`),
|
|
29
|
+
screenshots: tests[testIndex]?.steps.filter(({ step }) => step.kind === "screenshot")
|
|
30
|
+
.length ?? 0,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** The secrets a recording needs set: those its steps read, then those added from the panel, once each */
|
|
34
|
+
export function resultSecrets(events, added) {
|
|
35
|
+
return [
|
|
36
|
+
...new Set([
|
|
37
|
+
...events.flatMap((event) => event.op !== "remove" &&
|
|
38
|
+
event.step.kind === "fill" &&
|
|
39
|
+
event.step.value.kind === "secret"
|
|
40
|
+
? [event.step.value.name]
|
|
41
|
+
: []),
|
|
42
|
+
...added,
|
|
43
|
+
]),
|
|
44
|
+
];
|
|
45
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { Browser } from "../browser/browser.js";
|
|
1
|
+
import type { Browser } from "../browser/browser.js";
|
|
2
2
|
import type Page from "../browser/page.js";
|
|
3
|
-
import type { SupportedBrowser } from "../types/browser.js";
|
|
4
|
-
import type { PreparedRun } from "../runner/prepare-run.js";
|
|
5
3
|
import { type ProjectOptions } from "../runner/project.js";
|
|
6
|
-
import type {
|
|
7
|
-
import type { ControllerOptions } from "./toolbar/controller.js";
|
|
4
|
+
import type { SupportedBrowser } from "../types/browser.js";
|
|
8
5
|
import type { RecorderEvent } from "./normalise.js";
|
|
9
6
|
import { type MissingFile } from "./recorder.js";
|
|
7
|
+
export { pickTest } from "./spec-source.js";
|
|
8
|
+
export { resultSecrets, toolbarOptions } from "./session-secrets.js";
|
|
10
9
|
export interface RecordSpecOptions extends ProjectOptions {
|
|
11
10
|
/** The spec file, absolute or relative to the project folder. Created when `create` is given and it doesn't exist */
|
|
12
11
|
file: string;
|
|
@@ -23,7 +22,7 @@ export interface RecordSpecOptions extends ProjectOptions {
|
|
|
23
22
|
url?: string;
|
|
24
23
|
/** The browser to record in. @default the first of the config's `browsers`, else Firefox */
|
|
25
24
|
browser?: SupportedBrowser;
|
|
26
|
-
/** The browser's debugging port. @default 9223 */
|
|
25
|
+
/** The browser's debugging port. @default DEFAULT_PORT (9223) */
|
|
27
26
|
port?: number;
|
|
28
27
|
/** Run the browser without a window. A person can't use it then; for tests and machines without a display. @default the config's `use.headless`, else headless on CI and a window otherwise */
|
|
29
28
|
headless?: boolean;
|
|
@@ -67,14 +66,6 @@ export interface RecordResult {
|
|
|
67
66
|
/** The files folder was created by this recording */
|
|
68
67
|
firstFiles: boolean;
|
|
69
68
|
}
|
|
70
|
-
/** What the toolbar shows of the project: its environment, variables, secrets and the test's screenshots */
|
|
71
|
-
export declare function toolbarOptions(run: Pick<PreparedRun, "settings" | "secrets">, source: string, dir: string, config: Pick<SamuraiTestConfig, "environments">, testIndex: number): Promise<ControllerOptions>;
|
|
72
|
-
/** The secrets a recording needs set: those its steps read, then those added from the panel, once each */
|
|
73
|
-
export declare function resultSecrets(events: readonly RecorderEvent[], added: Iterable<string>): string[];
|
|
74
|
-
/** The index of the test `selector` names (see `RecordSpecOptions.test`) */
|
|
75
|
-
export declare function pickTest(tests: readonly {
|
|
76
|
-
name: string;
|
|
77
|
-
}[], selector: string | number | undefined): number;
|
|
78
69
|
/**
|
|
79
70
|
* Opens a browser window and records what the person does into a spec file, step by step, until `signal`
|
|
80
71
|
* aborts or the window is closed. Every step is written to the file as it happens, as a minimal edit.
|