@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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { ElementHandle } from "../../script/element-handle.js";
|
|
3
|
+
import { ToolbarController } from "./controller.js";
|
|
4
|
+
export const SECRET = "hunter2-very-secret";
|
|
5
|
+
export const NEW_SECRET = "n3w-s3cr3t-value";
|
|
6
|
+
const OPTIONS = {
|
|
7
|
+
environment: "dev",
|
|
8
|
+
canAddVariables: true,
|
|
9
|
+
variables: { testUser: "dev@example.com", retries: 3 },
|
|
10
|
+
secrets: { ADMIN_PASSWORD: SECRET, API_TOKEN: undefined },
|
|
11
|
+
secretsIgnored: true,
|
|
12
|
+
screenshots: 0,
|
|
13
|
+
};
|
|
14
|
+
export const TARGET = new ElementHandle("el-1");
|
|
15
|
+
export const CANDIDATES = [
|
|
16
|
+
{ method: "getByRole", role: "textbox", name: "Email" },
|
|
17
|
+
];
|
|
18
|
+
export const LOCATOR = { chain: [CANDIDATES[0]], fallbacks: [] };
|
|
19
|
+
export const COUNT_CALL = {
|
|
20
|
+
method: "getByRole",
|
|
21
|
+
role: "listitem",
|
|
22
|
+
};
|
|
23
|
+
export const INFO = {
|
|
24
|
+
ok: true,
|
|
25
|
+
label: `textbox "Email"`,
|
|
26
|
+
text: "",
|
|
27
|
+
value: "a@b.c",
|
|
28
|
+
attributes: [{ name: "id", value: "email" }],
|
|
29
|
+
first: { matcher: "toHaveValue", expected: "a@b.c" },
|
|
30
|
+
counts: [{ label: "listitem", count: 3 }],
|
|
31
|
+
};
|
|
32
|
+
export const lastOf = (items) => items[items.length - 1];
|
|
33
|
+
export const msg = (message) => ({ type: "toolbar", ...message });
|
|
34
|
+
export const insert = (id, kind, name) => msg({
|
|
35
|
+
action: "insert",
|
|
36
|
+
id,
|
|
37
|
+
target: TARGET,
|
|
38
|
+
candidates: CANDIDATES,
|
|
39
|
+
kind,
|
|
40
|
+
name,
|
|
41
|
+
});
|
|
42
|
+
export const add = (id, kind, name, value) => msg({
|
|
43
|
+
action: "add-insertable",
|
|
44
|
+
id,
|
|
45
|
+
target: TARGET,
|
|
46
|
+
candidates: CANDIDATES,
|
|
47
|
+
kind,
|
|
48
|
+
name,
|
|
49
|
+
value,
|
|
50
|
+
});
|
|
51
|
+
export const pick = (id) => msg({ action: "pick", id, target: TARGET, candidates: CANDIDATES });
|
|
52
|
+
/** A controller over fakes that record everything */
|
|
53
|
+
export function setup(options = {}, overrides = {}) {
|
|
54
|
+
const pushes = [];
|
|
55
|
+
const replies = [];
|
|
56
|
+
const observed = [];
|
|
57
|
+
const statuses = [];
|
|
58
|
+
const calls = [];
|
|
59
|
+
const page = { url: "http://localhost/a" };
|
|
60
|
+
let controller;
|
|
61
|
+
const deps = {
|
|
62
|
+
push: (state) => pushes.push(structuredClone(state)),
|
|
63
|
+
reply: (id, result) => replies.push({ id, result: structuredClone(result) }),
|
|
64
|
+
currentUrl: async () => page.url,
|
|
65
|
+
rank: async () => LOCATOR,
|
|
66
|
+
inspect: async () => ({
|
|
67
|
+
info: INFO,
|
|
68
|
+
counts: [COUNT_CALL],
|
|
69
|
+
}),
|
|
70
|
+
type: async (_locator, value) => {
|
|
71
|
+
calls.push(`type ${value}`);
|
|
72
|
+
},
|
|
73
|
+
mute: async () => {
|
|
74
|
+
calls.push("mute");
|
|
75
|
+
},
|
|
76
|
+
unmute: async () => {
|
|
77
|
+
calls.push("unmute");
|
|
78
|
+
},
|
|
79
|
+
addVariable: async (name, value) => {
|
|
80
|
+
calls.push(`addVariable ${name}=${value}`);
|
|
81
|
+
},
|
|
82
|
+
setSecret: async (name, value) => {
|
|
83
|
+
calls.push(`setSecret ${name}=${value}`);
|
|
84
|
+
},
|
|
85
|
+
secretAdded: (name) => {
|
|
86
|
+
calls.push(`secretAdded ${name}`);
|
|
87
|
+
},
|
|
88
|
+
// As the recorder does: an observation becomes a step, which is counted
|
|
89
|
+
observe: (observation) => {
|
|
90
|
+
observed.push(observation);
|
|
91
|
+
controller.stepAdded(observation.type === "screenshot"
|
|
92
|
+
? {
|
|
93
|
+
kind: "screenshot",
|
|
94
|
+
name: observation.name,
|
|
95
|
+
fullPage: observation.fullPage,
|
|
96
|
+
}
|
|
97
|
+
: { kind: "waitForNetworkIdle" });
|
|
98
|
+
},
|
|
99
|
+
stop: () => {
|
|
100
|
+
calls.push("stop");
|
|
101
|
+
},
|
|
102
|
+
onStatus: (status) => statuses.push(status),
|
|
103
|
+
...overrides,
|
|
104
|
+
};
|
|
105
|
+
controller = new ToolbarController(deps, { ...OPTIONS, ...options });
|
|
106
|
+
const last = () => lastOf(pushes);
|
|
107
|
+
const noSecretLeaks = () => {
|
|
108
|
+
const sent = JSON.stringify(pushes) + JSON.stringify(replies);
|
|
109
|
+
assert.ok(!sent.includes(SECRET), "a secret value reached the page");
|
|
110
|
+
assert.ok(!sent.includes(NEW_SECRET), "a new secret value reached the page");
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
controller,
|
|
114
|
+
pushes,
|
|
115
|
+
replies,
|
|
116
|
+
observed,
|
|
117
|
+
statuses,
|
|
118
|
+
calls,
|
|
119
|
+
page,
|
|
120
|
+
last,
|
|
121
|
+
noSecretLeaks,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** Deps whose `type` fails, as a field that can't be typed into does */
|
|
125
|
+
export const failingType = {
|
|
126
|
+
type: async () => {
|
|
127
|
+
throw new Error("not editable");
|
|
128
|
+
},
|
|
129
|
+
};
|
|
@@ -1,51 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
ok: true;
|
|
9
|
-
};
|
|
10
|
-
/** `counts[i]` is the locator `info.counts[i]` describes */
|
|
11
|
-
counts: LocatorCall[];
|
|
12
|
-
}
|
|
13
|
-
/** Everything the controller does to the browser and the project; the recorder implements these */
|
|
14
|
-
export interface ControllerDeps {
|
|
15
|
-
/** Sends the state to every loaded top-level document's toolbar */
|
|
16
|
-
push(state: ToolbarState): void;
|
|
17
|
-
reply(id: number, result: PickInfo | ActionReply): void;
|
|
18
|
-
currentUrl(): Promise<string>;
|
|
19
|
-
rank(target: ElementHandle, candidates: LocatorCall[]): Promise<LocatorSpec | undefined>;
|
|
20
|
-
inspect(target: ElementHandle, locator: LocatorSpec, candidates: LocatorCall[]): Promise<Inspection>;
|
|
21
|
-
/** Types into the field with capture muted */
|
|
22
|
-
type(locator: LocatorSpec, value: string): Promise<void>;
|
|
23
|
-
mute(): Promise<void>;
|
|
24
|
-
unmute(): Promise<void>;
|
|
25
|
-
addVariable(name: string, value: string): Promise<void>;
|
|
26
|
-
setSecret(name: string, value: string): Promise<void>;
|
|
27
|
-
/** A secret was written to the project (called before its value is typed) */
|
|
28
|
-
secretAdded(name: string): void;
|
|
29
|
-
observe(observation: Observation): void;
|
|
30
|
-
stop(): void;
|
|
31
|
-
onStatus(status: {
|
|
32
|
-
state: "recording" | "paused";
|
|
33
|
-
toolbar: boolean;
|
|
34
|
-
}): void;
|
|
35
|
-
}
|
|
36
|
-
export interface ControllerOptions {
|
|
37
|
-
environment: string;
|
|
38
|
-
canAddVariables: boolean;
|
|
39
|
-
variables: Record<string, string | number | boolean>;
|
|
40
|
-
/** Name → value; undefined when not set */
|
|
41
|
-
secrets: Record<string, string | undefined>;
|
|
42
|
-
secretsIgnored: boolean;
|
|
43
|
-
/** Screenshot steps already in the test, for the default name */
|
|
44
|
-
screenshots: number;
|
|
45
|
-
}
|
|
46
|
-
type Message = ToolbarMessage & {
|
|
47
|
-
target?: ElementHandle;
|
|
48
|
-
};
|
|
1
|
+
import type { Step } from "../../steps/model.js";
|
|
2
|
+
import type { ControllerDeps, ControllerOptions, Message } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The recorder's side of the toolbar: owns its state (pause, the rail, the environment's variables and
|
|
5
|
+
* secrets) and turns its messages into observations. Secret values stay here; the page only ever gets
|
|
6
|
+
* their names and whether they are set.
|
|
7
|
+
*/
|
|
49
8
|
export declare class ToolbarController {
|
|
50
9
|
private readonly deps;
|
|
51
10
|
private readonly options;
|
|
@@ -64,24 +23,17 @@ export declare class ToolbarController {
|
|
|
64
23
|
private savingName;
|
|
65
24
|
private warnings;
|
|
66
25
|
private warningId;
|
|
67
|
-
/** The picked element an `assert` with the same id refers to */
|
|
68
26
|
private pick;
|
|
69
|
-
|
|
70
|
-
private readonly
|
|
71
|
-
/** Ids `accept` let through whose `handle` hasn't started yet */
|
|
72
|
-
private readonly claimed;
|
|
73
|
-
/** Ids answered `{ ok: true }`: their step exists, a repeat is a double click */
|
|
74
|
-
private readonly done;
|
|
75
|
-
/** Bumped on every page load: work started for an older page is not answered */
|
|
76
|
-
private page;
|
|
77
|
-
/** Wake-ups for the work that is waiting on the current page: called, and forgotten, when the page changes */
|
|
78
|
-
private pageGone;
|
|
27
|
+
private readonly guard;
|
|
28
|
+
private readonly scope;
|
|
79
29
|
private toolbar;
|
|
80
30
|
/** A `ready` arrived since the last load: it counts for the next one */
|
|
81
31
|
private readySinceLoad;
|
|
82
32
|
private watchdog;
|
|
83
33
|
private disposed;
|
|
84
34
|
constructor(deps: ControllerDeps, options: ControllerOptions);
|
|
35
|
+
/** What the action handlers (assert, insert, add) use of this controller */
|
|
36
|
+
private readonly actions;
|
|
85
37
|
get paused(): boolean;
|
|
86
38
|
private state;
|
|
87
39
|
private push;
|
|
@@ -101,10 +53,6 @@ export declare class ToolbarController {
|
|
|
101
53
|
* and doesn't count for the coming load
|
|
102
54
|
*/
|
|
103
55
|
navigating(): void;
|
|
104
|
-
/** Lets what waits on the current document stop waiting: its commands may never be answered once it is gone */
|
|
105
|
-
private leavePage;
|
|
106
|
-
/** Runs `work`, but stops waiting for it when the page is left; resolves `gone` then */
|
|
107
|
-
private whileOnPage;
|
|
108
56
|
/** A step was emitted (any source): count it */
|
|
109
57
|
stepAdded(step: Step): void;
|
|
110
58
|
/** A recorded step was taken out again (a cancelled file picker's click) */
|
|
@@ -120,7 +68,6 @@ export declare class ToolbarController {
|
|
|
120
68
|
private pause;
|
|
121
69
|
private resume;
|
|
122
70
|
private answer;
|
|
123
|
-
/** Runs an action that expects a reply; a second one with the same id while the first runs is dropped */
|
|
124
71
|
/**
|
|
125
72
|
* Whether to process `message`; call it as the message arrives, before queueing it. An `assert`, `insert` or
|
|
126
73
|
* `add-insertable` whose id is in flight or was already answered `{ ok: true }` is a double click and is
|
|
@@ -128,19 +75,6 @@ export declare class ToolbarController {
|
|
|
128
75
|
*/
|
|
129
76
|
accept(message: Message): boolean;
|
|
130
77
|
/** Runs an action that expects a reply, unless it is a repeat (see `accept`) */
|
|
131
|
-
private
|
|
78
|
+
private replying;
|
|
132
79
|
private picked;
|
|
133
|
-
/**
|
|
134
|
-
* A field may hold an inserted secret: its value (and any text or attribute equal to a secret) is left out,
|
|
135
|
-
* and the first matcher follows the Alt+click rule without a value, so neither the page nor the spec gets it.
|
|
136
|
-
*/
|
|
137
|
-
private withoutSecrets;
|
|
138
|
-
private assert;
|
|
139
|
-
private insert;
|
|
140
|
-
private addInsertable;
|
|
141
|
-
/** Undefined when `work` succeeded, else the reply for its error */
|
|
142
|
-
private write;
|
|
143
|
-
private locate;
|
|
144
|
-
private typeIn;
|
|
145
80
|
}
|
|
146
|
-
export {};
|
|
@@ -1,29 +1,20 @@
|
|
|
1
1
|
import { cleanName } from "../../browser/evidence.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { ActionGuard } from "./action-guard.js";
|
|
3
|
+
import { assertAction } from "./actions/assert.js";
|
|
4
|
+
import { addInsertableAction, insertAction } from "./actions/insert.js";
|
|
5
|
+
import { withoutSecrets } from "./actions/pick.js";
|
|
6
|
+
import { fail, PAUSED, UNIDENTIFIABLE } from "./actions/reply.js";
|
|
7
|
+
import { LEFT, PageScope } from "./page-scope.js";
|
|
6
8
|
/** A wait for time is 0.1–60 s */
|
|
7
9
|
const WAIT_MIN_MS = 100;
|
|
8
10
|
const WAIT_MAX_MS = 60000;
|
|
9
11
|
/** How long a loaded page's toolbar has to say `ready` */
|
|
10
12
|
const READY_WITHIN = 2000;
|
|
11
|
-
const UNIDENTIFIABLE = "This element can't be identified uniquely";
|
|
12
|
-
const TYPE_FAILED = "Couldn't type into that field";
|
|
13
|
-
const NO_ENVIRONMENT = "This recording has no environment";
|
|
14
|
-
const PAUSED = "Nothing you do now is recorded.";
|
|
15
|
-
const fail = (error, field) => ({
|
|
16
|
-
ok: false,
|
|
17
|
-
error,
|
|
18
|
-
...(field && { field }),
|
|
19
|
-
});
|
|
20
13
|
/**
|
|
21
14
|
* The recorder's side of the toolbar: owns its state (pause, the rail, the environment's variables and
|
|
22
15
|
* secrets) and turns its messages into observations. Secret values stay here; the page only ever gets
|
|
23
16
|
* their names and whether they are set.
|
|
24
17
|
*/
|
|
25
|
-
/** What a wait on the page ends with when the page was left first */
|
|
26
|
-
const LEFT = Symbol("left the page");
|
|
27
18
|
export class ToolbarController {
|
|
28
19
|
constructor(deps, options) {
|
|
29
20
|
this.deps = deps;
|
|
@@ -35,16 +26,8 @@ export class ToolbarController {
|
|
|
35
26
|
this.toastId = 0;
|
|
36
27
|
this.warnings = [];
|
|
37
28
|
this.warningId = 0;
|
|
38
|
-
|
|
39
|
-
this.
|
|
40
|
-
/** Ids `accept` let through whose `handle` hasn't started yet */
|
|
41
|
-
this.claimed = new Set();
|
|
42
|
-
/** Ids answered `{ ok: true }`: their step exists, a repeat is a double click */
|
|
43
|
-
this.done = new Set();
|
|
44
|
-
/** Bumped on every page load: work started for an older page is not answered */
|
|
45
|
-
this.page = 0;
|
|
46
|
-
/** Wake-ups for the work that is waiting on the current page: called, and forgotten, when the page changes */
|
|
47
|
-
this.pageGone = new Set();
|
|
29
|
+
this.guard = new ActionGuard();
|
|
30
|
+
this.scope = new PageScope();
|
|
48
31
|
this.toolbar = true;
|
|
49
32
|
/** A `ready` arrived since the last load: it counts for the next one */
|
|
50
33
|
this.readySinceLoad = false;
|
|
@@ -55,6 +38,19 @@ export class ToolbarController {
|
|
|
55
38
|
String(value),
|
|
56
39
|
]));
|
|
57
40
|
this.secrets = new Map(Object.entries(options.secrets));
|
|
41
|
+
this.actions = {
|
|
42
|
+
deps,
|
|
43
|
+
options,
|
|
44
|
+
variables: this.variables,
|
|
45
|
+
secrets: this.secrets,
|
|
46
|
+
isPaused: () => this.paused,
|
|
47
|
+
pick: () => this.pick,
|
|
48
|
+
setPick: (pick) => {
|
|
49
|
+
this.pick = pick;
|
|
50
|
+
},
|
|
51
|
+
showToast: (text) => this.showToast(text),
|
|
52
|
+
push: () => this.push(),
|
|
53
|
+
};
|
|
58
54
|
}
|
|
59
55
|
get paused() {
|
|
60
56
|
return this.status === "paused";
|
|
@@ -140,11 +136,11 @@ export class ToolbarController {
|
|
|
140
136
|
case "pick":
|
|
141
137
|
return this.picked(message);
|
|
142
138
|
case "assert":
|
|
143
|
-
return this.
|
|
139
|
+
return this.replying(message, () => assertAction(this.actions, message));
|
|
144
140
|
case "insert":
|
|
145
|
-
return this.
|
|
141
|
+
return this.replying(message, () => insertAction(this.actions, message));
|
|
146
142
|
case "add-insertable":
|
|
147
|
-
return this.
|
|
143
|
+
return this.replying(message, () => addInsertableAction(this.actions, message));
|
|
148
144
|
case "wait":
|
|
149
145
|
if (this.paused)
|
|
150
146
|
return;
|
|
@@ -176,8 +172,7 @@ export class ToolbarController {
|
|
|
176
172
|
loaded() {
|
|
177
173
|
if (this.disposed)
|
|
178
174
|
return;
|
|
179
|
-
this.
|
|
180
|
-
this.leavePage();
|
|
175
|
+
this.scope.enter();
|
|
181
176
|
this.pick = undefined;
|
|
182
177
|
this.toast = undefined;
|
|
183
178
|
this.push();
|
|
@@ -202,28 +197,7 @@ export class ToolbarController {
|
|
|
202
197
|
*/
|
|
203
198
|
navigating() {
|
|
204
199
|
this.readySinceLoad = false;
|
|
205
|
-
this.
|
|
206
|
-
}
|
|
207
|
-
/** Lets what waits on the current document stop waiting: its commands may never be answered once it is gone */
|
|
208
|
-
leavePage() {
|
|
209
|
-
const waiting = [...this.pageGone];
|
|
210
|
-
this.pageGone.clear();
|
|
211
|
-
for (const wake of waiting)
|
|
212
|
-
wake();
|
|
213
|
-
}
|
|
214
|
-
/** Runs `work`, but stops waiting for it when the page is left; resolves `gone` then */
|
|
215
|
-
async whileOnPage(work, gone) {
|
|
216
|
-
let wake;
|
|
217
|
-
const left = new Promise((resolve) => {
|
|
218
|
-
wake = () => resolve(gone);
|
|
219
|
-
this.pageGone.add(wake);
|
|
220
|
-
});
|
|
221
|
-
try {
|
|
222
|
-
return await Promise.race([work(), left]);
|
|
223
|
-
}
|
|
224
|
-
finally {
|
|
225
|
-
this.pageGone.delete(wake);
|
|
226
|
-
}
|
|
200
|
+
this.scope.leave();
|
|
227
201
|
}
|
|
228
202
|
/** A step was emitted (any source): count it */
|
|
229
203
|
stepAdded(step) {
|
|
@@ -297,229 +271,43 @@ export class ToolbarController {
|
|
|
297
271
|
this.deps.observe({ type: "goto", url });
|
|
298
272
|
this.push();
|
|
299
273
|
}
|
|
300
|
-
/** Runs an action that expects a reply; a second one with the same id while the first runs is dropped */
|
|
301
274
|
/**
|
|
302
275
|
* Whether to process `message`; call it as the message arrives, before queueing it. An `assert`, `insert` or
|
|
303
276
|
* `add-insertable` whose id is in flight or was already answered `{ ok: true }` is a double click and is
|
|
304
277
|
* dropped (its first copy answers). A retry after `{ ok: false }` is accepted.
|
|
305
278
|
*/
|
|
306
279
|
accept(message) {
|
|
307
|
-
|
|
308
|
-
message.action !== "insert" &&
|
|
309
|
-
message.action !== "add-insertable")
|
|
310
|
-
return true;
|
|
311
|
-
const { id } = message;
|
|
312
|
-
if (this.inFlight.has(id) || this.done.has(id))
|
|
313
|
-
return false;
|
|
314
|
-
this.inFlight.add(id);
|
|
315
|
-
this.claimed.add(id);
|
|
316
|
-
return true;
|
|
280
|
+
return this.guard.accept(message);
|
|
317
281
|
}
|
|
318
282
|
/** Runs an action that expects a reply, unless it is a repeat (see `accept`) */
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (!this.claimed.has(id) && !this.accept(message))
|
|
322
|
-
return;
|
|
323
|
-
this.claimed.delete(id);
|
|
324
|
-
try {
|
|
325
|
-
const result = await work();
|
|
326
|
-
if (result.ok)
|
|
327
|
-
this.done.add(id);
|
|
328
|
-
this.reply(id, result);
|
|
329
|
-
}
|
|
330
|
-
finally {
|
|
331
|
-
this.inFlight.delete(id);
|
|
332
|
-
}
|
|
283
|
+
replying(message, work) {
|
|
284
|
+
return this.guard.run(message, work, (id, result) => this.reply(id, result));
|
|
333
285
|
}
|
|
334
286
|
async picked(message) {
|
|
335
|
-
const page = this.
|
|
287
|
+
const page = this.scope.current;
|
|
336
288
|
if (this.paused)
|
|
337
289
|
return this.reply(message.id, fail(PAUSED));
|
|
338
290
|
let found;
|
|
339
291
|
try {
|
|
340
292
|
// Chrome never answers a command into a document that is being replaced, so a pick of the old page must not
|
|
341
293
|
// keep the messages behind it waiting
|
|
342
|
-
found = await this.
|
|
294
|
+
found = await this.scope.whileOn(async () => {
|
|
343
295
|
const locator = message.target &&
|
|
344
296
|
(await this.deps.rank(message.target, message.candidates));
|
|
345
297
|
if (!locator)
|
|
346
298
|
return undefined;
|
|
347
299
|
const inspected = await this.deps.inspect(message.target, locator, message.candidates);
|
|
348
|
-
if (page === this.
|
|
300
|
+
if (page === this.scope.current)
|
|
349
301
|
this.pick = { id: message.id, locator, counts: inspected.counts };
|
|
350
302
|
return inspected;
|
|
351
|
-
}
|
|
303
|
+
});
|
|
352
304
|
}
|
|
353
305
|
catch {
|
|
354
306
|
found = undefined; // the element went away while it was inspected
|
|
355
307
|
}
|
|
356
308
|
// The page that asked is gone: its successor's toolbar isn't waiting for this
|
|
357
|
-
if (found === LEFT || page !== this.
|
|
309
|
+
if (found === LEFT || page !== this.scope.current)
|
|
358
310
|
return;
|
|
359
|
-
this.reply(message.id, found ?
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* A field may hold an inserted secret: its value (and any text or attribute equal to a secret) is left out,
|
|
363
|
-
* and the first matcher follows the Alt+click rule without a value, so neither the page nor the spec gets it.
|
|
364
|
-
*/
|
|
365
|
-
withoutSecrets(info) {
|
|
366
|
-
const values = [...this.secrets.values()].filter((value) => value !== undefined && value !== "");
|
|
367
|
-
const secret = (text) => text !== undefined && values.some((value) => text.includes(value));
|
|
368
|
-
if (!secret(info.value) &&
|
|
369
|
-
!secret(info.text) &&
|
|
370
|
-
!secret(info.first.expected) &&
|
|
371
|
-
!info.attributes.some((a) => secret(a.value)))
|
|
372
|
-
return info;
|
|
373
|
-
const text = secret(info.text) ? "" : info.text;
|
|
374
|
-
const first = expectationFor(text, undefined);
|
|
375
|
-
const { value: _value, ...rest } = info;
|
|
376
|
-
return {
|
|
377
|
-
...rest,
|
|
378
|
-
text,
|
|
379
|
-
attributes: info.attributes.filter((a) => !secret(a.value)),
|
|
380
|
-
first: {
|
|
381
|
-
matcher: first.matcher,
|
|
382
|
-
...("expected" in first && { expected: String(first.expected) }),
|
|
383
|
-
},
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
async assert(message) {
|
|
387
|
-
if (this.paused)
|
|
388
|
-
return fail(PAUSED);
|
|
389
|
-
const pick = this.pick;
|
|
390
|
-
if (!pick || pick.id !== message.id)
|
|
391
|
-
return fail("Pick the element again");
|
|
392
|
-
let locator = pick.locator;
|
|
393
|
-
let expectation;
|
|
394
|
-
const expected = String(message.expected ?? "");
|
|
395
|
-
switch (message.matcher) {
|
|
396
|
-
case "toBeVisible":
|
|
397
|
-
expectation = { matcher: "toBeVisible" };
|
|
398
|
-
break;
|
|
399
|
-
case "toHaveText":
|
|
400
|
-
case "toContainText":
|
|
401
|
-
case "toHaveValue":
|
|
402
|
-
expectation = { matcher: message.matcher, expected };
|
|
403
|
-
break;
|
|
404
|
-
case "toHaveAttribute":
|
|
405
|
-
if (!message.attribute)
|
|
406
|
-
return fail("Choose an attribute");
|
|
407
|
-
expectation = {
|
|
408
|
-
matcher: "toHaveAttribute",
|
|
409
|
-
name: message.attribute,
|
|
410
|
-
expected,
|
|
411
|
-
};
|
|
412
|
-
break;
|
|
413
|
-
case "toHaveCount": {
|
|
414
|
-
const call = pick.counts[message.countIndex ?? 0];
|
|
415
|
-
const count = Number(expected);
|
|
416
|
-
if (!call)
|
|
417
|
-
return fail("Choose what to count");
|
|
418
|
-
if (expected.trim() === "" || !Number.isInteger(count) || count < 0)
|
|
419
|
-
return fail("A count is a whole number");
|
|
420
|
-
locator = { chain: [call], fallbacks: [] };
|
|
421
|
-
expectation = { matcher: "toHaveCount", expected: count };
|
|
422
|
-
break;
|
|
423
|
-
}
|
|
424
|
-
default:
|
|
425
|
-
return fail("Choose a matcher");
|
|
426
|
-
}
|
|
427
|
-
this.pick = undefined;
|
|
428
|
-
this.deps.observe({
|
|
429
|
-
type: "expect",
|
|
430
|
-
locator,
|
|
431
|
-
not: message.not === true,
|
|
432
|
-
expectation,
|
|
433
|
-
});
|
|
434
|
-
return { ok: true };
|
|
435
|
-
}
|
|
436
|
-
async insert(message) {
|
|
437
|
-
if (this.paused)
|
|
438
|
-
return fail(PAUSED);
|
|
439
|
-
const { kind, name } = message;
|
|
440
|
-
const value = kind === "secret" ? this.secrets.get(name) : this.variables.get(name);
|
|
441
|
-
if (value === undefined)
|
|
442
|
-
return fail(kind === "secret"
|
|
443
|
-
? `Set a value for ${name} first`
|
|
444
|
-
: `There is no variable "${name}"`);
|
|
445
|
-
const locator = await this.locate(message);
|
|
446
|
-
if (!locator)
|
|
447
|
-
return fail(UNIDENTIFIABLE);
|
|
448
|
-
return this.typeIn(locator, kind, name, value);
|
|
449
|
-
}
|
|
450
|
-
async addInsertable(message) {
|
|
451
|
-
if (this.paused)
|
|
452
|
-
return fail(PAUSED);
|
|
453
|
-
const { kind, name } = message;
|
|
454
|
-
const value = String(message.value ?? "");
|
|
455
|
-
if (kind === "env" && !this.options.canAddVariables)
|
|
456
|
-
return fail(NO_ENVIRONMENT);
|
|
457
|
-
const known = kind === "env"
|
|
458
|
-
? this.variables.has(name)
|
|
459
|
-
: this.secrets.get(name) !== undefined;
|
|
460
|
-
if (known)
|
|
461
|
-
return fail(`"${name}" already exists`, "name");
|
|
462
|
-
// Found before anything is written: an element that can't be filled leaves the project as it was
|
|
463
|
-
const locator = await this.locate(message);
|
|
464
|
-
if (!locator)
|
|
465
|
-
return fail(UNIDENTIFIABLE);
|
|
466
|
-
const written = kind === "env"
|
|
467
|
-
? await this.write(() => this.deps.addVariable(name, value), (err) => err instanceof ConfigEditError
|
|
468
|
-
? fail(`samurai.config.ts can't be changed automatically. Add ${name} to environments.${this.options.environment}.variables yourself.`)
|
|
469
|
-
: err instanceof ExistsError || !VARIABLE_NAME.test(name)
|
|
470
|
-
? fail(errorText(err), "name")
|
|
471
|
-
: fail(errorText(err)))
|
|
472
|
-
: await this.write(() => this.deps.setSecret(name, value), (err) =>
|
|
473
|
-
// An error never echoes the value
|
|
474
|
-
SECRET_NAME.test(name)
|
|
475
|
-
? fail(scrub(errorText(err), value))
|
|
476
|
-
: fail(scrub(errorText(err), value), "name"));
|
|
477
|
-
if (written)
|
|
478
|
-
return written;
|
|
479
|
-
if (kind === "env")
|
|
480
|
-
this.variables.set(name, value);
|
|
481
|
-
else {
|
|
482
|
-
this.secrets.set(name, value);
|
|
483
|
-
this.deps.secretAdded(name);
|
|
484
|
-
}
|
|
485
|
-
this.push();
|
|
486
|
-
return this.typeIn(locator, kind, name, value, true);
|
|
487
|
-
}
|
|
488
|
-
/** Undefined when `work` succeeded, else the reply for its error */
|
|
489
|
-
async write(work, replyFor) {
|
|
490
|
-
try {
|
|
491
|
-
await work();
|
|
492
|
-
return undefined;
|
|
493
|
-
}
|
|
494
|
-
catch (err) {
|
|
495
|
-
return replyFor(err);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
async locate(message) {
|
|
499
|
-
if (!message.target)
|
|
500
|
-
return undefined;
|
|
501
|
-
try {
|
|
502
|
-
return await this.deps.rank(message.target, message.candidates);
|
|
503
|
-
}
|
|
504
|
-
catch {
|
|
505
|
-
return undefined; // the element went away
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
async typeIn(locator, kind, name, value, saved = false) {
|
|
509
|
-
try {
|
|
510
|
-
await this.deps.type(locator, value);
|
|
511
|
-
}
|
|
512
|
-
catch {
|
|
513
|
-
// Typing clicks the field, which closes the panel the reply is for: the toast is what the tester sees
|
|
514
|
-
this.showToast(saved
|
|
515
|
-
? `Saved ${name}, but couldn't type into that field`
|
|
516
|
-
: TYPE_FAILED);
|
|
517
|
-
this.push();
|
|
518
|
-
return fail(TYPE_FAILED);
|
|
519
|
-
}
|
|
520
|
-
this.deps.observe({ type: "insert", locator, value: { kind, name } });
|
|
521
|
-
return { ok: true };
|
|
311
|
+
this.reply(message.id, found ? withoutSecrets(found.info, this.secrets) : fail(UNIDENTIFIABLE));
|
|
522
312
|
}
|
|
523
313
|
}
|
|
524
|
-
const errorText = (err) => err instanceof Error ? err.message : String(err);
|
|
525
|
-
const scrub = (text, secret) => secret === "" ? text : text.split(secret).join("••••••");
|
|
@@ -2,9 +2,7 @@ import type Page from "../../browser/page.js";
|
|
|
2
2
|
import type { ElementHandle } from "../../script/element-handle.js";
|
|
3
3
|
import type { LocatorCall, LocatorSpec } from "../../steps/model.js";
|
|
4
4
|
import type { BiDiConnector } from "../../transport/bidi-connection.js";
|
|
5
|
-
import type { Inspection } from "./
|
|
6
|
-
/** A locator call in short, as the Assert panel shows it: `heading "Sign in"`, `label "Email"`, `li.item` */
|
|
7
|
-
export declare function describeCall(call: LocatorCall): string;
|
|
5
|
+
import type { Inspection } from "./types.js";
|
|
8
6
|
/**
|
|
9
7
|
* What the Assert panel needs for a picked element: its short label, text, value, attributes, the first
|
|
10
8
|
* matcher (today's Alt+click rule) and up to three locators that match more than one element, with counts.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { callFunction } from "../../script/call-function.js";
|
|
2
2
|
import { locatorFromSpec } from "../../steps/build.js";
|
|
3
3
|
import { CANDIDATES_JS } from "../candidates.js";
|
|
4
|
-
import { expectationFor } from "../
|
|
4
|
+
import { expectationFor } from "../expectation.js";
|
|
5
5
|
import { TOOLBAR_SANDBOX } from "./protocol.js";
|
|
6
6
|
/** Most Count choices the Assert panel offers */
|
|
7
7
|
const MAX_COUNTS = 3;
|
|
@@ -27,7 +27,7 @@ const INSPECT_SOURCE = String.raw `(el) => {
|
|
|
27
27
|
};
|
|
28
28
|
}`;
|
|
29
29
|
/** A locator call in short, as the Assert panel shows it: `heading "Sign in"`, `label "Email"`, `li.item` */
|
|
30
|
-
|
|
30
|
+
function describeCall(call) {
|
|
31
31
|
switch (call.method) {
|
|
32
32
|
case "getByRole":
|
|
33
33
|
return call.name === undefined
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** What a wait on the page ends with when the page was left first */
|
|
2
|
+
export declare const LEFT: unique symbol;
|
|
3
|
+
/**
|
|
4
|
+
* Tracks which document the toolbar is working for. Work started for an older document must not be answered, and
|
|
5
|
+
* Chrome never answers a command into a document that is being replaced, so such work stops being waited for.
|
|
6
|
+
*/
|
|
7
|
+
export declare class PageScope {
|
|
8
|
+
private page;
|
|
9
|
+
/** Wake-ups for the work waiting on the current page: called, and forgotten, when the page changes */
|
|
10
|
+
private readonly waiting;
|
|
11
|
+
/** Bumped on every page load */
|
|
12
|
+
get current(): number;
|
|
13
|
+
/** A new document loaded */
|
|
14
|
+
enter(): void;
|
|
15
|
+
/** Lets what waits on the current document stop waiting */
|
|
16
|
+
leave(): void;
|
|
17
|
+
/** Runs `work`, but stops waiting for it when the page is left; resolves `LEFT` then */
|
|
18
|
+
whileOn<T>(work: () => Promise<T>): Promise<T | typeof LEFT>;
|
|
19
|
+
}
|