@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,43 @@
|
|
|
1
|
+
/** What a wait on the page ends with when the page was left first */
|
|
2
|
+
export const LEFT = Symbol("left the page");
|
|
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 class PageScope {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.page = 0;
|
|
10
|
+
/** Wake-ups for the work waiting on the current page: called, and forgotten, when the page changes */
|
|
11
|
+
this.waiting = new Set();
|
|
12
|
+
}
|
|
13
|
+
/** Bumped on every page load */
|
|
14
|
+
get current() {
|
|
15
|
+
return this.page;
|
|
16
|
+
}
|
|
17
|
+
/** A new document loaded */
|
|
18
|
+
enter() {
|
|
19
|
+
this.page++;
|
|
20
|
+
this.leave();
|
|
21
|
+
}
|
|
22
|
+
/** Lets what waits on the current document stop waiting */
|
|
23
|
+
leave() {
|
|
24
|
+
const wakes = [...this.waiting];
|
|
25
|
+
this.waiting.clear();
|
|
26
|
+
for (const wake of wakes)
|
|
27
|
+
wake();
|
|
28
|
+
}
|
|
29
|
+
/** Runs `work`, but stops waiting for it when the page is left; resolves `LEFT` then */
|
|
30
|
+
async whileOn(work) {
|
|
31
|
+
let wake;
|
|
32
|
+
const left = new Promise((resolve) => {
|
|
33
|
+
wake = () => resolve(LEFT);
|
|
34
|
+
this.waiting.add(wake);
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
return await Promise.race([work(), left]);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
this.waiting.delete(wake);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -3,7 +3,7 @@ import type { LocatorCall } from "../../steps/model.js";
|
|
|
3
3
|
export declare const TOOLBAR_SANDBOX = "samurai-toolbar";
|
|
4
4
|
/** `Symbol.for(TOOLBAR_KEY)` on the sandbox global holds the toolbar's hook: `update`, `reply`, `press`, `root` */
|
|
5
5
|
export declare const TOOLBAR_KEY = "samurai.toolbar";
|
|
6
|
-
|
|
6
|
+
type MatcherChoice = "toBeVisible" | "toHaveText" | "toContainText" | "toHaveValue" | "toHaveAttribute" | "toHaveCount";
|
|
7
7
|
/** What the toolbar draws; secrets are names only */
|
|
8
8
|
export interface ToolbarState {
|
|
9
9
|
status: "recording" | "paused";
|
|
@@ -143,3 +143,4 @@ export type ActionReply = {
|
|
|
143
143
|
error: string;
|
|
144
144
|
field?: "name";
|
|
145
145
|
};
|
|
146
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Attaching the host to the document. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const ATTACH_JS = String.raw ` // ---- Attach ----
|
|
3
|
+
let announced = false;
|
|
4
|
+
const attach = () => {
|
|
5
|
+
const html = document.documentElement;
|
|
6
|
+
if (!html) return false;
|
|
7
|
+
if (host.parentNode !== html) html.appendChild(host);
|
|
8
|
+
if (!announced) {
|
|
9
|
+
announced = true;
|
|
10
|
+
render();
|
|
11
|
+
post({ action: "ready", url: location.href });
|
|
12
|
+
}
|
|
13
|
+
return true;
|
|
14
|
+
};
|
|
15
|
+
const rebind = (fresh) => {
|
|
16
|
+
channel = fresh;
|
|
17
|
+
announced = false;
|
|
18
|
+
attach();
|
|
19
|
+
};
|
|
20
|
+
let watcher = null;
|
|
21
|
+
// The recording ended while this document stays open: take every listener and the host away
|
|
22
|
+
const dispose = () => {
|
|
23
|
+
endDrag();
|
|
24
|
+
for (const [target, type, handler, capture] of listening.splice(0)) target.removeEventListener(type, handler, capture);
|
|
25
|
+
if (watcher) watcher.disconnect();
|
|
26
|
+
clearTimeout(toastTimer);
|
|
27
|
+
channel = () => {};
|
|
28
|
+
host.remove();
|
|
29
|
+
if (globalThis[KEY] && globalThis[KEY].root === root) delete globalThis[KEY];
|
|
30
|
+
};
|
|
31
|
+
globalThis[KEY] = { root, update, reply, press, rebind, dispose, state: () => state, picking: () => ui.picking };
|
|
32
|
+
|
|
33
|
+
const focused = document.activeElement;
|
|
34
|
+
if (focused && focused.nodeType === 1 && isTextEntry(focused)) ui.field = focused;
|
|
35
|
+
if (!attach()) {
|
|
36
|
+
watcher = new MutationObserver(() => { if (attach()) watcher.disconnect(); });
|
|
37
|
+
watcher.observe(document, { childList: true });
|
|
38
|
+
}
|
|
39
|
+
// The page may replace the root element while it loads
|
|
40
|
+
listen(document, "DOMContentLoaded", attach);
|
|
41
|
+
listen(window, "load", attach);`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** DOM helpers: the shadow root, element builder and icons. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const DOM_JS = String.raw ` // ---- DOM ----
|
|
3
|
+
const h = (tag, props, ...kids) => {
|
|
4
|
+
const node = document.createElement(tag);
|
|
5
|
+
for (const key in props || {}) {
|
|
6
|
+
const value = props[key];
|
|
7
|
+
if (value === undefined || value === null || value === false) continue;
|
|
8
|
+
if (key === "class") node.className = value;
|
|
9
|
+
else if (key === "value" || key === "checked" || key === "disabled" || key === "selected") node[key] = value;
|
|
10
|
+
else node.setAttribute(key, value === true ? "" : String(value));
|
|
11
|
+
}
|
|
12
|
+
for (const kid of kids.flat(Infinity)) {
|
|
13
|
+
if (kid === null || kid === undefined || kid === false || kid === "") continue;
|
|
14
|
+
node.append(typeof kid === "string" ? document.createTextNode(kid) : kid);
|
|
15
|
+
}
|
|
16
|
+
return node;
|
|
17
|
+
};
|
|
18
|
+
const ico = (text) => h("span", { class: "ico" }, text);
|
|
19
|
+
|
|
20
|
+
// Styles go through the CSSOM (property by property, and a constructed sheet), which a page's CSP does not
|
|
21
|
+
// block, unlike a style attribute or a <style> element without 'unsafe-inline'
|
|
22
|
+
const host = document.createElement("samurai-recorder-toolbar");
|
|
23
|
+
const HOST_STYLE = [
|
|
24
|
+
["all", "initial"], ["display", "block"], ["position", "fixed"], ["top", "0"], ["left", "0"], ["width", "0"],
|
|
25
|
+
["height", "0"], ["overflow", "visible"], ["z-index", "2147483647"],
|
|
26
|
+
];
|
|
27
|
+
for (const [name, value] of HOST_STYLE) host.style.setProperty(name, value, "important");
|
|
28
|
+
const root = host.attachShadow({ mode: "closed" });
|
|
29
|
+
const frame = h("div", { class: "frame" });
|
|
30
|
+
const container = h("div", { class: "ui" });
|
|
31
|
+
const outline = h("div", { class: "outline", hidden: true });
|
|
32
|
+
const toast = h("div", { class: "toast ui", hidden: true });
|
|
33
|
+
const notices = h("div", { class: "notices ui" });
|
|
34
|
+
let adopted = false;
|
|
35
|
+
try {
|
|
36
|
+
const sheet = new CSSStyleSheet();
|
|
37
|
+
sheet.replaceSync(CSS);
|
|
38
|
+
root.adoptedStyleSheets = [sheet];
|
|
39
|
+
adopted = root.adoptedStyleSheets.length === 1;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
adopted = false;
|
|
42
|
+
}
|
|
43
|
+
// Without constructed sheets: a <style> element, which a strict CSP blocks
|
|
44
|
+
root.append(...(adopted ? [] : [h("style", null, CSS)]), frame, outline, container, toast, notices);
|
|
45
|
+
|
|
46
|
+
const viewport = () => ({ w: frame.clientWidth || innerWidth, h: frame.clientHeight || innerHeight });
|
|
47
|
+
`;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Dragging the rail. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const DRAG_JS = String.raw ` // ---- Drag ----
|
|
3
|
+
const onDragMove = (event) => {
|
|
4
|
+
if (!ui.drag) return;
|
|
5
|
+
ui.drag.pos = { x: event.clientX - ui.drag.dx, y: event.clientY - ui.drag.dy };
|
|
6
|
+
ui.drag.moved = true;
|
|
7
|
+
placeAll();
|
|
8
|
+
};
|
|
9
|
+
const endDrag = () => {
|
|
10
|
+
if (!ui.drag) return;
|
|
11
|
+
const { moved, pointer } = ui.drag;
|
|
12
|
+
ui.drag = null;
|
|
13
|
+
window.removeEventListener("pointermove", onDragMove, true);
|
|
14
|
+
window.removeEventListener("pointerup", endDrag, true);
|
|
15
|
+
window.removeEventListener("pointercancel", endDrag, true);
|
|
16
|
+
try { host.releasePointerCapture(pointer); } catch (err) { /* already released */ }
|
|
17
|
+
if (!moved) return;
|
|
18
|
+
ui.dropped = { x: Math.round(railBox.x), y: Math.round(railBox.y) };
|
|
19
|
+
post({ action: "move", x: ui.dropped.x, y: ui.dropped.y });
|
|
20
|
+
placeAll();
|
|
21
|
+
};
|
|
22
|
+
const startDrag = (event) => {
|
|
23
|
+
if (event.button !== 0) return;
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
ui.drag = { dx: event.clientX - railBox.x, dy: event.clientY - railBox.y, pos: { x: railBox.x, y: railBox.y }, moved: false, pointer: event.pointerId };
|
|
26
|
+
// Captured on the host, which re-renders never replace
|
|
27
|
+
try { host.setPointerCapture(event.pointerId); } catch (err) { /* moves still arrive while over the window */ }
|
|
28
|
+
window.addEventListener("pointermove", onDragMove, true);
|
|
29
|
+
window.addEventListener("pointerup", endDrag, true);
|
|
30
|
+
window.addEventListener("pointercancel", endDrag, true);
|
|
31
|
+
};
|
|
32
|
+
`;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Events on the page, seen in the capture phase. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const EVENTS_PAGE_JS = String.raw ` // ---- Events on the page (window capture runs before the page's and the capture script's listeners) ----
|
|
3
|
+
const swallow = (event) => {
|
|
4
|
+
event.preventDefault();
|
|
5
|
+
event.stopImmediatePropagation();
|
|
6
|
+
};
|
|
7
|
+
// Typing in the toolbar stays in the toolbar: these run first on the window (registered at document start,
|
|
8
|
+
// before any page script), so no page listener sees a key, typed text or the clipboard of a toolbar input
|
|
9
|
+
// (the New secret value among them). The browser's default action, the text going into the input, still
|
|
10
|
+
// happens. Outside the shadow root the event's target is the host, so the input is the focused element.
|
|
11
|
+
const TYPING = ["keydown", "keyup", "keypress", "beforeinput", "input", "compositionstart", "compositionupdate", "compositionend", "paste", "cut", "copy"];
|
|
12
|
+
for (const type of TYPING) {
|
|
13
|
+
listen(window, type, (event) => {
|
|
14
|
+
// An untrusted event was dispatched by the page itself: it neither carries the tester's typing nor acts
|
|
15
|
+
if (!event.isTrusted || !inToolbar(event)) return;
|
|
16
|
+
event.stopImmediatePropagation();
|
|
17
|
+
const target = root.activeElement;
|
|
18
|
+
if (type === "keydown") {
|
|
19
|
+
if (event.key === "Escape" && (ui.picking || ui.panel)) {
|
|
20
|
+
event.preventDefault();
|
|
21
|
+
handlers.escape();
|
|
22
|
+
} else onKeydown(event, target);
|
|
23
|
+
} else if (type === "input" && target && target.type !== "radio" && target.type !== "checkbox") {
|
|
24
|
+
// A radio or checkbox the pointer changed need not have the focus; its change event (below) updates it
|
|
25
|
+
onField(event, target);
|
|
26
|
+
}
|
|
27
|
+
}, true);
|
|
28
|
+
}
|
|
29
|
+
listen(window, "keydown", (event) => {
|
|
30
|
+
if (event.isTrusted && event.key === "Escape" && (ui.picking || ui.panel)) {
|
|
31
|
+
swallow(event);
|
|
32
|
+
handlers.escape();
|
|
33
|
+
}
|
|
34
|
+
}, true);
|
|
35
|
+
listen(window, "pointerover", (event) => {
|
|
36
|
+
if (!ui.picking) return;
|
|
37
|
+
if (inToolbar(event) || !event.target || event.target.nodeType !== 1) hideOutline();
|
|
38
|
+
else showOutline(event.target);
|
|
39
|
+
}, true);
|
|
40
|
+
for (const type of ["pointerdown", "mousedown", "pointerup", "mouseup", "dblclick", "auxclick", "contextmenu"]) {
|
|
41
|
+
listen(window, type, (event) => {
|
|
42
|
+
if (inToolbar(event)) return;
|
|
43
|
+
if (ui.picking) return swallow(event);
|
|
44
|
+
if (type === "pointerdown" && ui.panel) {
|
|
45
|
+
closePanel();
|
|
46
|
+
render();
|
|
47
|
+
}
|
|
48
|
+
}, true);
|
|
49
|
+
}
|
|
50
|
+
listen(window, "click", (event) => {
|
|
51
|
+
if (!ui.picking || inToolbar(event)) return;
|
|
52
|
+
swallow(event);
|
|
53
|
+
pick(event.target);
|
|
54
|
+
}, true);
|
|
55
|
+
listen(document, "focusin", (event) => {
|
|
56
|
+
if (inToolbar(event) || !event.target || event.target.nodeType !== 1) return;
|
|
57
|
+
ui.field = isTextEntry(event.target) ? event.target : null;
|
|
58
|
+
if (ui.panel === "insert" && !ui.insert.field && ui.field) {
|
|
59
|
+
ui.insert.field = ui.field;
|
|
60
|
+
render();
|
|
61
|
+
}
|
|
62
|
+
}, true);
|
|
63
|
+
listen(window, "resize", () => render());
|
|
64
|
+
listen(window, "scroll", () => {
|
|
65
|
+
if (ui.panel === "assert" && ui.assert && !outline.hidden) showOutline(ui.assert.target);
|
|
66
|
+
}, true);
|
|
67
|
+
`;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Events inside the toolbar. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const EVENTS_TOOLBAR_JS = String.raw ` // ---- Events inside the toolbar; none of them reach the page's bubbling listeners ----
|
|
3
|
+
root.addEventListener("click", (event) => {
|
|
4
|
+
event.stopPropagation();
|
|
5
|
+
const control = event.target.closest && event.target.closest("[data-action]");
|
|
6
|
+
if (!control || control.disabled) return;
|
|
7
|
+
press(control.getAttribute("data-action"), {
|
|
8
|
+
kind: control.getAttribute("data-kind"),
|
|
9
|
+
name: control.getAttribute("data-name"),
|
|
10
|
+
matcher: control.getAttribute("data-matcher"),
|
|
11
|
+
id: control.getAttribute("data-id"),
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
root.addEventListener("pointerdown", (event) => {
|
|
15
|
+
event.stopPropagation();
|
|
16
|
+
if (event.target.closest && event.target.closest("[data-grip]")) startDrag(event);
|
|
17
|
+
});
|
|
18
|
+
const onKeydown = (event, target) => {
|
|
19
|
+
const enter = event.key === "Enter" && target && target.getAttribute && target.getAttribute("data-enter");
|
|
20
|
+
if (!enter || event.isComposing) return;
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
if (target.getAttribute("data-field")) onField(event, target);
|
|
23
|
+
press(enter);
|
|
24
|
+
};
|
|
25
|
+
// Typing reaches these only when it does not cross the shadow boundary (the window listeners below take the rest)
|
|
26
|
+
root.addEventListener("input", (event) => { event.stopPropagation(); onField(event); });
|
|
27
|
+
root.addEventListener("change", (event) => { event.stopPropagation(); onField(event); });
|
|
28
|
+
root.addEventListener("keydown", (event) => {
|
|
29
|
+
event.stopPropagation();
|
|
30
|
+
onKeydown(event, event.target);
|
|
31
|
+
});
|
|
32
|
+
for (const type of ["keyup", "keypress", "mousedown", "mouseup", "pointerup", "dblclick", "focusin", "focusout"]) {
|
|
33
|
+
root.addEventListener(type, (event) => event.stopPropagation());
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** press() for tests and form field updates. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const FIELDS_JS = String.raw ` const press = (action, detail) => {
|
|
3
|
+
const handler = handlers[action];
|
|
4
|
+
if (handler) handler(detail || {});
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const onField = (event, target) => {
|
|
8
|
+
const t = target || event.target;
|
|
9
|
+
const field = t && t.getAttribute && t.getAttribute("data-field");
|
|
10
|
+
if (!field) return;
|
|
11
|
+
const value = t.value;
|
|
12
|
+
if (field === "expected" && ui.assert) ui.assert.expected = value;
|
|
13
|
+
else if (field === "attribute" && ui.assert) {
|
|
14
|
+
ui.assert.attribute = value;
|
|
15
|
+
ui.assert.expected = prefill(ui.assert, "toHaveAttribute");
|
|
16
|
+
render();
|
|
17
|
+
} else if (field === "countIndex" && ui.assert) {
|
|
18
|
+
ui.assert.countIndex = Number(value);
|
|
19
|
+
ui.assert.expected = prefill(ui.assert, "toHaveCount");
|
|
20
|
+
render();
|
|
21
|
+
} else if (field === "filter" && ui.insert) {
|
|
22
|
+
if (ui.insert.filter === value) return;
|
|
23
|
+
ui.insert.filter = value;
|
|
24
|
+
render();
|
|
25
|
+
} else if (field === "name" && ui.form) {
|
|
26
|
+
if (ui.form.name === value) return;
|
|
27
|
+
ui.form.name = value;
|
|
28
|
+
ui.form.nameError = null;
|
|
29
|
+
render();
|
|
30
|
+
} else if (field === "value" && ui.form) ui.form.value = value;
|
|
31
|
+
else if (field === "waitKind") {
|
|
32
|
+
ui.wait.kind = value === "time" ? "time" : "idle";
|
|
33
|
+
render();
|
|
34
|
+
} else if (field === "seconds") {
|
|
35
|
+
ui.wait.seconds = value;
|
|
36
|
+
ui.wait.kind = "time";
|
|
37
|
+
render();
|
|
38
|
+
} else if (field === "shotName") ui.shot.name = value;
|
|
39
|
+
else if (field === "fullPage") {
|
|
40
|
+
ui.shot.fullPage = value === "true";
|
|
41
|
+
render();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
`;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/** Every toolbar action, as a click on its control runs it. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const HANDLERS_JS = String.raw ` // Every action is what a click on its control does; press() runs the same functions
|
|
3
|
+
const handlers = {
|
|
4
|
+
dismiss: (d) => post({ action: "dismiss", id: Number(d.id) }),
|
|
5
|
+
pause: () => { if (!paused()) post({ action: "pause" }); },
|
|
6
|
+
resume: () => { if (paused()) post({ action: "resume" }); },
|
|
7
|
+
stop: () => {
|
|
8
|
+
stopPicking();
|
|
9
|
+
closePanel();
|
|
10
|
+
post({ action: "stop" });
|
|
11
|
+
render();
|
|
12
|
+
},
|
|
13
|
+
collapse: () => {
|
|
14
|
+
stopPicking();
|
|
15
|
+
closePanel();
|
|
16
|
+
post({ action: "collapse", collapsed: true });
|
|
17
|
+
render();
|
|
18
|
+
},
|
|
19
|
+
expand: () => post({ action: "collapse", collapsed: false }),
|
|
20
|
+
"resume-yes": () => { if (state.resumeQuestion) post({ action: "resume-goto", add: true }); },
|
|
21
|
+
"resume-no": () => { if (state.resumeQuestion) post({ action: "resume-goto", add: false }); },
|
|
22
|
+
assert: () => {
|
|
23
|
+
if (!usable()) return;
|
|
24
|
+
const wasOn = toolOn("assert");
|
|
25
|
+
stopPicking();
|
|
26
|
+
closePanel();
|
|
27
|
+
ui.picking = !wasOn;
|
|
28
|
+
render();
|
|
29
|
+
},
|
|
30
|
+
insert: () => openTool("insert"),
|
|
31
|
+
wait: () => openTool("wait"),
|
|
32
|
+
screenshot: () => openTool("screenshot"),
|
|
33
|
+
pick: (detail) => pick(detail.target),
|
|
34
|
+
matcher: (detail) => {
|
|
35
|
+
const a = ui.panel === "assert" && ui.assert;
|
|
36
|
+
if (!a || !a.info || !a.info.ok || unavailable(a.info, detail.matcher)) return;
|
|
37
|
+
a.matcher = detail.matcher;
|
|
38
|
+
a.expected = prefill(a, detail.matcher);
|
|
39
|
+
render();
|
|
40
|
+
},
|
|
41
|
+
not: () => {
|
|
42
|
+
if (ui.panel !== "assert" || !ui.assert || !ui.assert.info) return;
|
|
43
|
+
ui.assert.not = !ui.assert.not;
|
|
44
|
+
render();
|
|
45
|
+
},
|
|
46
|
+
"assert-add": () => {
|
|
47
|
+
const a = ui.panel === "assert" && ui.assert;
|
|
48
|
+
if (!a || ui.busy || !a.info || !a.info.ok) return;
|
|
49
|
+
const message = { action: "assert", id: a.id, target: a.target, candidates: a.candidates, matcher: a.matcher, not: a.not };
|
|
50
|
+
if (a.matcher !== "toBeVisible") message.expected = a.expected;
|
|
51
|
+
if (a.matcher === "toHaveAttribute") message.attribute = a.attribute;
|
|
52
|
+
if (a.matcher === "toHaveCount") message.countIndex = a.countIndex;
|
|
53
|
+
ui.busy = true;
|
|
54
|
+
a.error = null;
|
|
55
|
+
post(message);
|
|
56
|
+
render();
|
|
57
|
+
},
|
|
58
|
+
"insert-item": (detail) => {
|
|
59
|
+
if (ui.panel !== "insert" || ui.busy || !ui.insert.field) return;
|
|
60
|
+
if (detail.kind === "secret") {
|
|
61
|
+
const secret = state.secrets.find((s) => s.name === detail.name);
|
|
62
|
+
if (!secret) return;
|
|
63
|
+
if (!secret.set) return openForm("secret", secret.name, true);
|
|
64
|
+
} else if (!state.canAddVariables || !state.variables.some((v) => v.name === detail.name)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
sendInsert(detail.kind, detail.name);
|
|
68
|
+
},
|
|
69
|
+
"insert-first": () => {
|
|
70
|
+
if (ui.panel !== "insert" || !ui.insert.field) return;
|
|
71
|
+
const found = matches();
|
|
72
|
+
const first = found.variables[0] ? { kind: "env", name: found.variables[0].name } : found.secrets[0] && { kind: "secret", name: found.secrets[0].name };
|
|
73
|
+
if (first) handlers["insert-item"](first);
|
|
74
|
+
},
|
|
75
|
+
"insert-new": (detail) => {
|
|
76
|
+
if (ui.panel !== "insert" || !ui.insert.field) return;
|
|
77
|
+
const filter = ui.insert.filter.trim();
|
|
78
|
+
const base = filter || fieldName(ui.insert.field);
|
|
79
|
+
const name = detail.kind === "secret" ? snake(base) : filter ? variableName(filter) : camel(base);
|
|
80
|
+
openForm(detail.kind === "secret" ? "secret" : "env", name, false);
|
|
81
|
+
},
|
|
82
|
+
"new-kind": (detail) => {
|
|
83
|
+
const f = ui.panel === "new" && ui.form;
|
|
84
|
+
if (!f || ui.busy || f.kind === detail.kind) return;
|
|
85
|
+
f.kind = detail.kind === "secret" ? "secret" : "env";
|
|
86
|
+
f.name = f.kind === "secret" ? snake(f.name) : camel(f.name);
|
|
87
|
+
f.nameError = null;
|
|
88
|
+
f.error = null;
|
|
89
|
+
render();
|
|
90
|
+
},
|
|
91
|
+
"new-save": () => {
|
|
92
|
+
const f = ui.panel === "new" && ui.form;
|
|
93
|
+
if (!f || ui.busy || !f.name.trim() || (f.kind === "env" && !state.canAddVariables)) return;
|
|
94
|
+
const field = ui.insert.field;
|
|
95
|
+
const id = nextId++;
|
|
96
|
+
ui.pending = id;
|
|
97
|
+
ui.busy = true;
|
|
98
|
+
f.nameError = null;
|
|
99
|
+
f.error = null;
|
|
100
|
+
post({ action: "add-insertable", id, target: field, candidates: candidatesOf(field), kind: f.kind, name: f.name.trim(), value: f.value });
|
|
101
|
+
render();
|
|
102
|
+
},
|
|
103
|
+
"new-cancel": () => {
|
|
104
|
+
if (ui.panel !== "new") return;
|
|
105
|
+
ui.panel = "insert";
|
|
106
|
+
ui.form = null;
|
|
107
|
+
ui.busy = false;
|
|
108
|
+
ui.pending = 0;
|
|
109
|
+
ui.focus = "filter";
|
|
110
|
+
render();
|
|
111
|
+
},
|
|
112
|
+
"wait-add": () => {
|
|
113
|
+
if (ui.panel !== "wait" || ui.busy) return;
|
|
114
|
+
if (ui.wait.kind === "time" && !secondsValid()) return;
|
|
115
|
+
ui.busy = true;
|
|
116
|
+
ui.closeOnUpdate = true;
|
|
117
|
+
post(ui.wait.kind === "time" ? { action: "wait", kind: "time", ms: Math.round(Number(ui.wait.seconds) * 1000) } : { action: "wait", kind: "idle" });
|
|
118
|
+
render();
|
|
119
|
+
},
|
|
120
|
+
"screenshot-add": () => {
|
|
121
|
+
if (ui.panel !== "screenshot" || ui.busy) return;
|
|
122
|
+
ui.busy = true;
|
|
123
|
+
ui.closeOnUpdate = true;
|
|
124
|
+
post({ action: "screenshot", name: ui.shot.name.trim(), fullPage: ui.shot.fullPage });
|
|
125
|
+
render();
|
|
126
|
+
},
|
|
127
|
+
escape: () => {
|
|
128
|
+
if (!ui.picking && !ui.panel) return false;
|
|
129
|
+
stopPicking();
|
|
130
|
+
closePanel();
|
|
131
|
+
render();
|
|
132
|
+
return true;
|
|
133
|
+
},
|
|
134
|
+
};`;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Variable and field naming helpers. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const NAMES_JS = String.raw ` // ---- Names and labels ----
|
|
3
|
+
const words = (text) => String(text).replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
4
|
+
const camel = (text) => words(text).map((w, i) => (i ? w[0].toUpperCase() + w.slice(1).toLowerCase() : w.toLowerCase())).join("");
|
|
5
|
+
const snake = (text) => words(text).map((w) => w.toUpperCase()).join("_");
|
|
6
|
+
const variableName = (text) => (/^[A-Za-z_$][\w$]*$/.test(text.trim()) ? text.trim() : camel(text));
|
|
7
|
+
const fieldName = (el) => labelTexts(el)[0] || el.getAttribute("name") || el.id || "";
|
|
8
|
+
const fieldLabel = (el) => {
|
|
9
|
+
const text = labelTexts(el)[0] || el.getAttribute("placeholder") || el.getAttribute("name") || el.id || "field";
|
|
10
|
+
return text.length > 40 ? text.slice(0, 39) + "…" : text;
|
|
11
|
+
};
|
|
12
|
+
`;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Opening and closing panels, and picking an element. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const PICKING_JS = String.raw ` // ---- Panels and picking ----
|
|
3
|
+
const closePanel = () => {
|
|
4
|
+
ui.panel = null;
|
|
5
|
+
ui.assert = null;
|
|
6
|
+
ui.busy = false;
|
|
7
|
+
ui.closeOnUpdate = false;
|
|
8
|
+
ui.pending = 0;
|
|
9
|
+
hideOutline();
|
|
10
|
+
};
|
|
11
|
+
const stopPicking = () => {
|
|
12
|
+
ui.picking = false;
|
|
13
|
+
if (ui.panel !== "assert") hideOutline();
|
|
14
|
+
};
|
|
15
|
+
const usable = () => !paused() && !state.collapsed;
|
|
16
|
+
const openTool = (tool) => {
|
|
17
|
+
if (!usable()) return;
|
|
18
|
+
const wasOpen = toolOn(tool);
|
|
19
|
+
stopPicking();
|
|
20
|
+
closePanel();
|
|
21
|
+
if (!wasOpen) {
|
|
22
|
+
ui.panel = tool;
|
|
23
|
+
if (tool === "insert") {
|
|
24
|
+
// The field focused now, else the last one (a toolbar button may have taken the focus)
|
|
25
|
+
const active = document.activeElement;
|
|
26
|
+
const focused = active && active !== host && active.nodeType === 1 && isTextEntry(active) ? active : null;
|
|
27
|
+
const field = focused || (ui.field && ui.field.isConnected ? ui.field : null);
|
|
28
|
+
ui.field = field;
|
|
29
|
+
ui.insert = { field, filter: "", error: null };
|
|
30
|
+
if (field) ui.focus = "filter";
|
|
31
|
+
} else if (tool === "wait") {
|
|
32
|
+
ui.wait = { kind: "idle", seconds: "2" };
|
|
33
|
+
} else if (tool === "screenshot") {
|
|
34
|
+
ui.shot = { name: state.nextScreenshot, fullPage: false };
|
|
35
|
+
ui.focus = "shotName";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
render();
|
|
39
|
+
};
|
|
40
|
+
const pick = (target) => {
|
|
41
|
+
if (!ui.picking || !target || target.nodeType !== 1) return;
|
|
42
|
+
ui.picking = false;
|
|
43
|
+
closePanel();
|
|
44
|
+
const id = nextId++;
|
|
45
|
+
const candidates = candidatesOf(target);
|
|
46
|
+
const r = target.getBoundingClientRect();
|
|
47
|
+
ui.assert = {
|
|
48
|
+
id, target, candidates, info: null, matcher: "toBeVisible", expected: "", attribute: null, countIndex: 0, not: false, error: null,
|
|
49
|
+
rect: { left: r.left, top: r.top, right: r.right, bottom: r.bottom },
|
|
50
|
+
};
|
|
51
|
+
ui.panel = "assert";
|
|
52
|
+
ui.pending = id;
|
|
53
|
+
showOutline(target);
|
|
54
|
+
post({ action: "pick", id, target, candidates });
|
|
55
|
+
render();
|
|
56
|
+
};
|
|
57
|
+
const openForm = (kind, name, setValue) => {
|
|
58
|
+
ui.form = { kind, name, value: "", nameError: null, error: null, setValue };
|
|
59
|
+
ui.panel = "new";
|
|
60
|
+
ui.focus = name ? "value" : "name";
|
|
61
|
+
render();
|
|
62
|
+
};
|
|
63
|
+
const sendInsert = (kind, name) => {
|
|
64
|
+
const field = ui.insert.field;
|
|
65
|
+
const id = nextId++;
|
|
66
|
+
ui.pending = id;
|
|
67
|
+
ui.busy = true;
|
|
68
|
+
ui.insert.error = null;
|
|
69
|
+
post({ action: "insert", id, target: field, candidates: candidatesOf(field), kind, name });
|
|
70
|
+
render();
|
|
71
|
+
};
|
|
72
|
+
`;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Where the rail and panels sit in the window. A slice of the toolbar function: it shares one closure with its neighbours, see `../source.ts`. */
|
|
2
|
+
export const PLACEMENT_JS = String.raw ` // ---- Placement ----
|
|
3
|
+
const place = (node, want) => {
|
|
4
|
+
const rect = node.getBoundingClientRect();
|
|
5
|
+
const v = viewport();
|
|
6
|
+
const x = clamp(want ? want.x : v.w - rect.width - 12, v.w - rect.width);
|
|
7
|
+
const y = clamp(want ? want.y : 12, v.h - rect.height);
|
|
8
|
+
node.style.left = x + "px";
|
|
9
|
+
node.style.top = y + "px";
|
|
10
|
+
return { x, y, w: rect.width, h: rect.height };
|
|
11
|
+
};
|
|
12
|
+
const wantedSpot = () => (ui.drag ? ui.drag.pos : ui.dropped || state.position);
|
|
13
|
+
const placePanel = (panel) => {
|
|
14
|
+
const rect = panel.getBoundingClientRect();
|
|
15
|
+
const v = viewport();
|
|
16
|
+
let x;
|
|
17
|
+
let y;
|
|
18
|
+
if (ui.panel === "assert" && ui.assert) {
|
|
19
|
+
const t = ui.assert.rect;
|
|
20
|
+
// Beside the element; below it (or above) when it is too wide for either side
|
|
21
|
+
if (t.right + 8 + rect.width <= v.w) [x, y] = [t.right + 8, t.top];
|
|
22
|
+
else if (t.left - 8 - rect.width >= 0) [x, y] = [t.left - 8 - rect.width, t.top];
|
|
23
|
+
else [x, y] = [t.left, t.bottom + 8 + rect.height <= v.h ? t.bottom + 8 : t.top - 8 - rect.height];
|
|
24
|
+
} else {
|
|
25
|
+
x = railBox.x + railBox.w / 2 > v.w / 2 ? railBox.x - 8 - rect.width : railBox.x + railBox.w + 8;
|
|
26
|
+
y = railBox.y;
|
|
27
|
+
}
|
|
28
|
+
panel.style.left = clamp(x, v.w - rect.width) + "px";
|
|
29
|
+
panel.style.top = clamp(y, v.h - rect.height) + "px";
|
|
30
|
+
};
|
|
31
|
+
const placeAll = () => {
|
|
32
|
+
const [rail, panel] = container.children;
|
|
33
|
+
if (rail) railBox = place(rail, wantedSpot());
|
|
34
|
+
if (panel) placePanel(panel);
|
|
35
|
+
};
|
|
36
|
+
const showOutline = (el) => {
|
|
37
|
+
const r = el.getBoundingClientRect();
|
|
38
|
+
outline.style.left = r.left + "px";
|
|
39
|
+
outline.style.top = r.top + "px";
|
|
40
|
+
outline.style.width = r.width + "px";
|
|
41
|
+
outline.style.height = r.height + "px";
|
|
42
|
+
outline.hidden = false;
|
|
43
|
+
};
|
|
44
|
+
const hideOutline = () => { outline.hidden = true; };
|
|
45
|
+
`;
|