@itmetsam/samurai-framework 0.21.5 → 0.22.1
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/dist/api.d.ts +8 -3
- package/dist/api.js +1 -0
- package/dist/browser/browser-context.d.ts +18 -6
- package/dist/browser/browser-context.js +25 -7
- package/dist/browser/browser.d.ts +4 -4
- package/dist/browser/browser.js +11 -8
- package/dist/browser/chrome-launch.js +9 -1
- package/dist/browser/context-cookie-store.d.ts +17 -0
- package/dist/browser/context-cookie-store.js +90 -0
- package/dist/browser/index.d.ts +3 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/keyboard.d.ts +35 -0
- package/dist/browser/keyboard.js +83 -0
- package/dist/browser/mouse.d.ts +42 -0
- package/dist/browser/mouse.js +66 -0
- package/dist/browser/page-content.d.ts +4 -0
- package/dist/browser/page-content.js +12 -0
- package/dist/browser/page-events.d.ts +11 -0
- package/dist/browser/page-events.js +23 -0
- package/dist/browser/page-logging.d.ts +24 -0
- package/dist/browser/page-logging.js +43 -0
- package/dist/browser/page-navigation.d.ts +0 -2
- package/dist/browser/page-navigation.js +1 -6
- package/dist/browser/page-settings.d.ts +1 -1
- package/dist/browser/page-viewport.d.ts +21 -0
- package/dist/browser/page-viewport.js +33 -0
- package/dist/browser/page.d.ts +23 -16
- package/dist/browser/page.js +48 -71
- package/dist/browser/screenshot.d.ts +6 -0
- package/dist/browser/screenshot.js +14 -0
- package/dist/browser/storage-state.d.ts +0 -2
- package/dist/browser/storage-state.js +0 -20
- package/dist/browser/use-applier.d.ts +16 -5
- package/dist/browser/use-applier.js +44 -17
- package/dist/browser/use-commands.d.ts +36 -0
- package/dist/browser/use-commands.js +120 -0
- package/dist/cli/args.d.ts +8 -4
- package/dist/cli/args.js +8 -5
- package/dist/cli/console-reporter.js +5 -1
- package/dist/cli/main.js +1 -0
- package/dist/cli/usage.d.ts +1 -1
- package/dist/cli/usage.js +4 -1
- package/dist/config/base-url.d.ts +4 -0
- package/dist/config/base-url.js +6 -0
- package/dist/config/config.js +7 -0
- package/dist/config/fixture-use.d.ts +19 -0
- package/dist/config/fixture-use.js +44 -0
- package/dist/config/fully-parallel.d.ts +3 -0
- package/dist/config/fully-parallel.js +6 -0
- package/dist/config/group-use.d.ts +10 -0
- package/dist/config/group-use.js +51 -0
- package/dist/config/resolved-config.d.ts +2 -0
- package/dist/config/resolved-config.js +3 -0
- package/dist/config/storage-state-file.d.ts +3 -0
- package/dist/config/storage-state-file.js +20 -0
- package/dist/config/use-options.d.ts +3 -1
- package/dist/config/use-options.js +74 -14
- package/dist/config/web-server-options.d.ts +7 -0
- package/dist/config/web-server-options.js +111 -0
- package/dist/locator/check-actions.js +1 -1
- package/dist/locator/drag-actions.d.ts +17 -0
- package/dist/locator/drag-actions.js +80 -0
- package/dist/locator/element-actions.d.ts +10 -4
- package/dist/locator/element-actions.js +23 -16
- package/dist/locator/element-state.d.ts +5 -0
- package/dist/locator/element-state.js +2 -1
- package/dist/locator/event-actions.d.ts +10 -0
- package/dist/locator/event-actions.js +81 -0
- package/dist/locator/input-actions.d.ts +2 -11
- package/dist/locator/input-actions.js +3 -56
- package/dist/locator/key-actions.d.ts +11 -0
- package/dist/locator/key-actions.js +52 -0
- package/dist/locator/key-map.d.ts +16 -0
- package/dist/locator/key-map.js +76 -0
- package/dist/locator/locator.d.ts +35 -5
- package/dist/locator/locator.js +70 -16
- package/dist/locator/page-scripts.d.ts +2 -0
- package/dist/locator/page-scripts.js +7 -0
- package/dist/locator/pointer-actions.d.ts +44 -0
- package/dist/locator/pointer-actions.js +117 -0
- package/dist/locator/pointer-element-actions.d.ts +21 -0
- package/dist/locator/pointer-element-actions.js +42 -0
- package/dist/request/api-request-context.d.ts +65 -0
- package/dist/request/api-request-context.js +198 -0
- package/dist/request/api-response.d.ts +34 -0
- package/dist/request/api-response.js +57 -0
- package/dist/request/cookie-jar.d.ts +48 -0
- package/dist/request/cookie-jar.js +164 -0
- package/dist/request/http-transport.d.ts +31 -0
- package/dist/request/http-transport.js +113 -0
- package/dist/request/redirect.d.ts +10 -0
- package/dist/request/redirect.js +21 -0
- package/dist/request/request-body.d.ts +27 -0
- package/dist/request/request-body.js +71 -0
- package/dist/request/request.d.ts +16 -0
- package/dist/request/request.js +20 -0
- package/dist/runner/all-hooks.d.ts +7 -1
- package/dist/runner/all-hooks.js +17 -1
- package/dist/runner/fixture-defs.d.ts +2 -1
- package/dist/runner/fixture-defs.js +52 -18
- package/dist/runner/fixture-meta.d.ts +20 -0
- package/dist/runner/fixture-meta.js +109 -0
- package/dist/runner/fixture-options.d.ts +8 -0
- package/dist/runner/fixture-options.js +35 -0
- package/dist/runner/fixture-outputs.d.ts +11 -0
- package/dist/runner/fixture-outputs.js +28 -0
- package/dist/runner/fixture-registry.d.ts +35 -0
- package/dist/runner/fixture-registry.js +73 -0
- package/dist/runner/fixture-scope.d.ts +39 -9
- package/dist/runner/fixture-scope.js +139 -70
- package/dist/runner/fixture-start.d.ts +27 -0
- package/dist/runner/fixture-start.js +76 -0
- package/dist/runner/fixture-use.d.ts +23 -0
- package/dist/runner/fixture-use.js +64 -0
- package/dist/runner/fixture-worker.d.ts +26 -0
- package/dist/runner/fixture-worker.js +46 -0
- package/dist/runner/fixtures.d.ts +4 -1
- package/dist/runner/fixtures.js +3 -2
- package/dist/runner/global-hooks.d.ts +13 -0
- package/dist/runner/global-hooks.js +66 -0
- package/dist/runner/options.d.ts +13 -2
- package/dist/runner/output-tail.d.ts +14 -0
- package/dist/runner/output-tail.js +30 -0
- package/dist/runner/pool-schedule.d.ts +22 -0
- package/dist/runner/pool-schedule.js +55 -0
- package/dist/runner/pool-units.d.ts +34 -0
- package/dist/runner/pool-units.js +85 -0
- package/dist/runner/pooled-run.d.ts +7 -5
- package/dist/runner/pooled-run.js +19 -9
- package/dist/runner/prepare-run.js +5 -0
- package/dist/runner/registry.js +21 -7
- package/dist/runner/reporter.d.ts +13 -1
- package/dist/runner/reporter.js +13 -2
- package/dist/runner/request-fixture.d.ts +14 -0
- package/dist/runner/request-fixture.js +25 -0
- package/dist/runner/run-hooks.d.ts +16 -0
- package/dist/runner/run-hooks.js +52 -0
- package/dist/runner/run-test.d.ts +3 -0
- package/dist/runner/run-test.js +23 -3
- package/dist/runner/run.d.ts +8 -0
- package/dist/runner/run.js +10 -0
- package/dist/runner/select.d.ts +15 -4
- package/dist/runner/select.js +38 -12
- package/dist/runner/serial-run.d.ts +23 -0
- package/dist/runner/serial-run.js +88 -0
- package/dist/runner/serial.d.ts +34 -0
- package/dist/runner/serial.js +63 -0
- package/dist/runner/shard.d.ts +22 -0
- package/dist/runner/shard.js +73 -0
- package/dist/runner/suite.d.ts +3 -3
- package/dist/runner/suite.js +4 -3
- package/dist/runner/test-function.d.ts +15 -3
- package/dist/runner/test-info.js +2 -1
- package/dist/runner/test-runner.d.ts +1 -0
- package/dist/runner/test-runner.js +67 -18
- package/dist/runner/use-options.d.ts +18 -4
- package/dist/runner/use-options.js +33 -6
- package/dist/runner/wait-for-url.d.ts +20 -0
- package/dist/runner/wait-for-url.js +65 -0
- package/dist/runner/web-server.d.ts +16 -0
- package/dist/runner/web-server.js +165 -0
- package/dist/runner/worker-pool.d.ts +13 -5
- package/dist/runner/worker-pool.js +85 -51
- package/dist/runner/worker-protocol.d.ts +8 -0
- package/dist/runner/worker-session.js +9 -1
- package/dist/runner/worker.d.ts +2 -0
- package/dist/runner/worker.js +7 -1
- package/dist/types/config.d.ts +117 -21
- package/dist/types/extend.d.ts +54 -2
- package/dist/types/test.d.ts +42 -0
- package/dist/types/use.d.ts +59 -7
- package/package.json +1 -1
- package/dist/browser/emulation.d.ts +0 -61
- package/dist/browser/emulation.js +0 -180
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { StorageState } from "../types/use.js";
|
|
2
|
+
/** The state itself, or the JSON file (relative to `baseDir`) that holds it. Throws `<who>: what is wrong` (`use()` unless told otherwise). */
|
|
3
|
+
export declare function loadStorageState(source: string | StorageState, baseDir: string, who?: string): StorageState;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { checkStorageStateObject } from "./use-options.js";
|
|
4
|
+
/** The state itself, or the JSON file (relative to `baseDir`) that holds it. Throws `<who>: what is wrong` (`use()` unless told otherwise). */
|
|
5
|
+
export function loadStorageState(source, baseDir, who = "use()") {
|
|
6
|
+
if (typeof source !== "string")
|
|
7
|
+
return source;
|
|
8
|
+
const file = path.resolve(baseDir, source);
|
|
9
|
+
let state;
|
|
10
|
+
try {
|
|
11
|
+
state = JSON.parse(readFileSync(file, "utf8"));
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
throw new Error(`${who}: storageState file ${JSON.stringify(source)} could not be read: ${err instanceof Error ? err.message : String(err)}`);
|
|
15
|
+
}
|
|
16
|
+
if (typeof state !== "object" || state === null || Array.isArray(state))
|
|
17
|
+
throw new Error(`${who}: storageState file ${JSON.stringify(source)} must hold an object`);
|
|
18
|
+
checkStorageStateObject(state);
|
|
19
|
+
return state;
|
|
20
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { SamuraiTestConfig } from "../types/config.js";
|
|
2
2
|
import type { UseOptions } from "../types/use.js";
|
|
3
|
+
/** The options of `use`: config, group, `test.use()` and (those that can change at run time) `page.use()` */
|
|
4
|
+
export declare const USE_OPTION_NAMES: readonly string[];
|
|
3
5
|
/**
|
|
4
6
|
* Checks the options of `test.use()` or of the config's `use` and returns them. The config's `use` also holds
|
|
5
7
|
* options that are not these (`viewport`, `headless`, ...), so it passes `allowOthers`; `test.use()` refuses
|
|
@@ -9,7 +11,7 @@ export declare function validateUseOptions(options: unknown, allowOthers?: boole
|
|
|
9
11
|
/** Checks a storage state object, whether it came from the options or from a file */
|
|
10
12
|
export declare function checkStorageStateObject(state: Record<string, unknown>): void;
|
|
11
13
|
/** The options after `layers` are applied in order: a later layer wins, key by key */
|
|
12
|
-
export declare function mergeUse(layers: readonly
|
|
14
|
+
export declare function mergeUse<T extends UseOptions>(layers: readonly T[]): T;
|
|
13
15
|
/** Checks the new options of a config's `use` */
|
|
14
16
|
export declare function validateUseConfig(config: SamuraiTestConfig): void;
|
|
15
17
|
/** The options of `test.use()` that a config's `use` holds, without the rest (`viewport`, `headless`, ...) */
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { checkFixtureUse, isFixtureUseKey } from "./fixture-use.js";
|
|
2
|
+
/** The options of `use`: config, group, `test.use()` and (those that can change at run time) `page.use()` */
|
|
3
|
+
export const USE_OPTION_NAMES = [
|
|
2
4
|
"locale",
|
|
3
|
-
"
|
|
5
|
+
"timezone",
|
|
4
6
|
"userAgent",
|
|
7
|
+
"geolocation",
|
|
8
|
+
"offline",
|
|
9
|
+
"orientation",
|
|
10
|
+
"screen",
|
|
5
11
|
"javaScriptEnabled",
|
|
6
12
|
"acceptInsecureCerts",
|
|
7
13
|
"hasTouch",
|
|
@@ -10,12 +16,20 @@ const NAMES = [
|
|
|
10
16
|
"extraHTTPHeaders",
|
|
11
17
|
"colorScheme",
|
|
12
18
|
];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"hasTouch",
|
|
17
|
-
|
|
19
|
+
/** A name that was in use once, and the one that replaced it: refused with a message that says which */
|
|
20
|
+
const RENAMED = {
|
|
21
|
+
timezoneId: "timezone",
|
|
22
|
+
touch: "hasTouch",
|
|
23
|
+
};
|
|
24
|
+
const ORIENTATIONS = [
|
|
25
|
+
"portrait-primary",
|
|
26
|
+
"portrait-secondary",
|
|
27
|
+
"landscape-primary",
|
|
28
|
+
"landscape-secondary",
|
|
18
29
|
];
|
|
30
|
+
/** These may be `null` (the real value back) */
|
|
31
|
+
const NULLABLE_BOOLEANS = ["javaScriptEnabled", "hasTouch", "offline"];
|
|
32
|
+
const BOOLEANS = ["acceptInsecureCerts", "isMobile"];
|
|
19
33
|
const COLOR_SCHEMES = ["light", "dark", "no-preference"];
|
|
20
34
|
const HEADER_NAME = /^[\w!#$%&'*+.^`|~-]+$/;
|
|
21
35
|
const fail = (problem) => {
|
|
@@ -30,17 +44,32 @@ const isObject = (value) => typeof value === "object" && value !== null && !Arra
|
|
|
30
44
|
export function validateUseOptions(options, allowOthers = false) {
|
|
31
45
|
if (!isObject(options))
|
|
32
46
|
return fail("the options must be an object");
|
|
33
|
-
for (const key of Object.keys(options))
|
|
34
|
-
|
|
47
|
+
for (const key of Object.keys(options)) {
|
|
48
|
+
const renamed = RENAMED[key];
|
|
49
|
+
if (renamed !== undefined)
|
|
50
|
+
fail(`unknown option "${key}"; use "${renamed}"`);
|
|
51
|
+
if (!allowOthers &&
|
|
52
|
+
!USE_OPTION_NAMES.includes(key) &&
|
|
53
|
+
!isFixtureUseKey(key))
|
|
35
54
|
fail(`unknown option "${key}"`);
|
|
55
|
+
}
|
|
56
|
+
checkFixtureUse(options);
|
|
36
57
|
const use = options;
|
|
37
58
|
for (const name of BOOLEANS)
|
|
38
59
|
if (use[name] !== undefined && typeof use[name] !== "boolean")
|
|
39
60
|
fail(`${name} must be true or false, got ${JSON.stringify(use[name])}`);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
61
|
+
for (const name of NULLABLE_BOOLEANS)
|
|
62
|
+
if (use[name] != null && typeof use[name] !== "boolean")
|
|
63
|
+
fail(`${name} must be true, false or null, got ${JSON.stringify(use[name])}`);
|
|
64
|
+
if (use.locale !== null)
|
|
65
|
+
checkLocale(use.locale);
|
|
66
|
+
if (use.timezone !== null)
|
|
67
|
+
checkTimezone(use.timezone);
|
|
68
|
+
if (use.userAgent != null && !nonEmpty(use.userAgent))
|
|
43
69
|
fail("userAgent must be a non-empty string");
|
|
70
|
+
checkGeolocation(use.geolocation);
|
|
71
|
+
checkOrientation(use.orientation);
|
|
72
|
+
checkScreen(use.screen);
|
|
44
73
|
if (use.colorScheme !== undefined && !COLOR_SCHEMES.includes(use.colorScheme))
|
|
45
74
|
fail(`colorScheme must be "light", "dark" or "no-preference", got ${JSON.stringify(use.colorScheme)}`);
|
|
46
75
|
checkHeaders(use.extraHTTPHeaders);
|
|
@@ -68,9 +97,40 @@ function checkTimezone(timezone) {
|
|
|
68
97
|
new Intl.DateTimeFormat(undefined, { timeZone: timezone });
|
|
69
98
|
}
|
|
70
99
|
catch {
|
|
71
|
-
fail(`unknown
|
|
100
|
+
fail(`unknown timezone ${JSON.stringify(timezone)}`);
|
|
72
101
|
}
|
|
73
102
|
}
|
|
103
|
+
const isNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
104
|
+
const isPositiveInteger = (value) => Number.isInteger(value) && value > 0;
|
|
105
|
+
function checkGeolocation(place) {
|
|
106
|
+
if (place === undefined || place === null)
|
|
107
|
+
return;
|
|
108
|
+
if (!isObject(place))
|
|
109
|
+
return fail("geolocation must be { latitude, longitude } or null");
|
|
110
|
+
const { latitude, longitude, accuracy } = place;
|
|
111
|
+
if (!isNumber(latitude) || latitude < -90 || latitude > 90)
|
|
112
|
+
fail("latitude must be between -90 and 90");
|
|
113
|
+
if (!isNumber(longitude) || longitude < -180 || longitude > 180)
|
|
114
|
+
fail("longitude must be between -180 and 180");
|
|
115
|
+
if (accuracy !== undefined && (!isNumber(accuracy) || accuracy < 0))
|
|
116
|
+
fail("accuracy must be 0 or more");
|
|
117
|
+
}
|
|
118
|
+
function checkOrientation(orientation) {
|
|
119
|
+
if (orientation === undefined || orientation === null)
|
|
120
|
+
return;
|
|
121
|
+
const type = orientation === "portrait" || orientation === "landscape"
|
|
122
|
+
? `${orientation}-primary`
|
|
123
|
+
: orientation;
|
|
124
|
+
if (!ORIENTATIONS.includes(type))
|
|
125
|
+
fail(`unknown orientation ${JSON.stringify(orientation)}`);
|
|
126
|
+
}
|
|
127
|
+
function checkScreen(screen) {
|
|
128
|
+
if (screen === undefined || screen === null)
|
|
129
|
+
return;
|
|
130
|
+
if (!isObject(screen) ||
|
|
131
|
+
!(isPositiveInteger(screen.width) && isPositiveInteger(screen.height)))
|
|
132
|
+
fail("screen width and height must be positive integers");
|
|
133
|
+
}
|
|
74
134
|
function checkHeaders(headers) {
|
|
75
135
|
if (headers === undefined)
|
|
76
136
|
return;
|
|
@@ -124,5 +184,5 @@ export function validateUseConfig(config) {
|
|
|
124
184
|
}
|
|
125
185
|
/** The options of `test.use()` that a config's `use` holds, without the rest (`viewport`, `headless`, ...) */
|
|
126
186
|
export function useOptionsOf(configUse) {
|
|
127
|
-
return Object.fromEntries(Object.entries(configUse ?? {}).filter(([key]) =>
|
|
187
|
+
return Object.fromEntries(Object.entries(configUse ?? {}).filter(([key]) => USE_OPTION_NAMES.includes(key)));
|
|
128
188
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SamuraiTestConfig, WebServerConfig } from "../types/config.js";
|
|
2
|
+
export declare const DEFAULT_WEB_SERVER_TIMEOUT = 60000;
|
|
3
|
+
export declare const DEFAULT_GRACEFUL_TIMEOUT = 5000;
|
|
4
|
+
/** The config's `webServer` as a list (empty when there is none). Call after validation */
|
|
5
|
+
export declare function webServersOf(config: SamuraiTestConfig): WebServerConfig[];
|
|
6
|
+
/** Checks the `webServer`, `globalSetup` and `globalTeardown` keys of a config. Throws `config.<key>: what is wrong` */
|
|
7
|
+
export declare function validateWebServerConfig(config: SamuraiTestConfig): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export const DEFAULT_WEB_SERVER_TIMEOUT = 60_000;
|
|
2
|
+
export const DEFAULT_GRACEFUL_TIMEOUT = 5_000;
|
|
3
|
+
const KEYS = [
|
|
4
|
+
"command",
|
|
5
|
+
"url",
|
|
6
|
+
"port",
|
|
7
|
+
"cwd",
|
|
8
|
+
"env",
|
|
9
|
+
"timeout",
|
|
10
|
+
"reuseExistingServer",
|
|
11
|
+
"stdout",
|
|
12
|
+
"stderr",
|
|
13
|
+
"ignoreHTTPSErrors",
|
|
14
|
+
"gracefulShutdown",
|
|
15
|
+
];
|
|
16
|
+
const STREAMS = ["pipe", "ignore"];
|
|
17
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
|
+
const fail = (where, problem) => {
|
|
19
|
+
throw new Error(`${where}: ${problem}`);
|
|
20
|
+
};
|
|
21
|
+
/** The config's `webServer` as a list (empty when there is none). Call after validation */
|
|
22
|
+
export function webServersOf(config) {
|
|
23
|
+
const { webServer } = config;
|
|
24
|
+
if (webServer === undefined)
|
|
25
|
+
return [];
|
|
26
|
+
return Array.isArray(webServer) ? webServer : [webServer];
|
|
27
|
+
}
|
|
28
|
+
/** Checks the `webServer`, `globalSetup` and `globalTeardown` keys of a config. Throws `config.<key>: what is wrong` */
|
|
29
|
+
export function validateWebServerConfig(config) {
|
|
30
|
+
const { webServer } = config;
|
|
31
|
+
if (webServer !== undefined) {
|
|
32
|
+
if (Array.isArray(webServer)) {
|
|
33
|
+
if (webServer.length === 0)
|
|
34
|
+
fail("config.webServer", "the list must not be empty");
|
|
35
|
+
webServer.forEach((server, index) => checkServer(server, `config.webServer[${index}]`));
|
|
36
|
+
}
|
|
37
|
+
else
|
|
38
|
+
checkServer(webServer, "config.webServer");
|
|
39
|
+
}
|
|
40
|
+
for (const key of ["globalSetup", "globalTeardown"]) {
|
|
41
|
+
const hook = config[key];
|
|
42
|
+
if (hook !== undefined &&
|
|
43
|
+
typeof hook !== "function" &&
|
|
44
|
+
(typeof hook !== "string" || hook === ""))
|
|
45
|
+
fail(`config.${key}`, "must be a module path or a function");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function checkServer(server, where) {
|
|
49
|
+
if (!isObject(server))
|
|
50
|
+
return fail(where, "must be an object");
|
|
51
|
+
for (const key of Object.keys(server))
|
|
52
|
+
if (!KEYS.includes(key))
|
|
53
|
+
fail(where, `unknown key "${key}"`);
|
|
54
|
+
const s = server;
|
|
55
|
+
if (typeof s.command !== "string" || s.command.trim() === "")
|
|
56
|
+
fail(`${where}.command`, "must be a non-empty string");
|
|
57
|
+
if ((s.url === undefined) === (s.port === undefined))
|
|
58
|
+
fail(where, "give either url or port");
|
|
59
|
+
if (s.url !== undefined)
|
|
60
|
+
checkUrl(s.url, `${where}.url`);
|
|
61
|
+
if (s.port !== undefined &&
|
|
62
|
+
!(Number.isInteger(s.port) && s.port >= 1 && s.port <= 65535))
|
|
63
|
+
fail(`${where}.port`, `must be a whole number from 1 to 65535`);
|
|
64
|
+
if (s.cwd !== undefined && (typeof s.cwd !== "string" || s.cwd === ""))
|
|
65
|
+
fail(`${where}.cwd`, "must be a non-empty string");
|
|
66
|
+
if (s.env !== undefined) {
|
|
67
|
+
if (!isObject(s.env))
|
|
68
|
+
fail(`${where}.env`, "must be an object of names and string values");
|
|
69
|
+
for (const [name, value] of Object.entries(s.env))
|
|
70
|
+
if (typeof value !== "string")
|
|
71
|
+
fail(`${where}.env`, `"${name}" must be a string`);
|
|
72
|
+
}
|
|
73
|
+
if (s.timeout !== undefined &&
|
|
74
|
+
!(typeof s.timeout === "number" &&
|
|
75
|
+
Number.isFinite(s.timeout) &&
|
|
76
|
+
s.timeout > 0))
|
|
77
|
+
fail(`${where}.timeout`, "must be a positive number of milliseconds");
|
|
78
|
+
for (const key of ["reuseExistingServer", "ignoreHTTPSErrors"])
|
|
79
|
+
if (s[key] !== undefined && typeof s[key] !== "boolean")
|
|
80
|
+
fail(`${where}.${key}`, "must be true or false");
|
|
81
|
+
for (const key of ["stdout", "stderr"])
|
|
82
|
+
if (s[key] !== undefined && !STREAMS.includes(s[key]))
|
|
83
|
+
fail(`${where}.${key}`, `must be "pipe" or "ignore"`);
|
|
84
|
+
if (s.gracefulShutdown !== undefined)
|
|
85
|
+
checkGraceful(s.gracefulShutdown, where);
|
|
86
|
+
}
|
|
87
|
+
function checkUrl(url, where) {
|
|
88
|
+
try {
|
|
89
|
+
if (typeof url !== "string")
|
|
90
|
+
throw new TypeError();
|
|
91
|
+
const protocol = new URL(url).protocol;
|
|
92
|
+
if (protocol !== "http:" && protocol !== "https:")
|
|
93
|
+
throw new TypeError();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
fail(where, "must be an http or https URL");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function checkGraceful(graceful, where) {
|
|
100
|
+
if (!isObject(graceful))
|
|
101
|
+
return fail(`${where}.gracefulShutdown`, "must be an object");
|
|
102
|
+
for (const key of Object.keys(graceful))
|
|
103
|
+
if (key !== "signal" && key !== "timeout")
|
|
104
|
+
fail(`${where}.gracefulShutdown`, `unknown key "${key}"`);
|
|
105
|
+
const { signal, timeout } = graceful;
|
|
106
|
+
if (signal !== undefined && signal !== "SIGTERM" && signal !== "SIGINT")
|
|
107
|
+
fail(`${where}.gracefulShutdown.signal`, `must be "SIGTERM" or "SIGINT"`);
|
|
108
|
+
if (timeout !== undefined &&
|
|
109
|
+
!(typeof timeout === "number" && Number.isFinite(timeout) && timeout >= 0))
|
|
110
|
+
fail(`${where}.gracefulShutdown.timeout`, "must be a number of milliseconds, 0 or more");
|
|
111
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { callOnExistingElement } from "./element-access.js";
|
|
2
|
-
import { clickRect } from "./
|
|
2
|
+
import { clickRect } from "./pointer-actions.js";
|
|
3
3
|
import { ATTACHED_ONLY, CLICK_CHECKS } from "./page-scripts.js";
|
|
4
4
|
/** Body run in the page as `(el) => { ... }`: what kind of control the element is, and whether it is checked */
|
|
5
5
|
const CONTROL_INFO_BODY = `
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ActionOptions } from "./actionability.js";
|
|
2
|
+
import type { ElementAccess } from "./element-access.js";
|
|
3
|
+
import { type Point } from "./pointer-actions.js";
|
|
4
|
+
export interface DragOptions extends ActionOptions {
|
|
5
|
+
/** Where on the dragged element to press: pixels from its top left corner (default: the centre) */
|
|
6
|
+
sourcePosition?: Point;
|
|
7
|
+
/** Where on the target to release (default: the centre) */
|
|
8
|
+
targetPosition?: Point;
|
|
9
|
+
/** Pointer moves between the two points (default 10) */
|
|
10
|
+
steps?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Presses on the element, moves to `target` in steps and releases there, with real pointer input. Both ends
|
|
14
|
+
* wait like a click does. Chrome runs a real HTML5 drag; Firefox starts one but never drops it from
|
|
15
|
+
* synthetic pointer input, so the drop (and `dragend`) are then fired as synthetic events (`isTrusted` false).
|
|
16
|
+
*/
|
|
17
|
+
export declare function dragElement(access: ElementAccess, target: ElementAccess, options?: DragOptions): Promise<void>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { pointAtElement, } from "./pointer-element-actions.js";
|
|
2
|
+
import { HOVER_CHECKS, CLICK_CHECKS } from "./page-scripts.js";
|
|
3
|
+
import { pointerDrag } from "./pointer-actions.js";
|
|
4
|
+
const DEFAULT_STEPS = 10;
|
|
5
|
+
const STATE = `globalThis[Symbol.for("samurai.drag")]`;
|
|
6
|
+
/** Starts watching the drag events of the document, to tell afterwards how far a native drag got */
|
|
7
|
+
const WATCH_DRAG = `() => {
|
|
8
|
+
const state = { started: false, dropped: false, data: [], off: [] };
|
|
9
|
+
const on = (target, type, fn, capture) => {
|
|
10
|
+
target.addEventListener(type, fn, capture);
|
|
11
|
+
state.off.push(() => target.removeEventListener(type, fn, capture));
|
|
12
|
+
};
|
|
13
|
+
on(document, "dragstart", () => { state.started = true; }, true);
|
|
14
|
+
on(document, "drop", () => { state.dropped = true; }, true);
|
|
15
|
+
// Bubbling to the window comes after the page's own dragstart handlers, so what they put in the data is there
|
|
16
|
+
on(window, "dragstart", (event) => {
|
|
17
|
+
const dt = event.dataTransfer;
|
|
18
|
+
for (const type of dt ? Array.from(dt.types) : []) {
|
|
19
|
+
if (type !== "Files") state.data.push([type, dt.getData(type)]);
|
|
20
|
+
}
|
|
21
|
+
}, false);
|
|
22
|
+
${STATE} = state;
|
|
23
|
+
}`;
|
|
24
|
+
/**
|
|
25
|
+
* Stops watching. When the browser began a native drag but never dropped (Firefox does this with
|
|
26
|
+
* synthetic pointer input), or the source is \`draggable\` and no drag began, fires the rest of the sequence as
|
|
27
|
+
* synthetic events with a DataTransfer that carries what the page's dragstart handler stored.
|
|
28
|
+
*/
|
|
29
|
+
const FINISH_DRAG = `(source, target, from, to, moved) => {
|
|
30
|
+
const state = ${STATE};
|
|
31
|
+
delete ${STATE};
|
|
32
|
+
if (!state) return;
|
|
33
|
+
state.off.forEach((off) => off());
|
|
34
|
+
if (state.dropped || !moved) return;
|
|
35
|
+
if (!state.started && !source.closest('[draggable="true"]')) return;
|
|
36
|
+
const data = new DataTransfer();
|
|
37
|
+
for (const [type, value] of state.data) data.setData(type, value);
|
|
38
|
+
const fire = (el, type, at) => el.dispatchEvent(new DragEvent(type, {
|
|
39
|
+
bubbles: true, cancelable: true, composed: true, dataTransfer: data, clientX: at.x, clientY: at.y,
|
|
40
|
+
}));
|
|
41
|
+
if (!state.started && !fire(source, "dragstart", from)) return;
|
|
42
|
+
if (!state.started) fire(target, "dragenter", to);
|
|
43
|
+
// A drop is only allowed where dragover was cancelled, as in a real drag
|
|
44
|
+
if (fire(target, "dragover", to)) { fire(source, "dragend", to); return; }
|
|
45
|
+
fire(target, "drop", to);
|
|
46
|
+
fire(source, "dragend", to);
|
|
47
|
+
}`;
|
|
48
|
+
/**
|
|
49
|
+
* Presses on the element, moves to `target` in steps and releases there, with real pointer input. Both ends
|
|
50
|
+
* wait like a click does. Chrome runs a real HTML5 drag; Firefox starts one but never drops it from
|
|
51
|
+
* synthetic pointer input, so the drop (and `dragend`) are then fired as synthetic events (`isTrusted` false).
|
|
52
|
+
*/
|
|
53
|
+
export async function dragElement(access, target, options = {}) {
|
|
54
|
+
const { sourcePosition, targetPosition, steps = DEFAULT_STEPS, ...wait } = options;
|
|
55
|
+
if (!Number.isInteger(steps) || steps < 1) {
|
|
56
|
+
throw new Error("dragTo(): steps must be a whole number, 1 or more");
|
|
57
|
+
}
|
|
58
|
+
const sourceOptions = {
|
|
59
|
+
...wait,
|
|
60
|
+
...(sourcePosition && { position: sourcePosition }),
|
|
61
|
+
};
|
|
62
|
+
const targetOptions = {
|
|
63
|
+
...wait,
|
|
64
|
+
...(targetPosition && { position: targetPosition }),
|
|
65
|
+
};
|
|
66
|
+
// The target first: scrolling it into view may move the source, which is probed last
|
|
67
|
+
const to = await pointAtElement(target, "dragTo", HOVER_CHECKS, targetOptions);
|
|
68
|
+
const from = await pointAtElement(access, "dragTo", CLICK_CHECKS, sourceOptions);
|
|
69
|
+
const source = await access.requireElement();
|
|
70
|
+
const destination = await target.requireElement();
|
|
71
|
+
await access.call(WATCH_DRAG, []);
|
|
72
|
+
let moved = false;
|
|
73
|
+
try {
|
|
74
|
+
await pointerDrag(access.connector, access.contextId, from, to, steps);
|
|
75
|
+
moved = true;
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
await access.call(FINISH_DRAG, [source, destination, from, to, moved]);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { type ElementScreenshotOptions } from "../browser/screenshot.js";
|
|
2
2
|
import type { ActionOptions } from "./actionability.js";
|
|
3
3
|
import { type ElementAccess } from "./element-access.js";
|
|
4
|
-
/** Waits until the element is attached, visible, stable, enabled and not covered, then clicks its centre with real pointer input */
|
|
5
|
-
export declare function clickElement(access: ElementAccess, options?: ActionOptions): Promise<void>;
|
|
6
4
|
/** Takes a screenshot of just the element, after scrolling it into view and waiting until it is attached and visible */
|
|
7
5
|
export declare function screenshotElement(access: ElementAccess, options?: ElementScreenshotOptions & {
|
|
8
6
|
timeout?: number;
|
|
9
7
|
}): Promise<Buffer>;
|
|
10
8
|
/** Waits until the element is attached, then focuses it */
|
|
11
9
|
export declare function focusElement(access: ElementAccess, options?: ActionOptions): Promise<void>;
|
|
12
|
-
/**
|
|
13
|
-
export declare function pressOnElement(access: ElementAccess, key: string, options?: ActionOptions
|
|
10
|
+
/** Focuses the element and presses `key` with real keyboard input; `key` can be a chord such as `Control+a` */
|
|
11
|
+
export declare function pressOnElement(access: ElementAccess, key: string, options?: ActionOptions & {
|
|
12
|
+
delay?: number;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
/** Focuses the element and types `text` into it one key press at a time, `delay` ms apart */
|
|
15
|
+
export declare function pressSequentially(access: ElementAccess, text: string, options?: ActionOptions & {
|
|
16
|
+
delay?: number;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
/** Empties an input, textarea or editable element, as `fill("")` does */
|
|
19
|
+
export declare function clearElement(access: ElementAccess, options?: ActionOptions): Promise<void>;
|
|
14
20
|
/** Waits until the field is editable and not covered, then replaces its value by typing `value`; an empty string clears it */
|
|
15
21
|
export declare function fillElement(access: ElementAccess, value: string, options?: ActionOptions & {
|
|
16
22
|
output?: string;
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { bindOutput } from "../wait/output-sink.js";
|
|
2
2
|
import { takeScreenshot, } from "../browser/screenshot.js";
|
|
3
3
|
import { callOnElement, callOnExistingElement, requireElement, } from "./element-access.js";
|
|
4
|
-
import { clickRect
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// "attached" is always required, so the box is present
|
|
10
|
-
await clickRect(access.connector, access.contextId, state.box);
|
|
11
|
-
}
|
|
4
|
+
import { clickRect } from "./pointer-actions.js";
|
|
5
|
+
import { DEFAULT_PAUSE_MS, pause, typeText } from "./input-actions.js";
|
|
6
|
+
import { chordSteps, performKeys, textSteps } from "./key-actions.js";
|
|
7
|
+
import { NAMED_KEYS, parseKeyChord } from "./key-map.js";
|
|
8
|
+
import { ATTACHED_ONLY, FILL_CHECKS, PRESS_CHECKS, SCREENSHOT_CHECKS, } from "./page-scripts.js";
|
|
12
9
|
/** Takes a screenshot of just the element, after scrolling it into view and waiting until it is attached and visible */
|
|
13
10
|
export async function screenshotElement(access, options = {}) {
|
|
14
11
|
const { timeout, ...capture } = options;
|
|
@@ -23,15 +20,25 @@ export async function focusElement(access, options) {
|
|
|
23
20
|
await access.waitForActionable("focus", ATTACHED_ONLY, { scroll: false, hitTest: false }, options);
|
|
24
21
|
await callOnExistingElement(access, `el.focus();`);
|
|
25
22
|
}
|
|
26
|
-
/** Waits until the element is attached, visible and enabled
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (code === "") {
|
|
30
|
-
throw new Error(`press(): unknown key "${key}". Use one of ${Object.keys(NAMED_KEYS).join(", ")} or a single character`);
|
|
31
|
-
}
|
|
32
|
-
await access.waitForActionable("press", PRESS_CHECKS, { scroll: true, hitTest: false }, options);
|
|
23
|
+
/** Waits until the element is attached, visible and enabled and focuses it; the keys then go to the page's keyboard */
|
|
24
|
+
async function focusForKeys(access, method, options) {
|
|
25
|
+
await access.waitForActionable(method, PRESS_CHECKS, { scroll: true, hitTest: false }, options);
|
|
33
26
|
await callOnElement(access, undefined, `el.focus();`);
|
|
34
|
-
|
|
27
|
+
}
|
|
28
|
+
/** Focuses the element and presses `key` with real keyboard input; `key` can be a chord such as `Control+a` */
|
|
29
|
+
export async function pressOnElement(access, key, options) {
|
|
30
|
+
const chord = parseKeyChord(key, "press");
|
|
31
|
+
await focusForKeys(access, "press", options);
|
|
32
|
+
await performKeys(access.connector, access.contextId, chordSteps(chord, options?.delay));
|
|
33
|
+
}
|
|
34
|
+
/** Focuses the element and types `text` into it one key press at a time, `delay` ms apart */
|
|
35
|
+
export async function pressSequentially(access, text, options) {
|
|
36
|
+
await focusForKeys(access, "pressSequentially", options);
|
|
37
|
+
await performKeys(access.connector, access.contextId, textSteps(text, options?.delay));
|
|
38
|
+
}
|
|
39
|
+
/** Empties an input, textarea or editable element, as `fill("")` does */
|
|
40
|
+
export async function clearElement(access, options) {
|
|
41
|
+
await fillElement(access, "", options);
|
|
35
42
|
}
|
|
36
43
|
/** Waits until the field is editable and not covered, then replaces its value by typing `value`; an empty string clears it */
|
|
37
44
|
export async function fillElement(access, value, options) {
|
|
@@ -22,6 +22,11 @@ export interface ProbeOptions {
|
|
|
22
22
|
scroll: boolean;
|
|
23
23
|
/** Check which element receives a click at the element's centre */
|
|
24
24
|
hitTest: boolean;
|
|
25
|
+
/** Where to check the hit: this far from the element's top left corner; the centre without it */
|
|
26
|
+
at?: {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
};
|
|
25
30
|
/**
|
|
26
31
|
* Probe a checkbox, radio or switch the way a user reaches it: opacity 0 does not hide it, and a click on
|
|
27
32
|
* one of its labels counts as hitting it (`control-probe.ts`). Only the check actions set this
|
|
@@ -46,7 +46,8 @@ export const PROBE_ELEMENT = `(el, options) => {
|
|
|
46
46
|
const editable = Boolean(enabled && !el.readOnly && typeable);
|
|
47
47
|
let hitTarget = null;
|
|
48
48
|
if (options.hitTest) {
|
|
49
|
-
const
|
|
49
|
+
const at = options.at || { x: rect.width / 2, y: rect.height / 2 };
|
|
50
|
+
const top = document.elementFromPoint(rect.left + at.x, rect.top + at.y);
|
|
50
51
|
hitTarget = !top ? "nothing"
|
|
51
52
|
: top === el || el.contains(top) ? "self"
|
|
52
53
|
: top.tagName.toLowerCase() + (top.id ? "#" + top.id : "") +
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ActionOptions } from "./actionability.js";
|
|
2
|
+
import { type ElementAccess } from "./element-access.js";
|
|
3
|
+
/** Waits until the element is attached, then removes focus from it */
|
|
4
|
+
export declare function blurElement(access: ElementAccess, options?: ActionOptions): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Waits until the element is attached, then fires a synthetic event of `type` at it (no scrolling, visibility or
|
|
7
|
+
* enabled checks, as with a user it would not be reached). The event class follows the type; `eventInit`
|
|
8
|
+
* (plain data) is merged over `bubbles`, `cancelable` and `composed`, which are all true.
|
|
9
|
+
*/
|
|
10
|
+
export declare function dispatchEventOn(access: ElementAccess, type: string, eventInit?: Record<string, unknown>, options?: ActionOptions): Promise<void>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { callOnExistingElement } from "./element-access.js";
|
|
2
|
+
import { ATTACHED_ONLY } from "./page-scripts.js";
|
|
3
|
+
/** Waits until the element is attached, then removes focus from it */
|
|
4
|
+
export async function blurElement(access, options) {
|
|
5
|
+
await access.waitForActionable("blur", ATTACHED_ONLY, { scroll: false, hitTest: false }, options);
|
|
6
|
+
await callOnExistingElement(access, `el.blur();`);
|
|
7
|
+
}
|
|
8
|
+
// The event interface each type is made with; any other type is a plain Event
|
|
9
|
+
const EVENT_CLASSES = {
|
|
10
|
+
MouseEvent: [
|
|
11
|
+
"click",
|
|
12
|
+
"dblclick",
|
|
13
|
+
"auxclick",
|
|
14
|
+
"contextmenu",
|
|
15
|
+
"mousedown",
|
|
16
|
+
"mouseup",
|
|
17
|
+
"mousemove",
|
|
18
|
+
"mouseover",
|
|
19
|
+
"mouseout",
|
|
20
|
+
"mouseenter",
|
|
21
|
+
"mouseleave",
|
|
22
|
+
],
|
|
23
|
+
PointerEvent: [
|
|
24
|
+
"pointerdown",
|
|
25
|
+
"pointerup",
|
|
26
|
+
"pointermove",
|
|
27
|
+
"pointerover",
|
|
28
|
+
"pointerout",
|
|
29
|
+
"pointerenter",
|
|
30
|
+
"pointerleave",
|
|
31
|
+
"pointercancel",
|
|
32
|
+
"gotpointercapture",
|
|
33
|
+
"lostpointercapture",
|
|
34
|
+
],
|
|
35
|
+
KeyboardEvent: ["keydown", "keyup", "keypress"],
|
|
36
|
+
FocusEvent: ["focus", "blur", "focusin", "focusout"],
|
|
37
|
+
InputEvent: ["input", "beforeinput"],
|
|
38
|
+
WheelEvent: ["wheel"],
|
|
39
|
+
DragEvent: [
|
|
40
|
+
"drag",
|
|
41
|
+
"dragstart",
|
|
42
|
+
"dragend",
|
|
43
|
+
"dragenter",
|
|
44
|
+
"dragover",
|
|
45
|
+
"dragleave",
|
|
46
|
+
"drop",
|
|
47
|
+
],
|
|
48
|
+
CompositionEvent: ["compositionstart", "compositionupdate", "compositionend"],
|
|
49
|
+
TouchEvent: ["touchstart", "touchend", "touchmove", "touchcancel"],
|
|
50
|
+
AnimationEvent: ["animationstart", "animationend", "animationiteration"],
|
|
51
|
+
TransitionEvent: [
|
|
52
|
+
"transitionrun",
|
|
53
|
+
"transitionstart",
|
|
54
|
+
"transitionend",
|
|
55
|
+
"transitioncancel",
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
/** Body run in the page as `(el, type, init, classes) => { ... }`: builds the event by its type and fires it at `el` */
|
|
59
|
+
const DISPATCH_BODY = `
|
|
60
|
+
const name = Object.keys(classes).find((key) => classes[key].includes(type));
|
|
61
|
+
const Ctor = (name && globalThis[name]) || Event;
|
|
62
|
+
const init = Object.assign({ bubbles: true, cancelable: true, composed: true }, eventInit);
|
|
63
|
+
if (name === "DragEvent" && !init.dataTransfer) init.dataTransfer = new DataTransfer();
|
|
64
|
+
el.dispatchEvent(new Ctor(type, init));`;
|
|
65
|
+
/**
|
|
66
|
+
* Waits until the element is attached, then fires a synthetic event of `type` at it (no scrolling, visibility or
|
|
67
|
+
* enabled checks, as with a user it would not be reached). The event class follows the type; `eventInit`
|
|
68
|
+
* (plain data) is merged over `bubbles`, `cancelable` and `composed`, which are all true.
|
|
69
|
+
*/
|
|
70
|
+
export async function dispatchEventOn(access, type, eventInit = {}, options) {
|
|
71
|
+
if (type === "")
|
|
72
|
+
throw new Error("dispatchEvent(): type must not be empty");
|
|
73
|
+
await access.waitForActionable("dispatchEvent", ATTACHED_ONLY, { scroll: false, hitTest: false }, options);
|
|
74
|
+
const el = await access.requireElement();
|
|
75
|
+
await access.call(`(el, type, eventInit, classes) => {${DISPATCH_BODY}}`, [
|
|
76
|
+
el,
|
|
77
|
+
type,
|
|
78
|
+
eventInit,
|
|
79
|
+
EVENT_CLASSES,
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import type { BiDiConnector } from "../transport/bidi-connection.js";
|
|
2
2
|
/** How long `wait()` pauses when no duration is given (ms) */
|
|
3
3
|
export declare const DEFAULT_PAUSE_MS = 300;
|
|
4
|
-
|
|
5
|
-
export declare const
|
|
6
|
-
/** Clicks the centre of `rect` (viewport pixels) with real pointer input */
|
|
7
|
-
export declare function clickRect(connector: BiDiConnector, context: string, rect: {
|
|
8
|
-
x: number;
|
|
9
|
-
y: number;
|
|
10
|
-
width: number;
|
|
11
|
-
height: number;
|
|
12
|
-
}): Promise<void>;
|
|
4
|
+
export declare const POINTER_ID = "samurai-pointer";
|
|
5
|
+
export declare const KEYBOARD_ID = "samurai-keyboard";
|
|
13
6
|
/** Pauses the input queue for `duration` ms */
|
|
14
7
|
export declare function pause(connector: BiDiConnector, context: string, duration: number): Promise<void>;
|
|
15
|
-
/** Presses and releases one key, given as its WebDriver code point or a character */
|
|
16
|
-
export declare function pressKey(connector: BiDiConnector, context: string, code: string): Promise<void>;
|
|
17
8
|
/** Types `text` one character at a time (each pressed and released) */
|
|
18
9
|
export declare function typeText(connector: BiDiConnector, context: string, text: string): Promise<void>;
|