@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
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** The recorder's side of the hook: update, reply, rebind, dispose. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const RECORDER_SIDE_JS = String.raw ` // ---- The recorder's side ----
|
|
3
|
+
const showToast = (text) => {
|
|
4
|
+
toast.textContent = text;
|
|
5
|
+
toast.hidden = false;
|
|
6
|
+
toast.classList.remove("gone");
|
|
7
|
+
clearTimeout(toastTimer);
|
|
8
|
+
toastTimer = setTimeout(() => {
|
|
9
|
+
toast.classList.add("gone");
|
|
10
|
+
toastTimer = setTimeout(() => { toast.hidden = true; }, 300);
|
|
11
|
+
}, 2500);
|
|
12
|
+
};
|
|
13
|
+
const update = (next) => {
|
|
14
|
+
state = Object.assign({}, DEFAULTS, next);
|
|
15
|
+
const dropped = ui.dropped;
|
|
16
|
+
if (dropped && state.position && Math.round(state.position.x) === dropped.x && Math.round(state.position.y) === dropped.y) ui.dropped = null;
|
|
17
|
+
if (ui.closeOnUpdate) closePanel();
|
|
18
|
+
if (!usable()) {
|
|
19
|
+
stopPicking();
|
|
20
|
+
closePanel();
|
|
21
|
+
}
|
|
22
|
+
if (state.toast && state.toast.id !== ui.toastId) {
|
|
23
|
+
ui.toastId = state.toast.id;
|
|
24
|
+
showToast(state.toast.text);
|
|
25
|
+
}
|
|
26
|
+
attach();
|
|
27
|
+
render();
|
|
28
|
+
};
|
|
29
|
+
const reply = (id, result) => {
|
|
30
|
+
if (!ui.pending || id !== ui.pending || !result) return;
|
|
31
|
+
ui.busy = false;
|
|
32
|
+
const ok = result.ok === true;
|
|
33
|
+
if (ui.panel === "assert" && ui.assert && !ui.assert.info) {
|
|
34
|
+
const a = ui.assert;
|
|
35
|
+
a.info = result;
|
|
36
|
+
if (ok) {
|
|
37
|
+
a.matcher = result.first.matcher;
|
|
38
|
+
a.attribute = result.attributes[0] ? result.attributes[0].name : null;
|
|
39
|
+
a.countIndex = 0;
|
|
40
|
+
a.expected = prefill(a, a.matcher);
|
|
41
|
+
}
|
|
42
|
+
} else if (ok) {
|
|
43
|
+
closePanel();
|
|
44
|
+
} else if (ui.panel === "assert" && ui.assert) {
|
|
45
|
+
ui.assert.error = result.error;
|
|
46
|
+
} else if (ui.panel === "insert") {
|
|
47
|
+
ui.insert.error = result.error;
|
|
48
|
+
} else if (ui.panel === "new") {
|
|
49
|
+
if (result.field === "name") ui.form.nameError = result.error;
|
|
50
|
+
else ui.form.error = result.error;
|
|
51
|
+
}
|
|
52
|
+
render();
|
|
53
|
+
};
|
|
54
|
+
`;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CANDIDATES_JS } from "../../candidates.js";
|
|
2
|
+
import { TOOLBAR_KEY } from "../protocol.js";
|
|
3
|
+
import { TOOLBAR_CSS } from "../styles.js";
|
|
4
|
+
/** Preamble: single-run guard, shared candidate code, styles, defaults and the toolbar state variables. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
5
|
+
export const SETUP_JS = String.raw ` if (window !== window.top) return;
|
|
6
|
+
const KEY = Symbol.for("${TOOLBAR_KEY}");
|
|
7
|
+
if (globalThis[KEY]) {
|
|
8
|
+
globalThis[KEY].rebind(send);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
${CANDIDATES_JS}
|
|
13
|
+
|
|
14
|
+
const CSS = ${JSON.stringify(TOOLBAR_CSS)};
|
|
15
|
+
const DEFAULTS = {
|
|
16
|
+
status: "recording", collapsed: false, position: null, steps: 0, environment: "", canAddVariables: false,
|
|
17
|
+
variables: [], secrets: [], secretsIgnored: true, resumeQuestion: false, nextScreenshot: "screenshot-1",
|
|
18
|
+
warnings: [],
|
|
19
|
+
};
|
|
20
|
+
const MATCHERS = [
|
|
21
|
+
["toBeVisible", "Visible"], ["toHaveText", "Has text"], ["toContainText", "Contains text"],
|
|
22
|
+
["toHaveValue", "Has value"], ["toHaveAttribute", "Has attribute"], ["toHaveCount", "Count"],
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
let channel = send;
|
|
26
|
+
let state = Object.assign({}, DEFAULTS);
|
|
27
|
+
let nextId = Date.now();
|
|
28
|
+
let railBox = { x: 0, y: 0, w: 0, h: 0 };
|
|
29
|
+
let toastTimer = 0;
|
|
30
|
+
// Local UI state; everything about steps lives in the recorder
|
|
31
|
+
const ui = {
|
|
32
|
+
panel: null, picking: false, busy: false, closeOnUpdate: false, pending: 0, focus: null,
|
|
33
|
+
field: null, assert: null, insert: null, form: null,
|
|
34
|
+
wait: { kind: "idle", seconds: "2" }, shot: { name: "", fullPage: false },
|
|
35
|
+
drag: null, dropped: null, toastId: null,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const post = (message) => {
|
|
39
|
+
try { channel(Object.assign({ type: "toolbar" }, message)); } catch (err) { /* the channel is gone */ }
|
|
40
|
+
};
|
|
41
|
+
// Listeners on the page's window and document, so dispose() can take them all off again
|
|
42
|
+
const listening = [];
|
|
43
|
+
const listen = (target, type, handler, capture) => {
|
|
44
|
+
target.addEventListener(type, handler, capture === true);
|
|
45
|
+
listening.push([target, type, handler, capture === true]);
|
|
46
|
+
};
|
|
47
|
+
const paused = () => state.status === "paused";
|
|
48
|
+
// Whole pixels, so a rail at the far edge does not overhang it by a rounding error
|
|
49
|
+
const clamp = (value, max) => Math.max(0, Math.min(value, Math.floor(max)));
|
|
50
|
+
`;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** Views: the Assert, Insert, New, Wait and Screenshot panels. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const VIEWS_PANELS_JS = String.raw ` const unavailable = (info, matcher) =>
|
|
3
|
+
(matcher === "toHaveValue" && info.value === undefined) ||
|
|
4
|
+
(matcher === "toHaveAttribute" && info.attributes.length === 0) ||
|
|
5
|
+
(matcher === "toHaveCount" && info.counts.length === 0);
|
|
6
|
+
const prefill = (a, matcher) => {
|
|
7
|
+
const info = a.info;
|
|
8
|
+
if (matcher === "toHaveAttribute") {
|
|
9
|
+
const found = info.attributes.find((at) => at.name === a.attribute) || info.attributes[0];
|
|
10
|
+
return found ? found.value : "";
|
|
11
|
+
}
|
|
12
|
+
if (matcher === "toHaveCount") {
|
|
13
|
+
const found = info.counts[a.countIndex];
|
|
14
|
+
return found ? String(found.count) : "";
|
|
15
|
+
}
|
|
16
|
+
if (matcher === info.first.matcher && info.first.expected !== undefined) return info.first.expected;
|
|
17
|
+
if (matcher === "toHaveText" || matcher === "toContainText") return info.text;
|
|
18
|
+
if (matcher === "toHaveValue") return info.value || "";
|
|
19
|
+
return "";
|
|
20
|
+
};
|
|
21
|
+
const assertValue = (a) => {
|
|
22
|
+
const info = a.info;
|
|
23
|
+
const expected = (type) => h("input", { type, class: "in", "data-field": "expected", value: a.expected, "data-enter": "assert-add", autocomplete: "off" });
|
|
24
|
+
if (a.matcher === "toHaveText" || a.matcher === "toContainText" || a.matcher === "toHaveValue") return h("div", { class: "val" }, expected("text"));
|
|
25
|
+
if (a.matcher === "toHaveAttribute") {
|
|
26
|
+
return h("div", { class: "val" },
|
|
27
|
+
h("select", { class: "in", "data-field": "attribute" }, info.attributes.map((at) => {
|
|
28
|
+
const shown = at.name + '="' + at.value + '"';
|
|
29
|
+
return h("option", { value: at.name, selected: at.name === a.attribute }, shown.length > 44 ? shown.slice(0, 43) + "…" : shown);
|
|
30
|
+
})),
|
|
31
|
+
expected("text"));
|
|
32
|
+
}
|
|
33
|
+
if (a.matcher === "toHaveCount") {
|
|
34
|
+
return h("div", { class: "val" },
|
|
35
|
+
h("select", { class: "in", "data-field": "countIndex" }, info.counts.map((c, i) =>
|
|
36
|
+
h("option", { value: String(i), selected: i === a.countIndex }, c.label + " (" + c.count + ")"))),
|
|
37
|
+
expected("number"));
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
const assertPanel = () => {
|
|
42
|
+
const a = ui.assert;
|
|
43
|
+
const info = a.info;
|
|
44
|
+
const head = h("div", { class: "h" }, "Assert", info && info.ok && h("code", { title: info.label }, info.label));
|
|
45
|
+
if (!info) return [head, h("div", { class: "msg" }, "…")];
|
|
46
|
+
if (!info.ok) return [head, h("div", { class: "err" }, info.error)];
|
|
47
|
+
return [
|
|
48
|
+
head,
|
|
49
|
+
h("div", { class: "opts" }, MATCHERS.map(([matcher, label]) =>
|
|
50
|
+
h("button", { class: a.matcher === matcher ? "o sel" : "o", "data-action": "matcher", "data-matcher": matcher, disabled: unavailable(info, matcher) }, label))),
|
|
51
|
+
assertValue(a),
|
|
52
|
+
a.error && h("div", { class: "err" }, a.error),
|
|
53
|
+
h("div", { class: "row" },
|
|
54
|
+
h("label", { class: "neg" }, h("input", { type: "checkbox", "data-action": "not", checked: a.not }), "Not"),
|
|
55
|
+
h("button", { class: "add", "data-action": "assert-add", disabled: ui.busy }, "Add")),
|
|
56
|
+
];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const matches = () => {
|
|
60
|
+
const filter = ui.insert.filter.trim().toLowerCase();
|
|
61
|
+
const hit = (item) => !filter || item.name.toLowerCase().indexOf(filter) >= 0;
|
|
62
|
+
return {
|
|
63
|
+
filter,
|
|
64
|
+
variables: state.canAddVariables ? state.variables.filter(hit) : [],
|
|
65
|
+
secrets: state.secrets.filter(hit),
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
const insertPanel = () => {
|
|
69
|
+
const ins = ui.insert;
|
|
70
|
+
if (!ins.field) return [h("div", { class: "h" }, "Insert"), h("div", { class: "msg" }, "Click into a field first")];
|
|
71
|
+
const found = matches();
|
|
72
|
+
const none = found.filter && found.variables.length === 0 && found.secrets.length === 0;
|
|
73
|
+
const item = (kind, name, value) =>
|
|
74
|
+
h("button", { class: "it", "data-action": "insert-item", "data-kind": kind, "data-name": name, disabled: ui.busy }, h("code", null, name), value);
|
|
75
|
+
return [
|
|
76
|
+
h("div", { class: "h stack" }, 'Insert into "' + fieldLabel(ins.field) + '"', h("small", null, "Environment: " + state.environment)),
|
|
77
|
+
h("input", { type: "text", class: "in search", "data-field": "filter", placeholder: "Filter…", value: ins.filter, "data-enter": "insert-first", autocomplete: "off", spellcheck: "false" }),
|
|
78
|
+
state.canAddVariables && [
|
|
79
|
+
h("div", { class: "grp" }, "Variables"),
|
|
80
|
+
found.variables.length
|
|
81
|
+
? found.variables.map((v) => item("env", v.name, h("span", { class: "v", title: v.value }, v.value)))
|
|
82
|
+
: h("div", { class: "empty" }, found.filter ? "No variable matches." : "No variables yet."),
|
|
83
|
+
],
|
|
84
|
+
h("div", { class: "grp" }, "Secrets"),
|
|
85
|
+
found.secrets.length
|
|
86
|
+
? found.secrets.map((s) => item("secret", s.name, s.set ? h("span", { class: "v" }, "••••••") : h("span", { class: "warn" }, "not set")))
|
|
87
|
+
: h("div", { class: "empty" }, found.filter ? "No secret matches." : "No secrets yet."),
|
|
88
|
+
ins.error && h("div", { class: "err" }, ins.error),
|
|
89
|
+
h("button", { class: "act", "data-action": "insert-new", "data-kind": "env" }, "+ Add variable" + (none ? ' "' + variableName(ins.filter) + '"' : "")),
|
|
90
|
+
h("button", { class: "act", "data-action": "insert-new", "data-kind": "secret" }, "+ Add secret" + (none ? ' "' + snake(ins.filter) + '"' : "")),
|
|
91
|
+
];
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const newHint = (secret, env, name) => {
|
|
95
|
+
if (!secret) return ["Saved to ", h("code", null, "environments." + env + ".variables"), " in samurai.config.ts. Other environments don't get it — add it there before running against them."];
|
|
96
|
+
if (!state.secretsIgnored) return h("span", { class: "warn" }, "Add .env." + env + " to .gitignore before committing.");
|
|
97
|
+
return ["Saved as ", h("code", null, "SAMURAI_SECRET_" + (name.trim() || "<NAME>")), " in ", h("code", null, ".env." + env), " (git-ignored)."];
|
|
98
|
+
};
|
|
99
|
+
const newPanel = () => {
|
|
100
|
+
const f = ui.form;
|
|
101
|
+
const secret = f.kind === "secret";
|
|
102
|
+
const blocked = !secret && !state.canAddVariables;
|
|
103
|
+
const env = state.environment;
|
|
104
|
+
return [
|
|
105
|
+
h("div", { class: "h stack" }, f.setValue ? "Set value" : secret ? "New secret" : "New variable", h("small", null, 'For "' + fieldLabel(ui.insert.field) + '"')),
|
|
106
|
+
h("div", { class: "seg" },
|
|
107
|
+
h("button", { class: secret ? "" : "sel", "data-action": "new-kind", "data-kind": "env" }, "Variable"),
|
|
108
|
+
h("button", { class: secret ? "sel" : "", "data-action": "new-kind", "data-kind": "secret" }, "Secret")),
|
|
109
|
+
blocked
|
|
110
|
+
? h("div", { class: "msg" }, "This recording has no environment")
|
|
111
|
+
: [
|
|
112
|
+
h("div", { class: "lbl" }, "Name"),
|
|
113
|
+
h("div", { class: "f" }, h("input", { type: "text", class: "in code", "data-field": "name", value: f.name, "data-enter": "new-save", autocomplete: "off", spellcheck: "false" })),
|
|
114
|
+
f.nameError && h("div", { class: "field-error" }, f.nameError),
|
|
115
|
+
h("div", { class: "lbl" }, "Value in " + env),
|
|
116
|
+
h("div", { class: "f" }, h("input", { type: secret ? "password" : "text", class: "in", "data-field": "value", value: f.value, "data-enter": "new-save", autocomplete: secret ? "new-password" : "off" })),
|
|
117
|
+
h("div", { class: "hint" }, newHint(secret, env, f.name)),
|
|
118
|
+
f.error && h("div", { class: "err" }, f.error),
|
|
119
|
+
],
|
|
120
|
+
h("div", { class: "foot plain" },
|
|
121
|
+
h("button", { class: "cancel", "data-action": "new-cancel" }, "Cancel"),
|
|
122
|
+
!blocked && h("button", { class: "add", "data-action": "new-save", disabled: ui.busy || !f.name.trim() }, "Save and insert")),
|
|
123
|
+
];
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const secondsValid = () => {
|
|
127
|
+
const seconds = Number(ui.wait.seconds);
|
|
128
|
+
return ui.wait.seconds.trim() !== "" && isFinite(seconds) && seconds >= 0.1 && seconds <= 60;
|
|
129
|
+
};
|
|
130
|
+
const radio = (field, value, checked, ...label) =>
|
|
131
|
+
h("label", { class: "radio" }, h("input", { type: "radio", name: field, "data-field": field, value, checked }), label);
|
|
132
|
+
const waitPanel = () => [
|
|
133
|
+
h("div", { class: "h" }, "Wait"),
|
|
134
|
+
radio("waitKind", "idle", ui.wait.kind === "idle", "Until the page settles"),
|
|
135
|
+
h("div", { class: "sub" }, "No requests for 500 ms"),
|
|
136
|
+
radio("waitKind", "time", ui.wait.kind === "time", "For ",
|
|
137
|
+
h("input", { type: "number", class: "num", "data-field": "seconds", min: "0.1", max: "60", step: "0.1", value: ui.wait.seconds, "data-enter": "wait-add" }),
|
|
138
|
+
" seconds"),
|
|
139
|
+
h("div", { class: "foot" }, h("button", { class: "add", "data-action": "wait-add", disabled: ui.busy || (ui.wait.kind === "time" && !secondsValid()) }, "Add")),
|
|
140
|
+
];
|
|
141
|
+
const screenshotPanel = () => [
|
|
142
|
+
h("div", { class: "h" }, "Screenshot"),
|
|
143
|
+
h("div", { class: "grp" }, "Name"),
|
|
144
|
+
h("div", { class: "f" }, h("input", { type: "text", class: "in", "data-field": "shotName", value: ui.shot.name, "data-enter": "screenshot-add", autocomplete: "off", spellcheck: "false" })),
|
|
145
|
+
radio("fullPage", "false", !ui.shot.fullPage, "Visible part"),
|
|
146
|
+
radio("fullPage", "true", ui.shot.fullPage, "Whole page"),
|
|
147
|
+
h("div", { class: "foot" }, h("button", { class: "add", "data-action": "screenshot-add", disabled: ui.busy }, "Add")),
|
|
148
|
+
];
|
|
149
|
+
const PANELS = { assert: assertPanel, insert: insertPanel, new: newPanel, wait: waitPanel, screenshot: screenshotPanel };
|
|
150
|
+
`;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Views: the chip and the rail. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const VIEWS_RAIL_JS = String.raw ` // ---- Views ----
|
|
3
|
+
const toolOn = (tool) =>
|
|
4
|
+
tool === "assert" ? ui.picking || ui.panel === "assert" : tool === "insert" ? ui.panel === "insert" || ui.panel === "new" : ui.panel === tool;
|
|
5
|
+
|
|
6
|
+
const chipView = () =>
|
|
7
|
+
h("div", { class: "chip" },
|
|
8
|
+
h("span", { "data-grip": true, title: "Drag to move" }, "⋮⋮"),
|
|
9
|
+
h("span", { class: paused() ? "dot pause" : "dot" }),
|
|
10
|
+
(paused() ? "Paused" : "Rec") + " · " + state.steps + (state.steps === 1 ? " step" : " steps"),
|
|
11
|
+
h("button", { class: "chev", "data-action": "expand", title: "Expand" }, "⌄"));
|
|
12
|
+
|
|
13
|
+
const railView = () => {
|
|
14
|
+
const tool = (action, icon, label) =>
|
|
15
|
+
h("button", { class: toolOn(action) ? "b on" : "b", "data-action": action, disabled: paused() }, ico(icon), label);
|
|
16
|
+
return h("div", { class: paused() || state.resumeQuestion ? "rail wide" : "rail" },
|
|
17
|
+
h("div", { class: "top" },
|
|
18
|
+
h("span", { "data-grip": true, title: "Drag to move" }, "⋮⋮⋮"),
|
|
19
|
+
h("button", { class: "chev", "data-action": "collapse", title: "Collapse" }, "⌃")),
|
|
20
|
+
h("div", { class: "st" }, h("span", { class: paused() ? "dot pause" : "dot" }), paused() ? "Paused" : "Recording"),
|
|
21
|
+
paused() && h("div", { class: "note" }, "Nothing you do now is recorded."),
|
|
22
|
+
state.resumeQuestion && [
|
|
23
|
+
h("div", { class: "note" }, "Add a step that opens this page?"),
|
|
24
|
+
h("div", { class: "ask" },
|
|
25
|
+
h("button", { class: "yes", "data-action": "resume-yes" }, "Yes"),
|
|
26
|
+
h("button", { "data-action": "resume-no" }, "No")),
|
|
27
|
+
],
|
|
28
|
+
h("div", { class: "sep" }),
|
|
29
|
+
paused()
|
|
30
|
+
? h("button", { class: "b on", "data-action": "resume" }, ico("▶"), "Resume")
|
|
31
|
+
: h("button", { class: "b", "data-action": "pause" }, ico("❚❚"), "Pause"),
|
|
32
|
+
tool("assert", "✓", "Assert"),
|
|
33
|
+
ui.picking && h("div", { class: "note" }, "Click an element · Esc cancels"),
|
|
34
|
+
tool("insert", "{x}", "Insert"),
|
|
35
|
+
tool("wait", "⏱", "Wait"),
|
|
36
|
+
tool("screenshot", "◉", "Screenshot"),
|
|
37
|
+
h("div", { class: "sep" }),
|
|
38
|
+
h("button", { class: "b stop", "data-action": "stop" }, ico("■"), "Stop"));
|
|
39
|
+
};
|
|
40
|
+
`;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Views: warnings, notices and the render pass. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const VIEWS_RENDER_JS = String.raw ` // Notice nodes live as long as their warning: rebuilding them would make screen readers announce each alert again
|
|
3
|
+
const noticeNodes = new Map();
|
|
4
|
+
let savingNode = null;
|
|
5
|
+
let savingText = null;
|
|
6
|
+
const renderNotices = () => {
|
|
7
|
+
const ids = new Set(state.warnings.map((w) => w.id));
|
|
8
|
+
for (const [id, node] of noticeNodes) {
|
|
9
|
+
if (ids.has(id)) continue;
|
|
10
|
+
node.remove();
|
|
11
|
+
noticeNodes.delete(id);
|
|
12
|
+
}
|
|
13
|
+
for (const w of state.warnings) {
|
|
14
|
+
if (noticeNodes.has(w.id)) continue;
|
|
15
|
+
const node = h("div", { class: "notice warning", "data-warning": String(w.id), role: "alert" },
|
|
16
|
+
h("span", null, w.text),
|
|
17
|
+
h("button", { class: "dismiss", "data-action": "dismiss", "data-id": String(w.id) }, "Dismiss"));
|
|
18
|
+
noticeNodes.set(w.id, node);
|
|
19
|
+
// Before the saving line, which stays last
|
|
20
|
+
notices.insertBefore(node, savingNode);
|
|
21
|
+
}
|
|
22
|
+
const saving = state.saving || null;
|
|
23
|
+
if (saving !== savingText) {
|
|
24
|
+
savingText = saving;
|
|
25
|
+
if (savingNode) savingNode.remove();
|
|
26
|
+
savingNode = saving ? h("div", { class: "notice saving" }, "Saving " + saving + "…") : null;
|
|
27
|
+
if (savingNode) notices.append(savingNode);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const render = () => {
|
|
31
|
+
const active = root.activeElement;
|
|
32
|
+
const kept = active && active.type !== "radio" ? active.getAttribute("data-field") : null;
|
|
33
|
+
let selection = null;
|
|
34
|
+
try { selection = kept ? [active.selectionStart, active.selectionEnd] : null; } catch (err) { selection = null; }
|
|
35
|
+
const panel = ui.panel && !state.collapsed ? h("div", { class: ui.panel === "new" || ui.panel === "insert" ? "panel wide" : "panel" }, PANELS[ui.panel]()) : null;
|
|
36
|
+
container.replaceChildren(state.collapsed ? chipView() : railView(), ...(panel ? [panel] : []));
|
|
37
|
+
renderNotices();
|
|
38
|
+
placeAll();
|
|
39
|
+
const focus = ui.focus || kept;
|
|
40
|
+
ui.focus = null;
|
|
41
|
+
const target = focus && root.querySelector('[data-field="' + focus + '"]');
|
|
42
|
+
if (!target) return;
|
|
43
|
+
target.focus({ preventScroll: true });
|
|
44
|
+
if (focus === kept && selection && selection[0] !== null) {
|
|
45
|
+
try { target.setSelectionRange(selection[0], selection[1]); } catch (err) { /* not a text input */ }
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
`;
|