@itmetsam/samurai-framework 0.9.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/dist/assert/assertion-error.d.ts +1 -1
- package/dist/assert/expect.d.ts +5 -50
- package/dist/assert/expect.js +5 -182
- package/dist/assert/locator-assertions.d.ts +22 -0
- package/dist/assert/locator-assertions.js +61 -0
- package/dist/assert/pending-assertions.d.ts +7 -0
- package/dist/assert/pending-assertions.js +40 -0
- package/dist/assert/text-match.d.ts +4 -0
- package/dist/assert/text-match.js +11 -0
- package/dist/assert/value-assertions.d.ts +23 -0
- package/dist/assert/value-assertions.js +82 -0
- package/dist/browser/browser-context.d.ts +0 -1
- package/dist/browser/browser-context.js +13 -5
- package/dist/browser/browser-finder.js +5 -9
- package/dist/browser/browser.d.ts +3 -31
- package/dist/browser/browser.js +36 -219
- package/dist/browser/chrome-launch.d.ts +14 -1
- package/dist/browser/chrome-launch.js +44 -1
- package/dist/browser/dialog-handling.d.ts +32 -0
- package/dist/browser/dialog-handling.js +85 -0
- package/dist/browser/document-watcher.d.ts +44 -0
- package/dist/browser/document-watcher.js +160 -0
- package/dist/browser/download-folders.d.ts +0 -1
- package/dist/browser/download-folders.js +7 -15
- package/dist/browser/download.js +5 -10
- package/dist/browser/downloads-dir.js +2 -2
- package/dist/browser/emit.d.ts +7 -0
- package/dist/browser/emit.js +16 -0
- package/dist/browser/emulation.d.ts +1 -1
- package/dist/browser/emulation.js +54 -18
- package/dist/browser/file-chooser.js +4 -11
- package/dist/browser/history.d.ts +6 -0
- package/dist/browser/history.js +89 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/init-script.d.ts +2 -0
- package/dist/browser/init-script.js +16 -0
- package/dist/browser/launch-firefox.d.ts +20 -0
- package/dist/browser/launch-firefox.js +66 -0
- package/dist/browser/launch-flags.d.ts +18 -0
- package/dist/browser/launch-flags.js +100 -0
- package/dist/browser/lazy.d.ts +21 -0
- package/dist/browser/lazy.js +44 -0
- package/dist/browser/page-events.d.ts +44 -0
- package/dist/browser/page-events.js +58 -0
- package/dist/browser/page-logs.js +2 -9
- package/dist/browser/page-navigation.d.ts +58 -0
- package/dist/browser/page-navigation.js +108 -0
- package/dist/browser/page-network.d.ts +69 -0
- package/dist/browser/page-network.js +163 -0
- package/dist/browser/page-settings.d.ts +35 -0
- package/dist/browser/page-settings.js +51 -0
- package/dist/browser/page-transfers.d.ts +36 -0
- package/dist/browser/page-transfers.js +50 -0
- package/dist/browser/page.d.ts +39 -154
- package/dist/browser/page.js +134 -758
- package/dist/browser/pdf.d.ts +1 -1
- package/dist/browser/session.d.ts +16 -0
- package/dist/browser/session.js +32 -0
- package/dist/browser/viewport.d.ts +19 -0
- package/dist/browser/viewport.js +72 -0
- package/dist/browser/warm-up.d.ts +1 -1
- package/dist/browser/warm-up.js +2 -1
- package/dist/browsers/index.d.ts +1 -1
- package/dist/browsers/index.js +1 -1
- package/dist/browsers/pins.d.ts +0 -2
- package/dist/browsers/pins.js +0 -2
- package/dist/browsers/store.d.ts +0 -2
- package/dist/browsers/store.js +1 -2
- package/dist/cli/args.d.ts +19 -7
- package/dist/cli/args.js +18 -9
- package/dist/cli/browsers.d.ts +1 -2
- package/dist/cli/browsers.js +2 -2
- package/dist/cli/console-reporter.d.ts +4 -0
- package/dist/cli/console-reporter.js +15 -7
- package/dist/cli/flow-reporter.js +2 -1
- package/dist/cli/flow.d.ts +1 -2
- package/dist/cli/flow.js +25 -22
- package/dist/cli/init.js +3 -8
- package/dist/cli/io.d.ts +10 -0
- package/dist/cli/io.js +1 -0
- package/dist/cli/main.d.ts +3 -10
- package/dist/cli/main.js +11 -137
- package/dist/cli/paint.d.ts +2 -0
- package/dist/cli/paint.js +4 -0
- package/dist/cli/record.d.ts +4 -0
- package/dist/cli/record.js +93 -0
- package/dist/cli/usage.d.ts +1 -0
- package/dist/cli/usage.js +58 -0
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.js +7 -0
- package/dist/config/browsers.d.ts +11 -0
- package/dist/config/browsers.js +32 -0
- package/dist/config/config.js +8 -1
- package/dist/config/mask.d.ts +1 -1
- package/dist/config/mask.js +7 -2
- package/dist/config/run-settings.d.ts +1 -2
- package/dist/config/run-settings.js +1 -6
- package/dist/config/timeouts.d.ts +2 -0
- package/dist/config/timeouts.js +13 -0
- package/dist/config/workers.d.ts +14 -0
- package/dist/config/workers.js +29 -0
- package/dist/flows/run/branch-context.d.ts +14 -0
- package/dist/flows/run/branch-context.js +35 -0
- package/dist/flows/run/handlers/condition.d.ts +1 -1
- package/dist/flows/run/handlers/end.d.ts +1 -1
- package/dist/flows/run/handlers/group.d.ts +1 -1
- package/dist/flows/run/handlers/index.d.ts +2 -2
- package/dist/flows/run/handlers/parallel.d.ts +1 -1
- package/dist/flows/run/handlers/start.d.ts +1 -1
- package/dist/flows/run/handlers/support.d.ts +3 -24
- package/dist/flows/run/handlers/support.js +1 -1
- package/dist/flows/run/handlers/test.d.ts +1 -1
- package/dist/flows/run/handlers/types.d.ts +27 -0
- package/dist/flows/run/handlers/types.js +1 -0
- package/dist/flows/run/index.d.ts +1 -1
- package/dist/flows/run/prepare-flow.d.ts +14 -0
- package/dist/flows/run/prepare-flow.js +44 -0
- package/dist/flows/run/project.d.ts +33 -2
- package/dist/flows/run/project.js +139 -49
- package/dist/flows/run/run.d.ts +8 -4
- package/dist/flows/run/run.js +30 -194
- package/dist/flows/run/walker.d.ts +56 -0
- package/dist/flows/run/walker.js +225 -0
- package/dist/flows/steps/catalog.d.ts +1 -1
- package/dist/flows/steps/handler.d.ts +1 -1
- package/dist/flows/steps/set.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/locator/actionability.d.ts +33 -0
- package/dist/locator/actionability.js +104 -0
- package/dist/locator/chain-resolver.d.ts +20 -0
- package/dist/locator/chain-resolver.js +102 -0
- package/dist/locator/document-swap.d.ts +1 -0
- package/dist/locator/document-swap.js +11 -0
- package/dist/locator/element-state.d.ts +3 -1
- package/dist/locator/element-state.js +11 -3
- package/dist/locator/file-actions.d.ts +35 -0
- package/dist/locator/file-actions.js +55 -0
- package/dist/{script/helpers.js → locator/helper-realm.js} +2 -2
- package/dist/locator/input-actions.d.ts +18 -0
- package/dist/locator/input-actions.js +80 -0
- package/dist/locator/label-locator.d.ts +1 -1
- package/dist/locator/label-locator.js +5 -12
- package/dist/locator/locator.d.ts +14 -37
- package/dist/locator/locator.js +68 -424
- package/dist/locator/page-scripts.d.ts +16 -0
- package/dist/locator/page-scripts.js +54 -0
- package/dist/locator/page-search.d.ts +10 -0
- package/dist/locator/page-search.js +19 -0
- package/dist/locator/selector-errors.d.ts +0 -4
- package/dist/locator/selector-errors.js +0 -7
- package/dist/locator/selector.d.ts +14 -13
- package/dist/locator/selector.js +10 -16
- package/dist/locator/text-locator.d.ts +1 -1
- package/dist/locator/text-locator.js +5 -9
- package/dist/logger/index.d.ts +6 -0
- package/dist/logger/index.js +14 -7
- package/dist/network/bidi-wire.d.ts +10 -0
- package/dist/network/bidi-wire.js +38 -0
- package/dist/network/intercept.d.ts +30 -0
- package/dist/network/intercept.js +130 -0
- package/dist/network/network-tracker.d.ts +10 -47
- package/dist/network/network-tracker.js +34 -68
- package/dist/network/network-types.d.ts +33 -0
- package/dist/network/network-types.js +1 -0
- package/dist/network/network-wait.d.ts +7 -2
- package/dist/network/network-wait.js +14 -4
- package/dist/network/response.d.ts +1 -1
- package/dist/network/response.js +1 -1
- package/dist/network/route.d.ts +92 -0
- package/dist/network/route.js +142 -0
- package/dist/network/router.d.ts +6 -91
- package/dist/network/router.js +25 -258
- package/dist/recorder/expectation.d.ts +5 -0
- package/dist/recorder/expectation.js +20 -0
- package/dist/recorder/files-recorder.d.ts +30 -0
- package/dist/recorder/files-recorder.js +88 -0
- package/dist/recorder/normalise.d.ts +17 -6
- package/dist/recorder/normalise.js +96 -91
- package/dist/recorder/options.d.ts +56 -0
- package/dist/recorder/options.js +1 -0
- package/dist/recorder/page-messages.d.ts +35 -0
- package/dist/recorder/page-messages.js +26 -0
- package/dist/recorder/ranking.js +1 -0
- package/dist/recorder/realms.d.ts +6 -0
- package/dist/recorder/realms.js +13 -0
- package/dist/recorder/recorder.d.ts +10 -74
- package/dist/recorder/recorder.js +39 -199
- package/dist/recorder/session-browser.d.ts +22 -0
- package/dist/recorder/session-browser.js +21 -0
- package/dist/recorder/session-secrets.d.ts +8 -0
- package/dist/recorder/session-secrets.js +45 -0
- package/dist/recorder/session.d.ts +7 -13
- package/dist/recorder/session.js +22 -117
- package/dist/recorder/spec-source.d.ts +39 -0
- package/dist/recorder/spec-source.js +84 -0
- package/dist/recorder/toolbar/action-guard.d.ts +21 -0
- package/dist/recorder/toolbar/action-guard.js +43 -0
- package/dist/recorder/toolbar/actions/assert.d.ts +4 -0
- package/dist/recorder/toolbar/actions/assert.js +52 -0
- package/dist/recorder/toolbar/actions/insert.d.ts +6 -0
- package/dist/recorder/toolbar/actions/insert.js +93 -0
- package/dist/recorder/toolbar/actions/pick.d.ts +8 -0
- package/dist/recorder/toolbar/actions/pick.js +26 -0
- package/dist/recorder/toolbar/actions/reply.d.ts +7 -0
- package/dist/recorder/toolbar/actions/reply.js +10 -0
- package/dist/recorder/toolbar/controller-fixture.d.ts +47 -0
- package/dist/recorder/toolbar/controller-fixture.js +129 -0
- package/dist/recorder/toolbar/controller.d.ts +12 -78
- package/dist/recorder/toolbar/controller.js +35 -247
- package/dist/recorder/toolbar/inspect.d.ts +1 -3
- package/dist/recorder/toolbar/inspect.js +2 -2
- package/dist/recorder/toolbar/page-scope.d.ts +19 -0
- package/dist/recorder/toolbar/page-scope.js +43 -0
- package/dist/recorder/toolbar/protocol.d.ts +2 -1
- package/dist/recorder/toolbar/source/attach.d.ts +2 -0
- package/dist/recorder/toolbar/source/attach.js +41 -0
- package/dist/recorder/toolbar/source/dom.d.ts +2 -0
- package/dist/recorder/toolbar/source/dom.js +47 -0
- package/dist/recorder/toolbar/source/drag.d.ts +2 -0
- package/dist/recorder/toolbar/source/drag.js +32 -0
- package/dist/recorder/toolbar/source/events-page.d.ts +2 -0
- package/dist/recorder/toolbar/source/events-page.js +67 -0
- package/dist/recorder/toolbar/source/events-toolbar.d.ts +2 -0
- package/dist/recorder/toolbar/source/events-toolbar.js +35 -0
- package/dist/recorder/toolbar/source/fields.d.ts +2 -0
- package/dist/recorder/toolbar/source/fields.js +44 -0
- package/dist/recorder/toolbar/source/handlers.d.ts +2 -0
- package/dist/recorder/toolbar/source/handlers.js +134 -0
- package/dist/recorder/toolbar/source/names.d.ts +2 -0
- package/dist/recorder/toolbar/source/names.js +12 -0
- package/dist/recorder/toolbar/source/picking.d.ts +2 -0
- package/dist/recorder/toolbar/source/picking.js +72 -0
- package/dist/recorder/toolbar/source/placement.d.ts +2 -0
- package/dist/recorder/toolbar/source/placement.js +45 -0
- package/dist/recorder/toolbar/source/recorder-side.d.ts +2 -0
- package/dist/recorder/toolbar/source/recorder-side.js +54 -0
- package/dist/recorder/toolbar/source/setup.d.ts +2 -0
- package/dist/recorder/toolbar/source/setup.js +50 -0
- package/dist/recorder/toolbar/source/views-panels.d.ts +2 -0
- package/dist/recorder/toolbar/source/views-panels.js +150 -0
- package/dist/recorder/toolbar/source/views-rail.d.ts +2 -0
- package/dist/recorder/toolbar/source/views-rail.js +40 -0
- package/dist/recorder/toolbar/source/views-render.d.ts +2 -0
- package/dist/recorder/toolbar/source/views-render.js +48 -0
- package/dist/recorder/toolbar/source.js +30 -856
- package/dist/recorder/toolbar/types.d.ts +70 -0
- package/dist/recorder/toolbar/types.js +1 -0
- package/dist/recorder/toolbar-bridge.d.ts +43 -0
- package/dist/recorder/toolbar-bridge.js +106 -0
- package/dist/runner/browsers.d.ts +10 -0
- package/dist/runner/browsers.js +12 -0
- package/dist/runner/groups.d.ts +5 -1
- package/dist/runner/groups.js +9 -2
- package/dist/runner/options.d.ts +31 -0
- package/dist/runner/options.js +1 -0
- package/dist/runner/pooled-run.d.ts +43 -0
- package/dist/runner/pooled-run.js +75 -0
- package/dist/runner/ports.d.ts +2 -0
- package/dist/runner/ports.js +2 -0
- package/dist/runner/registry.d.ts +1 -1
- package/dist/runner/registry.js +1 -1
- package/dist/runner/reporter.d.ts +40 -11
- package/dist/runner/reporter.js +69 -16
- package/dist/runner/run-test.d.ts +16 -0
- package/dist/runner/run-test.js +178 -0
- package/dist/runner/run.d.ts +5 -2
- package/dist/runner/run.js +28 -28
- package/dist/runner/select.d.ts +24 -0
- package/dist/runner/select.js +77 -0
- package/dist/runner/test-dir.d.ts +13 -0
- package/dist/runner/test-dir.js +27 -0
- package/dist/runner/test-runner.d.ts +27 -50
- package/dist/runner/test-runner.js +102 -264
- package/dist/runner/worker-pool.d.ts +67 -0
- package/dist/runner/worker-pool.js +275 -0
- package/dist/runner/worker-process.d.ts +6 -0
- package/dist/runner/worker-process.js +54 -0
- package/dist/runner/worker-protocol.d.ts +67 -0
- package/dist/runner/worker-protocol.js +1 -0
- package/dist/runner/worker-session.d.ts +7 -0
- package/dist/runner/worker-session.js +60 -0
- package/dist/runner/worker.d.ts +26 -0
- package/dist/runner/worker.js +121 -0
- package/dist/runner/workers-option.d.ts +15 -0
- package/dist/runner/workers-option.js +22 -0
- package/dist/script/call-function.d.ts +1 -9
- package/dist/script/call-function.js +2 -19
- package/dist/script/element-handle.d.ts +1 -2
- package/dist/script/element-handle.js +1 -2
- package/dist/script/serialize.d.ts +3 -12
- package/dist/script/serialize.js +53 -62
- package/dist/steps/parse/call.d.ts +3 -0
- package/dist/steps/parse/call.js +81 -0
- package/dist/steps/parse/expectation.d.ts +5 -0
- package/dist/steps/parse/expectation.js +71 -0
- package/dist/steps/parse/file-entry.d.ts +4 -0
- package/dist/steps/parse/file-entry.js +74 -0
- package/dist/steps/parse/literals.d.ts +4 -0
- package/dist/steps/parse/literals.js +28 -0
- package/dist/steps/parse/step.d.ts +4 -0
- package/dist/steps/parse/step.js +81 -0
- package/dist/steps/parse.js +2 -323
- package/dist/transport/bidi-connection.d.ts +4 -2
- package/dist/transport/bidi-connection.js +9 -43
- package/dist/transport/log-redaction.d.ts +13 -0
- package/dist/transport/log-redaction.js +51 -0
- package/dist/transport/subscriptions.js +18 -13
- package/dist/transport/unsupported.d.ts +7 -0
- package/dist/transport/unsupported.js +13 -0
- package/dist/types/bidi-modules/browser.d.ts +1 -12
- package/dist/types/bidi-modules/browsing-context.d.ts +8 -18
- package/dist/types/bidi-modules/emulation.d.ts +29 -21
- package/dist/types/bidi-modules/input.d.ts +1 -9
- package/dist/types/bidi-modules/network.d.ts +29 -24
- package/dist/types/bidi-modules/script.d.ts +0 -9
- package/dist/types/bidi-modules/session.d.ts +0 -8
- package/dist/types/bidi-modules/storage.d.ts +0 -5
- package/dist/types/bidi.d.ts +7 -37
- package/dist/types/config.d.ts +17 -3
- package/dist/types/test.d.ts +15 -2
- package/dist/wait/wait-until.d.ts +1 -5
- package/dist/wait/wait-until.js +7 -16
- package/package.json +3 -2
- package/dist/config/default.d.ts +0 -2
- package/dist/config/default.js +0 -3
- package/dist/types/bidi-modules/web-extensions.d.ts +0 -9
- package/dist/types/bidi-protocols/command.d.ts +0 -7
- package/dist/types/bidi-protocols/event.d.ts +0 -17
- /package/dist/{script/helpers.d.ts → locator/helper-realm.d.ts} +0 -0
package/dist/locator/locator.js
CHANGED
|
@@ -1,103 +1,14 @@
|
|
|
1
1
|
import { BiDiError } from "../transport/bidi-error.js";
|
|
2
2
|
import { callFunction, ScriptError } from "../script/call-function.js";
|
|
3
|
-
import { CALL_PROBE, HELPER_SANDBOX, HELPERS_MISSING, } from "../script/helpers.js";
|
|
4
3
|
import { ElementHandle } from "../script/element-handle.js";
|
|
5
4
|
import { takeScreenshot, } from "../browser/screenshot.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { cssSelector, describeChain, describeSelector, labelSelector, roleSelector, testIdSelector, textSelector, toBiDiLocator, xpathSelector, } from "./selector.js";
|
|
14
|
-
export { InvalidSelectorError, UnsupportedOperationError, } from "./selector-errors.js";
|
|
15
|
-
/** Locator types a connection's browser rejected, so they are not tried again on every poll */
|
|
16
|
-
const unsupportedByConnector = new WeakMap();
|
|
17
|
-
function unsupportedTypes(connector) {
|
|
18
|
-
let types = unsupportedByConnector.get(connector);
|
|
19
|
-
if (!types)
|
|
20
|
-
unsupportedByConnector.set(connector, (types = new Set()));
|
|
21
|
-
return types;
|
|
22
|
-
}
|
|
23
|
-
/** What `setInputFiles` needs to know about the element before it sends files */
|
|
24
|
-
const FILE_INPUT_INFO = `(el) => ({
|
|
25
|
-
tag: el.tagName.toLowerCase(),
|
|
26
|
-
type: (el.getAttribute("type") || "").toLowerCase(),
|
|
27
|
-
multiple: Boolean(el.multiple),
|
|
28
|
-
})`;
|
|
29
|
-
const FILE_INPUT_CHECKS = ["attached", "enabled"];
|
|
30
|
-
const DROP_CHECKS = ["attached", "visible"];
|
|
31
|
-
/** The sandbox `dropFiles` builds its files in */
|
|
32
|
-
const DROP_SANDBOX = "samurai-drop";
|
|
33
|
-
const DROP_KEY = `globalThis[Symbol.for("samurai.drop")]`;
|
|
34
|
-
const DROP_INPUT = `() => {
|
|
35
|
-
const input = document.createElement("input");
|
|
36
|
-
input.type = "file";
|
|
37
|
-
input.multiple = true;
|
|
38
|
-
${DROP_KEY} = input;
|
|
39
|
-
return input;
|
|
40
|
-
}`;
|
|
41
|
-
/** Fires dragenter, dragover and drop at the element's centre with the input's files (retyped where asked) */
|
|
42
|
-
const DROP_EVENTS = `(el, types) => {
|
|
43
|
-
const input = ${DROP_KEY};
|
|
44
|
-
delete ${DROP_KEY};
|
|
45
|
-
const data = new DataTransfer();
|
|
46
|
-
Array.from(input.files).forEach((file, i) => {
|
|
47
|
-
data.items.add(types[i] ? new File([file], file.name, { type: types[i], lastModified: file.lastModified }) : file);
|
|
48
|
-
});
|
|
49
|
-
const box = el.getBoundingClientRect();
|
|
50
|
-
const at = { clientX: box.left + box.width / 2, clientY: box.top + box.height / 2 };
|
|
51
|
-
for (const type of ["dragenter", "dragover", "drop"]) {
|
|
52
|
-
el.dispatchEvent(new DragEvent(type, Object.assign({ bubbles: true, cancelable: true, composed: true, dataTransfer: data }, at)));
|
|
53
|
-
}
|
|
54
|
-
}`;
|
|
55
|
-
const SCREENSHOT_CHECKS = ["attached", "visible"];
|
|
56
|
-
const CLICK_CHECKS = [
|
|
57
|
-
"attached",
|
|
58
|
-
"visible",
|
|
59
|
-
"stable",
|
|
60
|
-
"enabled",
|
|
61
|
-
"hit target",
|
|
62
|
-
];
|
|
63
|
-
const FILL_CHECKS = [
|
|
64
|
-
"attached",
|
|
65
|
-
"visible",
|
|
66
|
-
"enabled",
|
|
67
|
-
"editable",
|
|
68
|
-
"hit target",
|
|
69
|
-
];
|
|
70
|
-
const PRESS_CHECKS = ["attached", "visible", "enabled"];
|
|
71
|
-
const ATTACHED_ONLY = ["attached"];
|
|
72
|
-
/** WebDriver code points of the named keys `press()` accepts; any other single character is typed as is */
|
|
73
|
-
const NAMED_KEYS = {
|
|
74
|
-
Backspace: "\uE003",
|
|
75
|
-
Tab: "\uE004",
|
|
76
|
-
Enter: "\uE007",
|
|
77
|
-
Escape: "\uE00C",
|
|
78
|
-
Space: " ",
|
|
79
|
-
ArrowLeft: "\uE012",
|
|
80
|
-
ArrowUp: "\uE013",
|
|
81
|
-
ArrowRight: "\uE014",
|
|
82
|
-
ArrowDown: "\uE015",
|
|
83
|
-
Delete: "\uE017",
|
|
84
|
-
Home: "\uE011",
|
|
85
|
-
End: "\uE010",
|
|
86
|
-
PageUp: "\uE00E",
|
|
87
|
-
PageDown: "\uE00F",
|
|
88
|
-
};
|
|
89
|
-
/** Unicode code point WebDriver uses for the Backspace key */
|
|
90
|
-
const BACKSPACE = "";
|
|
91
|
-
/**
|
|
92
|
-
* Chrome answers a command sent while the page swaps documents with an unknown error that says the context
|
|
93
|
-
* is gone for the moment; the same call a moment later works.
|
|
94
|
-
*/
|
|
95
|
-
const DOCUMENT_SWAP = /Cannot find context with specified id|execution contexts? (was |were )?(cleared|destroyed)/i;
|
|
96
|
-
function isDocumentSwap(err) {
|
|
97
|
-
return (err instanceof BiDiError &&
|
|
98
|
-
err.code === "unknown error" &&
|
|
99
|
-
DOCUMENT_SWAP.test(err.message));
|
|
100
|
-
}
|
|
5
|
+
import { probeState, waitForActionable, waitForState, } from "./actionability.js";
|
|
6
|
+
import { ChainResolver } from "./chain-resolver.js";
|
|
7
|
+
import { dropFiles, setInputFiles, } from "./file-actions.js";
|
|
8
|
+
import { describeType, } from "./element-state.js";
|
|
9
|
+
import { clickRect, DEFAULT_PAUSE_MS, NAMED_KEYS, pause, pressKey, typeText, } from "./input-actions.js";
|
|
10
|
+
import { CLICK_CHECKS, FILL_CHECKS, PRESS_CHECKS, SCREENSHOT_CHECKS, ATTACHED_ONLY, } from "./page-scripts.js";
|
|
11
|
+
import { cssSelector, describeChain, describeSelector, labelSelector, roleSelector, testIdSelector, textSelector, xpathSelector, } from "./selector.js";
|
|
101
12
|
export default class Locator {
|
|
102
13
|
/**
|
|
103
14
|
* `selector` is an xpath, or a chain of selector steps that each search inside the previous step's matches.
|
|
@@ -112,6 +23,27 @@ export default class Locator {
|
|
|
112
23
|
this.contextId = contextId;
|
|
113
24
|
this.helpers = helpers;
|
|
114
25
|
this.alternatives = alternatives;
|
|
26
|
+
const locator = this;
|
|
27
|
+
this.actionability = {
|
|
28
|
+
resolveOne: () => this.resolveOne(),
|
|
29
|
+
call: (fn, args, sandbox) => this.call(fn, args, sandbox),
|
|
30
|
+
get selector() {
|
|
31
|
+
return locator.selector;
|
|
32
|
+
},
|
|
33
|
+
helpers,
|
|
34
|
+
};
|
|
35
|
+
this.elementContext = {
|
|
36
|
+
connector: biDiConnector,
|
|
37
|
+
contextId,
|
|
38
|
+
get selector() {
|
|
39
|
+
return locator.selector;
|
|
40
|
+
},
|
|
41
|
+
call: (fn, args, sandbox) => this.call(fn, args, sandbox),
|
|
42
|
+
waitForActionable: (action, checks, probe, options) => this.waitForActionable(action, checks, probe, options),
|
|
43
|
+
requireElement: () => this.requireElement(),
|
|
44
|
+
noElementError: () => this.noElementError(),
|
|
45
|
+
};
|
|
46
|
+
this.resolver = new ChainResolver(biDiConnector, contextId, (fn, args, sandbox) => this.call(fn, args, sandbox));
|
|
115
47
|
}
|
|
116
48
|
/**
|
|
117
49
|
* The description of this locator used in error messages, e.g. `//form >> role=button[name="Save"]`;
|
|
@@ -190,7 +122,7 @@ export default class Locator {
|
|
|
190
122
|
*/
|
|
191
123
|
async resolve(max) {
|
|
192
124
|
this.matched = undefined;
|
|
193
|
-
const own = await this.resolveChain(max);
|
|
125
|
+
const own = await this.resolver.resolveChain(this.chain, max);
|
|
194
126
|
if (own.length > 0) {
|
|
195
127
|
this.matched = { index: 0, selector: describeChain(this.chain) };
|
|
196
128
|
return own;
|
|
@@ -206,7 +138,7 @@ export default class Locator {
|
|
|
206
138
|
}
|
|
207
139
|
/** The primary or the first fallback that currently matches, without its own fallbacks */
|
|
208
140
|
async firstMatching() {
|
|
209
|
-
if ((await this.resolveChain(1)).length > 0)
|
|
141
|
+
if ((await this.resolver.resolveChain(this.chain, 1)).length > 0)
|
|
210
142
|
return this.withoutFallbacks();
|
|
211
143
|
for (const alternative of this.alternatives) {
|
|
212
144
|
const found = await alternative.firstMatching();
|
|
@@ -218,90 +150,9 @@ export default class Locator {
|
|
|
218
150
|
withoutFallbacks() {
|
|
219
151
|
return new Locator(this.chain, this.biDiConnector, this.contextId, this.helpers);
|
|
220
152
|
}
|
|
221
|
-
async resolveChain(max) {
|
|
222
|
-
let nodes;
|
|
223
|
-
for (const [index, selector] of this.chain.entries()) {
|
|
224
|
-
if (selector.kind === "nth") {
|
|
225
|
-
const picked = nodes?.[selector.index];
|
|
226
|
-
nodes = picked ? [picked] : [];
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
try {
|
|
230
|
-
nodes = await this.locateStep(selector, index > 0, nodes, index === this.chain.length - 1 ? max : undefined);
|
|
231
|
-
}
|
|
232
|
-
catch (err) {
|
|
233
|
-
// The page is swapping documents: nothing matches right now, and the waits that poll try again
|
|
234
|
-
if (isDocumentSwap(err))
|
|
235
|
-
return [];
|
|
236
|
-
throw err;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (nodes.length === 0)
|
|
240
|
-
return [];
|
|
241
|
-
}
|
|
242
|
-
return nodes ?? [];
|
|
243
|
-
}
|
|
244
153
|
async resolveOne() {
|
|
245
154
|
return (await this.resolve(1))[0] ?? null;
|
|
246
155
|
}
|
|
247
|
-
async locateStep(selector, scoped, startNodes, max) {
|
|
248
|
-
if (selector.kind === "label")
|
|
249
|
-
return this.locateInPage(LABEL_LOCATE, selector, startNodes, max);
|
|
250
|
-
if (selector.kind === "text" &&
|
|
251
|
-
unsupportedTypes(this.biDiConnector).has("innerText")) {
|
|
252
|
-
return this.locateByText(selector, startNodes, max);
|
|
253
|
-
}
|
|
254
|
-
try {
|
|
255
|
-
return await this.locateNatively(selector, scoped, startNodes, max);
|
|
256
|
-
}
|
|
257
|
-
catch (err) {
|
|
258
|
-
if (err instanceof BiDiError && err.code === "invalid selector") {
|
|
259
|
-
throw new InvalidSelectorError(describeSelector(selector, scoped), err.message);
|
|
260
|
-
}
|
|
261
|
-
if (err instanceof BiDiError && err.code === "unsupported operation") {
|
|
262
|
-
if (selector.kind === "text") {
|
|
263
|
-
// The browser has no innerText locator (Firefox): remember that and search in the page instead
|
|
264
|
-
unsupportedTypes(this.biDiConnector).add("innerText");
|
|
265
|
-
return this.locateByText(selector, startNodes, max);
|
|
266
|
-
}
|
|
267
|
-
throw new UnsupportedOperationError(`${describeSelector(selector, scoped)}: this kind of locator is not supported by the browser (${err.message})`);
|
|
268
|
-
}
|
|
269
|
-
throw err;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
async locateNatively(selector, scoped, startNodes, max) {
|
|
273
|
-
const { nodes } = await this.biDiConnector.send("browsingContext.locateNodes", {
|
|
274
|
-
context: this.contextId,
|
|
275
|
-
locator: toBiDiLocator(selector, scoped),
|
|
276
|
-
...(max !== undefined && { maxNodeCount: max }),
|
|
277
|
-
// Only the references are needed; skip serializing the matched subtrees
|
|
278
|
-
serializationOptions: { maxDomDepth: 0 },
|
|
279
|
-
...(startNodes && {
|
|
280
|
-
startNodes: startNodes.map(({ sharedId }) => ({ sharedId })),
|
|
281
|
-
}),
|
|
282
|
-
});
|
|
283
|
-
const found = new Map();
|
|
284
|
-
for (const node of nodes) {
|
|
285
|
-
if (node.sharedId !== undefined && !found.has(node.sharedId)) {
|
|
286
|
-
found.set(node.sharedId, new ElementHandle(node.sharedId, node.handle));
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return [...found.values()];
|
|
290
|
-
}
|
|
291
|
-
/** Text search done in the page, for browsers without BiDi's innerText locator */
|
|
292
|
-
locateByText(selector, startNodes, max) {
|
|
293
|
-
return this.locateInPage(TEXT_LOCATE, selector, startNodes, max);
|
|
294
|
-
}
|
|
295
|
-
/** Runs a page-side search `(starts, value, match, ignoreCase) => elements` */
|
|
296
|
-
async locateInPage(fn, selector, startNodes, max) {
|
|
297
|
-
const found = await this.call(fn, [
|
|
298
|
-
startNodes ?? [],
|
|
299
|
-
selector.value,
|
|
300
|
-
selector.match,
|
|
301
|
-
selector.ignoreCase,
|
|
302
|
-
]);
|
|
303
|
-
return max === undefined ? found : found.slice(0, max);
|
|
304
|
-
}
|
|
305
156
|
/** Calls `fn` in the page; a throwing function becomes a "Failed to locate element" error */
|
|
306
157
|
async call(fn, args, sandbox) {
|
|
307
158
|
try {
|
|
@@ -338,120 +189,32 @@ export default class Locator {
|
|
|
338
189
|
async callOnExistingElement(body) {
|
|
339
190
|
const missing = Symbol("missing");
|
|
340
191
|
const result = await this.callOnElement(missing, body);
|
|
341
|
-
if (result === missing)
|
|
342
|
-
throw
|
|
343
|
-
}
|
|
192
|
+
if (result === missing)
|
|
193
|
+
throw this.noElementError();
|
|
344
194
|
return result;
|
|
345
195
|
}
|
|
346
|
-
/**
|
|
347
|
-
|
|
196
|
+
/** The error for an operation that needs an element when none matches */
|
|
197
|
+
noElementError() {
|
|
198
|
+
return new Error(`Failed to locate element ${this.selector}. Details: no element matches`);
|
|
199
|
+
}
|
|
200
|
+
/** The element the locator matches now; throws when none does */
|
|
201
|
+
async requireElement() {
|
|
348
202
|
const el = await this.resolveOne();
|
|
349
203
|
if (!el)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return parseElementState(await this.callProbe(el, options));
|
|
353
|
-
}
|
|
354
|
-
catch (err) {
|
|
355
|
-
// The document changed between the search and the probe
|
|
356
|
-
if ((err instanceof BiDiError && err.code === "no such node") ||
|
|
357
|
-
isDocumentSwap(err))
|
|
358
|
-
return DETACHED_STATE;
|
|
359
|
-
throw err;
|
|
360
|
-
}
|
|
204
|
+
throw this.noElementError();
|
|
205
|
+
return el;
|
|
361
206
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (!this.helpers)
|
|
365
|
-
return this.call(PROBE_ELEMENT, args);
|
|
366
|
-
await this.helpers.ensureInstalled();
|
|
367
|
-
let result = await this.call(CALL_PROBE, args, HELPER_SANDBOX);
|
|
368
|
-
if (result === HELPERS_MISSING) {
|
|
369
|
-
// A document got past the registration (e.g. created while it was being set up): install again once
|
|
370
|
-
await this.helpers.reinstall();
|
|
371
|
-
result = await this.call(CALL_PROBE, args, HELPER_SANDBOX);
|
|
372
|
-
if (result === HELPERS_MISSING) {
|
|
373
|
-
throw new ScriptError("the samurai helpers are missing in the page after reinstalling", "probeElement");
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
return result;
|
|
207
|
+
probeState(options) {
|
|
208
|
+
return probeState(this.actionability, options);
|
|
377
209
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
* Throws ActionTimeoutError describing the last probe on timeout.
|
|
381
|
-
*/
|
|
382
|
-
async waitForActionable(action, checks, probe, options) {
|
|
383
|
-
const timeout = await resolveTimeout(options?.timeout);
|
|
384
|
-
// Stability compares two probes; with timeout 0 there is only one
|
|
385
|
-
const required = options?.force
|
|
386
|
-
? ATTACHED_ONLY
|
|
387
|
-
: timeout === 0
|
|
388
|
-
? checks.filter((check) => check !== "stable")
|
|
389
|
-
: checks;
|
|
390
|
-
let lastChecks;
|
|
391
|
-
try {
|
|
392
|
-
return await waitUntil(() => this.probeState(probe), (current, previous) => {
|
|
393
|
-
lastChecks = evaluateChecks(required, current, previous);
|
|
394
|
-
return allPass(lastChecks);
|
|
395
|
-
}, { timeout });
|
|
396
|
-
}
|
|
397
|
-
catch (err) {
|
|
398
|
-
if (!(err instanceof WaitTimeoutError))
|
|
399
|
-
throw err;
|
|
400
|
-
const last = err.last;
|
|
401
|
-
throw new ActionTimeoutError({
|
|
402
|
-
action,
|
|
403
|
-
selector: this.selector,
|
|
404
|
-
timeout,
|
|
405
|
-
reason: !last
|
|
406
|
-
? "unreadable"
|
|
407
|
-
: last.attached
|
|
408
|
-
? "not-actionable"
|
|
409
|
-
: "not-attached",
|
|
410
|
-
checks: lastChecks,
|
|
411
|
-
coveredBy: lastChecks?.["hit target"] === "fail"
|
|
412
|
-
? (last?.hitTarget ?? undefined)
|
|
413
|
-
: undefined,
|
|
414
|
-
});
|
|
415
|
-
}
|
|
210
|
+
waitForActionable(action, checks, probe, options) {
|
|
211
|
+
return waitForActionable(this.actionability, action, checks, probe, options);
|
|
416
212
|
}
|
|
417
213
|
/** Waits until the element is attached, visible, stable, enabled and not covered, then clicks its centre with real pointer input */
|
|
418
214
|
async click(options) {
|
|
419
215
|
const state = await this.waitForActionable("click", CLICK_CHECKS, { scroll: true, hitTest: true }, options);
|
|
420
216
|
// "attached" is always required, so the box is present
|
|
421
|
-
await this.
|
|
422
|
-
}
|
|
423
|
-
/** Simulate a click event on the center off a page element.
|
|
424
|
-
* This is mostly used internally to focus input elements when using the `fill()` method
|
|
425
|
-
* @example
|
|
426
|
-
* const container = page.locator("div[@id=['clickable-element']");
|
|
427
|
-
* const rect = await container.getBoundingClientRect();
|
|
428
|
-
* await container.clickRect(rect);
|
|
429
|
-
*/
|
|
430
|
-
async clickRect(rect) {
|
|
431
|
-
await this.biDiConnector.send("input.performActions", {
|
|
432
|
-
context: this.contextId,
|
|
433
|
-
actions: [
|
|
434
|
-
{
|
|
435
|
-
type: "pointer",
|
|
436
|
-
id: "samurai-pointer",
|
|
437
|
-
actions: [
|
|
438
|
-
{
|
|
439
|
-
type: "pointerMove",
|
|
440
|
-
x: rect.x + rect.width / 2,
|
|
441
|
-
y: rect.y + rect.height / 2,
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
type: "pointerDown",
|
|
445
|
-
button: 0,
|
|
446
|
-
},
|
|
447
|
-
{
|
|
448
|
-
type: "pointerUp",
|
|
449
|
-
button: 0,
|
|
450
|
-
},
|
|
451
|
-
],
|
|
452
|
-
},
|
|
453
|
-
],
|
|
454
|
-
});
|
|
217
|
+
await clickRect(this.biDiConnector, this.contextId, state.box);
|
|
455
218
|
}
|
|
456
219
|
/**
|
|
457
220
|
* Sets the files of an `<input type=file>`; `[]` clears it. Paths are relative to the project folder (absolute
|
|
@@ -462,35 +225,7 @@ export default class Locator {
|
|
|
462
225
|
* await page.getByLabel("Upload").setInputFiles(generatedFile({ name: "huge.pdf", size: "60 MB" }));
|
|
463
226
|
*/
|
|
464
227
|
async setInputFiles(files, options) {
|
|
465
|
-
|
|
466
|
-
await this.waitForActionable("setInputFiles", FILE_INPUT_CHECKS, { scroll: false, hitTest: false }, options?.timeout === undefined ? undefined : { timeout: options.timeout });
|
|
467
|
-
const missing = () => new Error(`Failed to locate element ${this.selector}. Details: no element matches`);
|
|
468
|
-
const el = await this.resolveOne();
|
|
469
|
-
if (!el)
|
|
470
|
-
throw missing();
|
|
471
|
-
let info;
|
|
472
|
-
try {
|
|
473
|
-
info = await this.call(FILE_INPUT_INFO, [el]);
|
|
474
|
-
}
|
|
475
|
-
catch (err) {
|
|
476
|
-
if (err instanceof BiDiError && err.code === "no such node")
|
|
477
|
-
throw missing();
|
|
478
|
-
throw err;
|
|
479
|
-
}
|
|
480
|
-
if (info.tag !== "input" || info.type !== "file") {
|
|
481
|
-
const found = info.tag === "input"
|
|
482
|
-
? `<input type="${info.type || "text"}">`
|
|
483
|
-
: `<${info.tag}>`;
|
|
484
|
-
throw new Error(`setInputFiles(): ${this.selector} is not an <input type=file> (found ${found})`);
|
|
485
|
-
}
|
|
486
|
-
if (paths.length > 1 && !info.multiple) {
|
|
487
|
-
throw new Error(`setInputFiles(): ${this.selector} does not accept multiple files`);
|
|
488
|
-
}
|
|
489
|
-
await this.biDiConnector.send("input.setFiles", {
|
|
490
|
-
context: this.contextId,
|
|
491
|
-
element: { sharedId: el.sharedId },
|
|
492
|
-
files: paths,
|
|
493
|
-
});
|
|
228
|
+
await setInputFiles(this.elementContext, files, options);
|
|
494
229
|
}
|
|
495
230
|
/**
|
|
496
231
|
* Drops files on the element, as dragging them from the desktop does: `dragenter`, `dragover` and `drop`
|
|
@@ -501,21 +236,7 @@ export default class Locator {
|
|
|
501
236
|
* await page.getByTestId("dropzone").dropFiles(["files/a.png", "files/b.png"]);
|
|
502
237
|
*/
|
|
503
238
|
async dropFiles(files, options) {
|
|
504
|
-
|
|
505
|
-
const paths = await resolveFileInputs(list, "dropFiles");
|
|
506
|
-
const types = list.map((file) => isGeneratedFile(file) ? (file.type ?? "") : "");
|
|
507
|
-
await this.waitForActionable("dropFiles", DROP_CHECKS, { scroll: true, hitTest: false }, options?.timeout === undefined ? undefined : { timeout: options.timeout });
|
|
508
|
-
const el = await this.resolveOne();
|
|
509
|
-
if (!el)
|
|
510
|
-
throw new Error(`Failed to locate element ${this.selector}. Details: no element matches`);
|
|
511
|
-
// A file input of our own, made in a sandbox and never attached: the page doesn't see it
|
|
512
|
-
const input = await this.call(DROP_INPUT, [], DROP_SANDBOX);
|
|
513
|
-
await this.biDiConnector.send("input.setFiles", {
|
|
514
|
-
context: this.contextId,
|
|
515
|
-
element: { sharedId: input.sharedId },
|
|
516
|
-
files: paths,
|
|
517
|
-
});
|
|
518
|
-
await this.call(DROP_EVENTS, [el, types], DROP_SANDBOX);
|
|
239
|
+
await dropFiles(this.elementContext, files, options);
|
|
519
240
|
}
|
|
520
241
|
/**
|
|
521
242
|
* Takes a screenshot of just this element, after scrolling it into view and waiting until it is attached and
|
|
@@ -526,10 +247,7 @@ export default class Locator {
|
|
|
526
247
|
async screenshot(options = {}) {
|
|
527
248
|
const { timeout, ...capture } = options;
|
|
528
249
|
await this.waitForActionable("screenshot", SCREENSHOT_CHECKS, { scroll: true, hitTest: false }, timeout === undefined ? undefined : { timeout });
|
|
529
|
-
const el = await this.
|
|
530
|
-
if (!el) {
|
|
531
|
-
throw new Error(`Failed to locate element ${this.selector}. Details: no element matches`);
|
|
532
|
-
}
|
|
250
|
+
const el = await this.requireElement();
|
|
533
251
|
return takeScreenshot(this.biDiConnector, this.contextId, capture, {
|
|
534
252
|
sharedId: el.sharedId,
|
|
535
253
|
});
|
|
@@ -540,30 +258,8 @@ export default class Locator {
|
|
|
540
258
|
await this.callOnExistingElement(`el.focus();`);
|
|
541
259
|
}
|
|
542
260
|
/** Waits until the element reaches `state` (default "visible"); does not scroll */
|
|
543
|
-
async waitFor(
|
|
544
|
-
|
|
545
|
-
const reached = {
|
|
546
|
-
attached: (current) => current.attached,
|
|
547
|
-
detached: (current) => !current.attached,
|
|
548
|
-
visible: (current) => current.attached && current.visible,
|
|
549
|
-
hidden: (current) => !current.attached || !current.visible,
|
|
550
|
-
};
|
|
551
|
-
try {
|
|
552
|
-
await waitUntil(() => this.probeState({ scroll: false, hitTest: false }), reached[state], {
|
|
553
|
-
timeout: resolved,
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
catch (err) {
|
|
557
|
-
if (!(err instanceof WaitTimeoutError))
|
|
558
|
-
throw err;
|
|
559
|
-
throw new ActionTimeoutError({
|
|
560
|
-
action: "waitFor",
|
|
561
|
-
selector: this.selector,
|
|
562
|
-
timeout: resolved,
|
|
563
|
-
reason: err.last === undefined ? "unreadable" : "wrong-state",
|
|
564
|
-
state,
|
|
565
|
-
});
|
|
566
|
-
}
|
|
261
|
+
async waitFor(options = {}) {
|
|
262
|
+
await waitForState(this.actionability, options);
|
|
567
263
|
}
|
|
568
264
|
/** Returns the browser's getBoundingClientRect result for the given element */
|
|
569
265
|
async getBoundingClientRect() {
|
|
@@ -576,34 +272,22 @@ export default class Locator {
|
|
|
576
272
|
}
|
|
577
273
|
return rect;
|
|
578
274
|
}
|
|
579
|
-
stringOrNull(value) {
|
|
580
|
-
if (typeof value === "string")
|
|
581
|
-
return value;
|
|
582
|
-
if (value === null)
|
|
583
|
-
return null;
|
|
584
|
-
throw new Error(`Expected a string or null but received "${describeType(value)}"`);
|
|
585
|
-
}
|
|
586
275
|
/** Whether the element exists, has a non-empty box and is not hidden by CSS */
|
|
587
276
|
async isVisible() {
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
const style = getComputedStyle(el);
|
|
591
|
-
return rect.width > 0 && rect.height > 0 &&
|
|
592
|
-
style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
|
|
593
|
-
`);
|
|
594
|
-
return result === true;
|
|
277
|
+
const state = await this.probeState({ scroll: false, hitTest: false });
|
|
278
|
+
return state.attached && state.visible;
|
|
595
279
|
}
|
|
596
280
|
/** Trimmed text content, or null when the element is missing */
|
|
597
281
|
async textContent() {
|
|
598
|
-
return
|
|
282
|
+
return stringOrNull(await this.callOnElement(null, `return el.textContent.trim();`));
|
|
599
283
|
}
|
|
600
284
|
/** Value of an input/textarea/select, or null when missing */
|
|
601
285
|
async inputValue() {
|
|
602
|
-
return
|
|
286
|
+
return stringOrNull(await this.callOnElement(null, `return typeof el.value === "string" ? el.value : null;`));
|
|
603
287
|
}
|
|
604
288
|
/** Attribute value, or null when the element or attribute is missing */
|
|
605
289
|
async getAttribute(name) {
|
|
606
|
-
return
|
|
290
|
+
return stringOrNull(await this.callOnElement(null, `return el.getAttribute(name);`, ["name"], [name]));
|
|
607
291
|
}
|
|
608
292
|
/** References to the elements currently matching, in document order. Internal: for tooling that identifies elements */
|
|
609
293
|
async elements() {
|
|
@@ -624,22 +308,8 @@ export default class Locator {
|
|
|
624
308
|
return state.attached && state.editable;
|
|
625
309
|
}
|
|
626
310
|
/** Wait for certain time (ms). Default: 300ms */
|
|
627
|
-
async wait(duration =
|
|
628
|
-
await this.biDiConnector.
|
|
629
|
-
context: this.contextId,
|
|
630
|
-
actions: [
|
|
631
|
-
{
|
|
632
|
-
type: "none",
|
|
633
|
-
id: "samurai-sleep",
|
|
634
|
-
actions: [
|
|
635
|
-
{
|
|
636
|
-
type: "pause",
|
|
637
|
-
duration,
|
|
638
|
-
},
|
|
639
|
-
],
|
|
640
|
-
},
|
|
641
|
-
],
|
|
642
|
-
});
|
|
311
|
+
async wait(duration = DEFAULT_PAUSE_MS) {
|
|
312
|
+
await pause(this.biDiConnector, this.contextId, duration);
|
|
643
313
|
}
|
|
644
314
|
/**
|
|
645
315
|
* Waits until the element is attached, visible and enabled, focuses it and presses `key` with real keyboard input.
|
|
@@ -655,50 +325,24 @@ export default class Locator {
|
|
|
655
325
|
}
|
|
656
326
|
await this.waitForActionable("press", PRESS_CHECKS, { scroll: true, hitTest: false }, options);
|
|
657
327
|
await this.callOnElement(undefined, `el.focus();`);
|
|
658
|
-
await this.biDiConnector.
|
|
659
|
-
context: this.contextId,
|
|
660
|
-
actions: [
|
|
661
|
-
{
|
|
662
|
-
type: "key",
|
|
663
|
-
id: "samurai-keyboard",
|
|
664
|
-
actions: [
|
|
665
|
-
{ type: "keyDown", value: code },
|
|
666
|
-
{ type: "keyUp", value: code },
|
|
667
|
-
],
|
|
668
|
-
},
|
|
669
|
-
],
|
|
670
|
-
});
|
|
328
|
+
await pressKey(this.biDiConnector, this.contextId, code);
|
|
671
329
|
}
|
|
672
330
|
/** Waits until the field is editable and not covered, then replaces its value by typing `value`; an empty string clears it */
|
|
673
331
|
async fill(value, options) {
|
|
674
332
|
const state = await this.waitForActionable("fill", FILL_CHECKS, { scroll: true, hitTest: true }, options);
|
|
675
333
|
// Typing over the selected existing value replaces it; Backspace clears it for an empty value
|
|
676
|
-
const keys = value === "" ?
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
type: "keyDown",
|
|
680
|
-
value: char,
|
|
681
|
-
},
|
|
682
|
-
{
|
|
683
|
-
type: "keyUp",
|
|
684
|
-
value: char,
|
|
685
|
-
},
|
|
686
|
-
]);
|
|
687
|
-
await this.clickRect(state.box);
|
|
334
|
+
const keys = value === "" ? NAMED_KEYS.Backspace : value;
|
|
335
|
+
await clickRect(this.biDiConnector, this.contextId, state.box);
|
|
336
|
+
// Lets the focus the click caused (and the page's focus handlers) settle before the text is selected and typed over
|
|
688
337
|
await this.wait();
|
|
689
338
|
await this.callOnElement(undefined, `if (typeof el.select === "function") el.select();`);
|
|
690
|
-
await this.biDiConnector.
|
|
691
|
-
context: this.contextId,
|
|
692
|
-
actions: [
|
|
693
|
-
{
|
|
694
|
-
actions,
|
|
695
|
-
type: "key",
|
|
696
|
-
id: "samurai-keyboard",
|
|
697
|
-
},
|
|
698
|
-
],
|
|
699
|
-
});
|
|
339
|
+
await typeText(this.biDiConnector, this.contextId, keys);
|
|
700
340
|
}
|
|
701
341
|
}
|
|
702
|
-
function
|
|
703
|
-
|
|
342
|
+
function stringOrNull(value) {
|
|
343
|
+
if (typeof value === "string")
|
|
344
|
+
return value;
|
|
345
|
+
if (value === null)
|
|
346
|
+
return null;
|
|
347
|
+
throw new Error(`Expected a string or null but received "${describeType(value)}"`);
|
|
704
348
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CheckName } from "./element-state.js";
|
|
2
|
+
/** Page-side source strings and the actionability checks each action waits for */
|
|
3
|
+
/** What `setInputFiles` needs to know about the element before it sends files */
|
|
4
|
+
export declare const FILE_INPUT_INFO = "(el) => ({\n tag: el.tagName.toLowerCase(),\n type: (el.getAttribute(\"type\") || \"\").toLowerCase(),\n multiple: Boolean(el.multiple),\n})";
|
|
5
|
+
export declare const FILE_INPUT_CHECKS: readonly CheckName[];
|
|
6
|
+
export declare const DROP_CHECKS: readonly CheckName[];
|
|
7
|
+
/** The sandbox `dropFiles` builds its files in */
|
|
8
|
+
export declare const DROP_SANDBOX = "samurai-drop";
|
|
9
|
+
export declare const DROP_INPUT = "() => {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.multiple = true;\n globalThis[Symbol.for(\"samurai.drop\")] = input;\n return input;\n}";
|
|
10
|
+
/** Fires dragenter, dragover and drop at the element's centre with the input's files (retyped where asked) */
|
|
11
|
+
export declare const DROP_EVENTS = "(el, types) => {\n const input = globalThis[Symbol.for(\"samurai.drop\")];\n delete globalThis[Symbol.for(\"samurai.drop\")];\n const data = new DataTransfer();\n Array.from(input.files).forEach((file, i) => {\n data.items.add(types[i] ? new File([file], file.name, { type: types[i], lastModified: file.lastModified }) : file);\n });\n const box = el.getBoundingClientRect();\n const at = { clientX: box.left + box.width / 2, clientY: box.top + box.height / 2 };\n for (const type of [\"dragenter\", \"dragover\", \"drop\"]) {\n el.dispatchEvent(new DragEvent(type, Object.assign({ bubbles: true, cancelable: true, composed: true, dataTransfer: data }, at)));\n }\n}";
|
|
12
|
+
export declare const SCREENSHOT_CHECKS: readonly CheckName[];
|
|
13
|
+
export declare const CLICK_CHECKS: readonly CheckName[];
|
|
14
|
+
export declare const FILL_CHECKS: readonly CheckName[];
|
|
15
|
+
export declare const PRESS_CHECKS: readonly CheckName[];
|
|
16
|
+
export declare const ATTACHED_ONLY: readonly CheckName[];
|