@itmetsam/samurai-framework 0.21.2 → 0.21.4
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 +1 -1
- package/dist/assert/asymmetric.d.ts +29 -0
- package/dist/assert/asymmetric.js +166 -0
- package/dist/assert/element-probes.d.ts +20 -0
- package/dist/assert/element-probes.js +45 -0
- package/dist/assert/expect.d.ts +20 -9
- package/dist/assert/expect.js +12 -4
- package/dist/assert/locator-assertion-base.d.ts +52 -0
- package/dist/assert/locator-assertion-base.js +78 -0
- package/dist/assert/locator-assertions.d.ts +30 -49
- package/dist/assert/locator-assertions.js +44 -79
- package/dist/assert/more-locator-assertions.d.ts +30 -0
- package/dist/assert/more-locator-assertions.js +86 -0
- package/dist/assert/retry-assertions.d.ts +28 -0
- package/dist/assert/retry-assertions.js +113 -0
- package/dist/assert/text-match.d.ts +4 -2
- package/dist/assert/text-match.js +10 -2
- package/dist/assert/value-assertions.d.ts +29 -1
- package/dist/assert/value-assertions.js +84 -5
- package/dist/assert/value-matchers.d.ts +19 -0
- package/dist/assert/value-matchers.js +53 -0
- package/dist/browser/dialog.js +4 -1
- package/dist/browser/init-script.d.ts +11 -0
- package/dist/browser/init-script.js +29 -0
- package/dist/browser/navigation-error.d.ts +5 -1
- package/dist/browser/navigation-error.js +10 -2
- package/dist/browser/page-content.d.ts +5 -0
- package/dist/browser/page-content.js +43 -0
- package/dist/browser/page-evaluate.d.ts +9 -0
- package/dist/browser/page-evaluate.js +20 -0
- package/dist/browser/page-locators.d.ts +18 -0
- package/dist/browser/page-locators.js +19 -0
- package/dist/browser/page-navigation.d.ts +18 -0
- package/dist/browser/page-navigation.js +52 -2
- package/dist/browser/page-permissions.d.ts +6 -0
- package/dist/browser/page-permissions.js +15 -0
- package/dist/browser/page-settings.d.ts +3 -0
- package/dist/browser/page-settings.js +4 -0
- package/dist/browser/page.d.ts +76 -13
- package/dist/browser/page.js +103 -45
- package/dist/config/variables.js +2 -1
- package/dist/locator/accessible-name.d.ts +22 -0
- package/dist/locator/accessible-name.js +128 -0
- package/dist/locator/action-timeout-error.js +5 -1
- package/dist/locator/actionability.d.ts +3 -2
- package/dist/locator/actionability.js +4 -4
- package/dist/locator/attribute-locator.d.ts +13 -0
- package/dist/locator/attribute-locator.js +27 -0
- package/dist/locator/chain-resolver.d.ts +12 -3
- package/dist/locator/chain-resolver.js +85 -15
- package/dist/locator/check-actions.d.ts +15 -0
- package/dist/locator/check-actions.js +44 -0
- package/dist/locator/combine-step.d.ts +5 -0
- package/dist/locator/combine-step.js +15 -0
- package/dist/locator/control-probe.d.ts +10 -0
- package/dist/locator/control-probe.js +50 -0
- package/dist/locator/element-access.d.ts +26 -0
- package/dist/locator/element-access.js +53 -0
- package/dist/locator/element-actions.d.ts +17 -0
- package/dist/locator/element-actions.js +51 -0
- package/dist/locator/element-reads.d.ts +46 -4
- package/dist/locator/element-reads.js +114 -2
- package/dist/locator/element-state.d.ts +5 -0
- package/dist/locator/element-state.js +2 -0
- package/dist/locator/evaluate.d.ts +12 -0
- package/dist/locator/evaluate.js +36 -0
- package/dist/locator/file-actions.d.ts +2 -2
- package/dist/locator/filter-step.d.ts +24 -0
- package/dist/locator/filter-step.js +72 -0
- package/dist/locator/label-locator.d.ts +1 -1
- package/dist/locator/locator.d.ts +118 -30
- package/dist/locator/locator.js +218 -151
- package/dist/locator/page-scripts.d.ts +2 -0
- package/dist/locator/page-scripts.js +6 -0
- package/dist/locator/page-search.d.ts +11 -2
- package/dist/locator/page-search.js +15 -3
- package/dist/locator/page-step.d.ts +16 -0
- package/dist/locator/page-step.js +35 -0
- package/dist/locator/role-locator.d.ts +65 -0
- package/dist/locator/role-locator.js +125 -0
- package/dist/locator/role-table.d.ts +18 -0
- package/dist/locator/role-table.js +190 -0
- package/dist/locator/select-actions.d.ts +13 -0
- package/dist/locator/select-actions.js +73 -0
- package/dist/locator/selector-string.d.ts +7 -0
- package/dist/locator/selector-string.js +27 -0
- package/dist/locator/selector.d.ts +47 -15
- package/dist/locator/selector.js +83 -12
- package/dist/locator/strict-mode.d.ts +6 -0
- package/dist/locator/strict-mode.js +6 -0
- package/dist/locator/text-locator.d.ts +1 -1
- package/dist/network/url-match.js +3 -1
- package/dist/script/call-function.d.ts +7 -0
- package/dist/script/call-function.js +21 -3
- package/dist/types/config.d.ts +7 -0
- package/dist/wait/wait-until.d.ts +3 -1
- package/dist/wait/wait-until.js +7 -2
- package/package.json +1 -1
package/dist/locator/selector.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildRoleSelector, isNativeRole, } from "./role-locator.js";
|
|
1
2
|
function nonEmpty(value, what) {
|
|
2
3
|
if (value.trim() === "")
|
|
3
4
|
throw new TypeError(`${what} must not be empty`);
|
|
@@ -9,8 +10,26 @@ export function xpathSelector(value) {
|
|
|
9
10
|
export function cssSelector(value) {
|
|
10
11
|
return { kind: "css", value: nonEmpty(value, "a css selector") };
|
|
11
12
|
}
|
|
12
|
-
function
|
|
13
|
-
|
|
13
|
+
export function nthSelector(index) {
|
|
14
|
+
if (!Number.isInteger(index)) {
|
|
15
|
+
throw new TypeError(`nth(): index must be an integer, got ${index}`);
|
|
16
|
+
}
|
|
17
|
+
return { kind: "nth", index };
|
|
18
|
+
}
|
|
19
|
+
function textMatch(value, what, { exact = true, match, ignoreCase }) {
|
|
20
|
+
if (value instanceof RegExp) {
|
|
21
|
+
// The page tests it as written; g and y would make a repeated test depend on lastIndex
|
|
22
|
+
const plain = new RegExp(value.source, value.flags.replace(/[gy]/g, ""));
|
|
23
|
+
return { value: plain, match: "full", ignoreCase: false };
|
|
24
|
+
}
|
|
25
|
+
if (typeof value !== "string") {
|
|
26
|
+
throw new TypeError(`${what} must be a string or a RegExp`);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
value: nonEmpty(value, what),
|
|
30
|
+
match: match ?? (exact ? "full" : "partial"),
|
|
31
|
+
ignoreCase: ignoreCase ?? !exact,
|
|
32
|
+
};
|
|
14
33
|
}
|
|
15
34
|
export function textSelector(value, options = {}) {
|
|
16
35
|
return { kind: "text", ...textMatch(value, "the text", options) };
|
|
@@ -18,14 +37,24 @@ export function textSelector(value, options = {}) {
|
|
|
18
37
|
export function labelSelector(value, options = {}) {
|
|
19
38
|
return { kind: "label", ...textMatch(value, "the label", options) };
|
|
20
39
|
}
|
|
40
|
+
const ATTRIBUTE_WHAT = {
|
|
41
|
+
placeholder: "the placeholder",
|
|
42
|
+
alt: "the alt text",
|
|
43
|
+
title: "the title",
|
|
44
|
+
};
|
|
45
|
+
export function attributeSelector(attribute, value, options = {}) {
|
|
46
|
+
return {
|
|
47
|
+
kind: "attribute",
|
|
48
|
+
attribute,
|
|
49
|
+
...textMatch(value, ATTRIBUTE_WHAT[attribute], options),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
21
52
|
export function testIdSelector(value) {
|
|
22
53
|
return { kind: "testid", value: nonEmpty(value, "the test id") };
|
|
23
54
|
}
|
|
24
|
-
export function roleSelector(role,
|
|
55
|
+
export function roleSelector(role, options = {}) {
|
|
25
56
|
nonEmpty(role, "the role");
|
|
26
|
-
return
|
|
27
|
-
? { kind: "role", role }
|
|
28
|
-
: { kind: "role", role, name };
|
|
57
|
+
return buildRoleSelector(role, options);
|
|
29
58
|
}
|
|
30
59
|
/**
|
|
31
60
|
* Normalises an xpath. Relative xpaths get a leading `//` (`.//` when `scoped`, i.e. searching inside
|
|
@@ -48,6 +77,9 @@ export function toBiDiLocator(selector, scoped) {
|
|
|
48
77
|
case "css":
|
|
49
78
|
return { type: "css", value: selector.value };
|
|
50
79
|
case "text":
|
|
80
|
+
if (typeof selector.value !== "string") {
|
|
81
|
+
throw new Error("a RegExp text is resolved by the framework, not by the browser");
|
|
82
|
+
}
|
|
51
83
|
return {
|
|
52
84
|
type: "innerText",
|
|
53
85
|
value: selector.value,
|
|
@@ -55,6 +87,9 @@ export function toBiDiLocator(selector, scoped) {
|
|
|
55
87
|
ignoreCase: selector.ignoreCase,
|
|
56
88
|
};
|
|
57
89
|
case "role":
|
|
90
|
+
if (!isNativeRole(selector) || typeof selector.name === "object") {
|
|
91
|
+
throw new Error("this role search is resolved by the framework, not by the browser");
|
|
92
|
+
}
|
|
58
93
|
return {
|
|
59
94
|
type: "accessibility",
|
|
60
95
|
value: {
|
|
@@ -69,9 +104,14 @@ export function toBiDiLocator(selector, scoped) {
|
|
|
69
104
|
};
|
|
70
105
|
// The locator resolves these two itself, so a chain never hands them to the browser
|
|
71
106
|
case "label":
|
|
72
|
-
|
|
107
|
+
case "attribute":
|
|
108
|
+
throw new Error(`${selector.kind} is resolved by the framework, not by the browser`);
|
|
73
109
|
case "nth":
|
|
74
110
|
throw new Error("nth is resolved by the framework, not by the browser");
|
|
111
|
+
case "filter":
|
|
112
|
+
case "and":
|
|
113
|
+
case "or":
|
|
114
|
+
throw new Error(`${selector.kind} is resolved by the framework, not by the browser`);
|
|
75
115
|
}
|
|
76
116
|
}
|
|
77
117
|
/** A short name for error messages */
|
|
@@ -87,19 +127,50 @@ export function describeSelector(selector, scoped) {
|
|
|
87
127
|
selector.match === "partial" && "partial",
|
|
88
128
|
selector.ignoreCase && "ignoring case",
|
|
89
129
|
].filter(Boolean);
|
|
90
|
-
return `${selector.kind}=${
|
|
130
|
+
return `${selector.kind}=${describeValue(selector.value)}${flags.length ? ` (${flags.join(", ")})` : ""}`;
|
|
131
|
+
}
|
|
132
|
+
case "attribute": {
|
|
133
|
+
const flags = [
|
|
134
|
+
selector.match === "partial" && "partial",
|
|
135
|
+
selector.ignoreCase && "ignoring case",
|
|
136
|
+
].filter(Boolean);
|
|
137
|
+
return `${selector.attribute}=${describeValue(selector.value)}${flags.length ? ` (${flags.join(", ")})` : ""}`;
|
|
91
138
|
}
|
|
92
139
|
case "testid":
|
|
93
140
|
return `testid=${JSON.stringify(selector.value)}`;
|
|
94
141
|
case "role":
|
|
95
|
-
return
|
|
96
|
-
(selector.name === undefined
|
|
97
|
-
? ""
|
|
98
|
-
: `[name=${JSON.stringify(selector.name)}]`));
|
|
142
|
+
return `role=${selector.role}${describeRoleOptions(selector)}`;
|
|
99
143
|
case "nth":
|
|
100
144
|
return `nth=${selector.index}`;
|
|
145
|
+
case "filter":
|
|
146
|
+
return `filter(${describeFilter(selector)})`;
|
|
147
|
+
case "and":
|
|
148
|
+
case "or":
|
|
149
|
+
return `(${describeChain(selector.left)}) ${selector.kind} (${describeChain(selector.right)})`;
|
|
101
150
|
}
|
|
102
151
|
}
|
|
152
|
+
function describeValue(value) {
|
|
153
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
154
|
+
}
|
|
155
|
+
function describeRoleOptions({ kind: _kind, role: _role, name, exact, ...rest }) {
|
|
156
|
+
const parts = [
|
|
157
|
+
name !== undefined && `name=${describeValue(name)}`,
|
|
158
|
+
exact === false && "exact=false",
|
|
159
|
+
...Object.entries(rest).map(([key, value]) => `${key}=${String(value)}`),
|
|
160
|
+
].filter(Boolean);
|
|
161
|
+
return parts.length ? `[${parts.join(", ")}]` : "";
|
|
162
|
+
}
|
|
163
|
+
function describeFilter(tests) {
|
|
164
|
+
return [
|
|
165
|
+
tests.hasText !== undefined && `hasText=${describeValue(tests.hasText)}`,
|
|
166
|
+
tests.hasNotText !== undefined &&
|
|
167
|
+
`hasNotText=${describeValue(tests.hasNotText)}`,
|
|
168
|
+
tests.has && `has=${describeChain(tests.has)}`,
|
|
169
|
+
tests.hasNot && `hasNot=${describeChain(tests.hasNot)}`,
|
|
170
|
+
]
|
|
171
|
+
.filter(Boolean)
|
|
172
|
+
.join(", ");
|
|
173
|
+
}
|
|
103
174
|
export function describeChain(chain) {
|
|
104
175
|
return chain
|
|
105
176
|
.map((selector, index) => describeSelector(selector, index > 0))
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Whether a locator throws when it matches several elements; asked when an action or read needs its element */
|
|
2
|
+
export type StrictSource = () => Promise<boolean>;
|
|
3
|
+
/** The default: the first match is used */
|
|
4
|
+
export declare const NOT_STRICT: StrictSource;
|
|
5
|
+
/** The error a strict locator throws for an action or read that found several elements */
|
|
6
|
+
export declare function strictViolation(method: string, count: number, selector: string): Error;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The default: the first match is used */
|
|
2
|
+
export const NOT_STRICT = () => Promise.resolve(false);
|
|
3
|
+
/** The error a strict locator throws for an action or read that found several elements */
|
|
4
|
+
export function strictViolation(method, count, selector) {
|
|
5
|
+
return new Error(`${method}(): strict mode violation, ${count} elements match ${selector}`);
|
|
6
|
+
}
|
|
@@ -10,4 +10,4 @@
|
|
|
10
10
|
*
|
|
11
11
|
* A string, so the transpiler cannot inject helpers that do not exist in the page.
|
|
12
12
|
*/
|
|
13
|
-
export declare const TEXT_LOCATE = "(starts, value, match, ignoreCase) => {\n const skipped = new Set([\"SCRIPT\", \"STYLE\", \"NOSCRIPT\", \"TEMPLATE\", \"HEAD\", \"TITLE\", \"META\", \"LINK\"]);\n \n const
|
|
13
|
+
export declare const TEXT_LOCATE = "(starts, value, match, ignoreCase) => {\n const skipped = new Set([\"SCRIPT\", \"STYLE\", \"NOSCRIPT\", \"TEMPLATE\", \"HEAD\", \"TITLE\", \"META\", \"LINK\"]);\n \n const collapse = (text) => String(text).replace(/\\s+/g, \" \").trim();\n const normalise = (text) => {\n const collapsed = collapse(text);\n return ignoreCase ? collapsed.toLowerCase() : collapsed;\n };\n const pattern = typeof value === \"string\" ? null : new RegExp(value.source, value.flags.replace(/[gy]/g, \"\"));\n const wanted = pattern ? \"\" : normalise(value);\n const textMatches = (text) => {\n if (pattern) return pattern.test(collapse(text));\n const normal = normalise(text);\n return match === \"full\" ? normal === wanted : normal.includes(wanted);\n };\n const roots = starts.length > 0 ? starts : [document];\n const seen = new Set();\n const matches = [];\n for (const root of roots) {\n for (const el of root.querySelectorAll(\"*\")) {\n if (seen.has(el) || skipped.has(el.tagName.toUpperCase())) continue;\n seen.add(el);\n if (textMatches(el.innerText ?? el.textContent ?? \"\")) matches.push(el);\n }\n }\n const hasMatchingDescendant = new Set();\n for (const el of matches) {\n for (let parent = el.parentElement; parent; parent = parent.parentElement) hasMatchingDescendant.add(parent);\n }\n return matches\n .filter((el) => !hasMatchingDescendant.has(el))\n .sort((a, b) => (a === b ? 0 : a.compareDocumentPosition(b) & 4 ? -1 : 1));\n}";
|
|
@@ -24,7 +24,9 @@ function globToRegExp(glob) {
|
|
|
24
24
|
/** Whether `url` matches `pattern` */
|
|
25
25
|
export function matchUrl(pattern, url) {
|
|
26
26
|
if (typeof pattern === "string") {
|
|
27
|
-
return isExactString(pattern)
|
|
27
|
+
return isExactString(pattern)
|
|
28
|
+
? pattern === url
|
|
29
|
+
: globToRegExp(pattern).test(url);
|
|
28
30
|
}
|
|
29
31
|
// A global or sticky RegExp remembers where it stopped; every match must start fresh
|
|
30
32
|
pattern.lastIndex = 0;
|
|
@@ -20,7 +20,14 @@ export declare class ScriptError extends Error {
|
|
|
20
20
|
export declare class ScriptSerializationError extends Error {
|
|
21
21
|
constructor(message: string);
|
|
22
22
|
}
|
|
23
|
+
/** A function to run in the page, or its source (a string is used as written) */
|
|
24
|
+
export type PageFunction<A extends unknown[], R> = string | ((...args: A) => R | Promise<R>);
|
|
23
25
|
/** The source to send for `fn`; a function object must not depend on transpiler helpers */
|
|
24
26
|
export declare function toFunctionDeclaration(fn: string | ((...args: any[]) => unknown)): string;
|
|
25
27
|
/** Calls `fn` in the page with serialized arguments and returns its deserialized result */
|
|
26
28
|
export declare function callFunction<T = unknown>(connector: BiDiConnector, context: string, fn: string | ((...args: any[]) => unknown), args?: Arg[], options?: CallOptions): Promise<T>;
|
|
29
|
+
/**
|
|
30
|
+
* Evaluates `expression` in the page and returns its deserialized result; a promise is awaited.
|
|
31
|
+
* Throws `ScriptError` with the page's message when it throws.
|
|
32
|
+
*/
|
|
33
|
+
export declare function evaluateExpression<T = unknown>(connector: BiDiConnector, context: string, expression: string): Promise<T>;
|
|
@@ -26,10 +26,12 @@ export function toFunctionDeclaration(fn) {
|
|
|
26
26
|
if (typeof fn === "string")
|
|
27
27
|
return fn;
|
|
28
28
|
const source = fn.toString();
|
|
29
|
-
// tsx/esbuild wrap named functions in a __name() helper that does not exist in the page
|
|
29
|
+
// tsx/esbuild (keepNames) wrap named functions in a __name() helper that does not exist in the page
|
|
30
30
|
if (source.includes("__name(")) {
|
|
31
|
-
throw new ScriptSerializationError("The function's source uses
|
|
32
|
-
"
|
|
31
|
+
throw new ScriptSerializationError("The function's source uses __name(), a helper that tsx and esbuild add around named functions " +
|
|
32
|
+
"(also an arrow function assigned to a variable) and that does not exist in the page. " +
|
|
33
|
+
"Write the function without inner named functions (pass callbacks inline as anonymous arrow functions), " +
|
|
34
|
+
"or pass it as a string.");
|
|
33
35
|
}
|
|
34
36
|
return source;
|
|
35
37
|
}
|
|
@@ -52,3 +54,19 @@ export async function callFunction(connector, context, fn, args = [], options =
|
|
|
52
54
|
}
|
|
53
55
|
return fromRemoteValue(result.result);
|
|
54
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Evaluates `expression` in the page and returns its deserialized result; a promise is awaited.
|
|
59
|
+
* Throws `ScriptError` with the page's message when it throws.
|
|
60
|
+
*/
|
|
61
|
+
export async function evaluateExpression(connector, context, expression) {
|
|
62
|
+
const result = await connector.send("script.evaluate", {
|
|
63
|
+
expression,
|
|
64
|
+
awaitPromise: true,
|
|
65
|
+
target: { context },
|
|
66
|
+
resultOwnership: "none",
|
|
67
|
+
});
|
|
68
|
+
if (result.type === "exception") {
|
|
69
|
+
throw new ScriptError(result.exceptionDetails.text, expression, result.exceptionDetails.stackTrace);
|
|
70
|
+
}
|
|
71
|
+
return fromRemoteValue(result.result);
|
|
72
|
+
}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -142,6 +142,13 @@ export interface SamuraiTestConfig {
|
|
|
142
142
|
* Left out, a run is headless when the `CI` environment variable is set and shows a window otherwise
|
|
143
143
|
*/
|
|
144
144
|
headless?: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Make an action or read on a locator that matches several elements throw
|
|
147
|
+
* `name(): strict mode violation, N elements match ...` instead of using the first one. Narrow the locator
|
|
148
|
+
* (`first()`, `nth()`, `filter()`) or turn it off for one locator with `locator.strict(false)`
|
|
149
|
+
* @default false
|
|
150
|
+
*/
|
|
151
|
+
strictSelectors?: boolean;
|
|
145
152
|
};
|
|
146
153
|
/** What to do with the browser's console output and uncaught page exceptions */
|
|
147
154
|
logs?: {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface WaitOptions {
|
|
2
2
|
/** Total time (ms) to keep probing; 0 probes exactly once */
|
|
3
3
|
timeout: number;
|
|
4
|
+
/** Pauses (ms) after the first, second, ... probe; the last one repeats. Default: 100 every time */
|
|
5
|
+
intervals?: readonly number[];
|
|
4
6
|
}
|
|
5
7
|
export declare class WaitTimeoutError<T> extends Error {
|
|
6
8
|
readonly timeout: number;
|
|
@@ -13,4 +15,4 @@ export declare class WaitTimeoutError<T> extends Error {
|
|
|
13
15
|
* Always probes at least once; with `timeout > 0` every probe races the time left.
|
|
14
16
|
* Probe errors are rethrown immediately. Throws WaitTimeoutError at the deadline.
|
|
15
17
|
*/
|
|
16
|
-
export declare function waitUntil<T>(probe: () => Promise<T>, isDone: (current: T, previous: T | undefined) => boolean, { timeout }: WaitOptions): Promise<T>;
|
|
18
|
+
export declare function waitUntil<T>(probe: () => Promise<T>, isDone: (current: T, previous: T | undefined) => boolean, { timeout, intervals }: WaitOptions): Promise<T>;
|
package/dist/wait/wait-until.js
CHANGED
|
@@ -29,9 +29,10 @@ function probeBefore(probe, remaining) {
|
|
|
29
29
|
* Always probes at least once; with `timeout > 0` every probe races the time left.
|
|
30
30
|
* Probe errors are rethrown immediately. Throws WaitTimeoutError at the deadline.
|
|
31
31
|
*/
|
|
32
|
-
export async function waitUntil(probe, isDone, { timeout }) {
|
|
32
|
+
export async function waitUntil(probe, isDone, { timeout, intervals }) {
|
|
33
33
|
const deadline = Date.now() + timeout;
|
|
34
34
|
let previous;
|
|
35
|
+
let probes = 0;
|
|
35
36
|
for (let first = true;; first = false) {
|
|
36
37
|
const remaining = deadline - Date.now();
|
|
37
38
|
// Only start a probe while time remains; a later one could only lose the race
|
|
@@ -49,6 +50,10 @@ export async function waitUntil(probe, isDone, { timeout }) {
|
|
|
49
50
|
const left = deadline - Date.now();
|
|
50
51
|
if (left <= 0)
|
|
51
52
|
throw new WaitTimeoutError(timeout, previous);
|
|
52
|
-
|
|
53
|
+
const pause = intervals?.length
|
|
54
|
+
? intervals[Math.min(probes, intervals.length - 1)]
|
|
55
|
+
: POLL_INTERVAL;
|
|
56
|
+
probes++;
|
|
57
|
+
await sleep(Math.min(pause, left));
|
|
53
58
|
}
|
|
54
59
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itmetsam/samurai-framework",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.4",
|
|
4
4
|
"description": "Browser test framework over WebDriver BiDi: auto-waiting locators, environments and secrets, a recorder, and a command line.",
|
|
5
5
|
"main": "dist/api.js",
|
|
6
6
|
"exports": {
|