prowl-tools 0.1.7 → 0.1.8
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 +14 -1
- package/dist/{chunk-R7NUH44M.js → chunk-CWLRDV5P.js} +290 -399
- package/dist/chunk-CWLRDV5P.js.map +1 -0
- package/dist/{chunk-O3OUTZ2P.js → chunk-JFJQNJSJ.js} +394 -2
- package/dist/chunk-JFJQNJSJ.js.map +1 -0
- package/dist/index.cjs +930 -641
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/lib.cjs +626 -346
- package/dist/lib.cjs.map +1 -1
- package/dist/lib.d.cts +71 -17
- package/dist/lib.d.ts +71 -17
- package/dist/lib.js +3 -3
- package/dist/{loader-X37URHUV.js → loader-JTHA4BYG.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-O3OUTZ2P.js.map +0 -1
- package/dist/chunk-R7NUH44M.js.map +0 -1
- /package/dist/{loader-X37URHUV.js.map → loader-JTHA4BYG.js.map} +0 -0
|
@@ -2,383 +2,13 @@ import {
|
|
|
2
2
|
SUPPORTED_BROWSER_ENGINES,
|
|
3
3
|
ensureAllowedDomain,
|
|
4
4
|
huntSchema,
|
|
5
|
+
interpolateHunt,
|
|
5
6
|
listHunts,
|
|
6
7
|
loadConfig,
|
|
7
8
|
loadHunt,
|
|
8
9
|
loadHuntTags,
|
|
9
10
|
resolveViewport
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
|
|
12
|
-
// src/config/interpolate.ts
|
|
13
|
-
import crypto from "crypto";
|
|
14
|
-
var VAR_PATTERN = /\{\{([A-Z0-9_]+)\}\}/g;
|
|
15
|
-
function collectInterpolatedValues(input, vars, values) {
|
|
16
|
-
if (typeof input === "string") {
|
|
17
|
-
for (const match of input.matchAll(VAR_PATTERN)) {
|
|
18
|
-
const varValue = vars[match[1]];
|
|
19
|
-
if (varValue) values.add(varValue);
|
|
20
|
-
}
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if (Array.isArray(input)) {
|
|
24
|
-
for (const item of input) {
|
|
25
|
-
collectInterpolatedValues(item, vars, values);
|
|
26
|
-
}
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
if (input && typeof input === "object") {
|
|
30
|
-
for (const [key, value] of Object.entries(input)) {
|
|
31
|
-
collectInterpolatedValues(key, vars, values);
|
|
32
|
-
collectInterpolatedValues(value, vars, values);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function interpolateString(input, vars) {
|
|
37
|
-
const usedVars = [];
|
|
38
|
-
const value = input.replace(VAR_PATTERN, (_, name) => {
|
|
39
|
-
const varValue = vars[name];
|
|
40
|
-
if (varValue === void 0) {
|
|
41
|
-
throw new Error(`Missing variable: ${name}`);
|
|
42
|
-
}
|
|
43
|
-
usedVars.push(name);
|
|
44
|
-
return varValue;
|
|
45
|
-
});
|
|
46
|
-
return { value, usedVars };
|
|
47
|
-
}
|
|
48
|
-
var RANDOM_FIRST_NAMES = ["Alex", "Jordan", "Morgan", "Taylor", "Casey", "Riley", "Quinn", "Avery"];
|
|
49
|
-
var RANDOM_LAST_NAMES = ["Smith", "Johnson", "Brown", "Davis", "Wilson", "Clark", "Hall", "Young"];
|
|
50
|
-
function generateRandomVars(randomSource) {
|
|
51
|
-
const random = randomSource?.random ?? Math.random;
|
|
52
|
-
const randomBytes = randomSource?.randomBytes ?? crypto.randomBytes;
|
|
53
|
-
const randomUUID = randomSource?.randomUUID ?? crypto.randomUUID;
|
|
54
|
-
const hex = randomBytes(4).toString("hex");
|
|
55
|
-
const firstIndex = Math.floor(random() * RANDOM_FIRST_NAMES.length);
|
|
56
|
-
const lastIndex = Math.floor(random() * RANDOM_LAST_NAMES.length);
|
|
57
|
-
const num2 = Math.floor(random() * 9e3) + 1e3;
|
|
58
|
-
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
59
|
-
let text = "";
|
|
60
|
-
for (let i = 0; i < 8; i++) {
|
|
61
|
-
text += chars[Math.floor(random() * chars.length)];
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
RANDOM_EMAIL: `prowl_${hex}@test.com`,
|
|
65
|
-
RANDOM_NAME: `${RANDOM_FIRST_NAMES[firstIndex]} ${RANDOM_LAST_NAMES[lastIndex]}`,
|
|
66
|
-
RANDOM_NUMBER: String(num2),
|
|
67
|
-
RANDOM_UUID: randomUUID(),
|
|
68
|
-
RANDOM_TEXT: text
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
function interpolateStep(step, vars, stepPath2, redacted) {
|
|
72
|
-
const isExplicitFill = (value) => typeof value.selector === "string" && typeof value.value === "string";
|
|
73
|
-
const interpolateSinglePair = (record) => {
|
|
74
|
-
const entries = Object.entries(record);
|
|
75
|
-
if (entries.length !== 1) {
|
|
76
|
-
throw new Error("Shorthand step expects exactly one key-value pair");
|
|
77
|
-
}
|
|
78
|
-
const [key, value] = entries[0];
|
|
79
|
-
return {
|
|
80
|
-
[interpolateString(key, vars).value]: interpolateString(value, vars).value
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
if ("navigate" in step) {
|
|
84
|
-
const result = interpolateString(step.navigate, vars);
|
|
85
|
-
return { navigate: result.value };
|
|
86
|
-
}
|
|
87
|
-
if ("click" in step) {
|
|
88
|
-
if (typeof step.click === "string") {
|
|
89
|
-
return { click: interpolateString(step.click, vars).value };
|
|
90
|
-
}
|
|
91
|
-
const result = interpolateString(step.click.selector, vars);
|
|
92
|
-
return { click: { selector: result.value } };
|
|
93
|
-
}
|
|
94
|
-
if ("fill" in step) {
|
|
95
|
-
if (isExplicitFill(step.fill)) {
|
|
96
|
-
const selectorResult = interpolateString(step.fill.selector, vars);
|
|
97
|
-
const valueResult2 = interpolateString(step.fill.value, vars);
|
|
98
|
-
if (valueResult2.usedVars.length > 0) {
|
|
99
|
-
redacted.add(stepPath2);
|
|
100
|
-
}
|
|
101
|
-
return { fill: { selector: selectorResult.value, value: valueResult2.value } };
|
|
102
|
-
}
|
|
103
|
-
const [rawLabel, rawValue] = Object.entries(step.fill)[0] ?? [];
|
|
104
|
-
if (rawLabel === void 0 || rawValue === void 0) {
|
|
105
|
-
throw new Error("Shorthand fill expects exactly one key-value pair");
|
|
106
|
-
}
|
|
107
|
-
const labelResult = interpolateString(rawLabel, vars);
|
|
108
|
-
const valueResult = interpolateString(rawValue, vars);
|
|
109
|
-
if (valueResult.usedVars.length > 0) {
|
|
110
|
-
redacted.add(stepPath2);
|
|
111
|
-
}
|
|
112
|
-
return {
|
|
113
|
-
fill: {
|
|
114
|
-
[labelResult.value]: valueResult.value
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
if ("type" in step) {
|
|
119
|
-
const valueResult = interpolateString(step.type, vars);
|
|
120
|
-
if (valueResult.usedVars.length > 0) {
|
|
121
|
-
redacted.add(stepPath2);
|
|
122
|
-
}
|
|
123
|
-
return { type: valueResult.value };
|
|
124
|
-
}
|
|
125
|
-
if ("selectOption" in step) {
|
|
126
|
-
const selectorResult = interpolateString(step.selectOption.selector, vars);
|
|
127
|
-
const valueResult = interpolateString(step.selectOption.value, vars);
|
|
128
|
-
return { selectOption: { selector: selectorResult.value, value: valueResult.value } };
|
|
129
|
-
}
|
|
130
|
-
if ("select" in step) {
|
|
131
|
-
return { select: interpolateSinglePair(step.select) };
|
|
132
|
-
}
|
|
133
|
-
if ("press" in step) {
|
|
134
|
-
const selectorResult = interpolateString(step.press.selector, vars);
|
|
135
|
-
const keyResult = interpolateString(step.press.key, vars);
|
|
136
|
-
return { press: { selector: selectorResult.value, key: keyResult.value } };
|
|
137
|
-
}
|
|
138
|
-
if ("onDialog" in step) {
|
|
139
|
-
return { onDialog: { action: step.onDialog.action } };
|
|
140
|
-
}
|
|
141
|
-
if ("setInputFiles" in step) {
|
|
142
|
-
const selectorResult = interpolateString(step.setInputFiles.selector, vars);
|
|
143
|
-
const rawFiles = step.setInputFiles.files;
|
|
144
|
-
const files = Array.isArray(rawFiles) ? rawFiles.map((f) => interpolateString(f, vars).value) : interpolateString(rawFiles, vars).value;
|
|
145
|
-
return { setInputFiles: { selector: selectorResult.value, files } };
|
|
146
|
-
}
|
|
147
|
-
if ("runHunt" in step) {
|
|
148
|
-
if (typeof step.runHunt === "string") {
|
|
149
|
-
return { runHunt: interpolateString(step.runHunt, vars).value };
|
|
150
|
-
}
|
|
151
|
-
const nameResult = interpolateString(step.runHunt.name, vars);
|
|
152
|
-
const interpolatedVars = {};
|
|
153
|
-
for (const [key, value] of Object.entries(step.runHunt.vars ?? {})) {
|
|
154
|
-
interpolatedVars[key] = interpolateString(value, vars).value;
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
runHunt: {
|
|
158
|
-
name: nameResult.value,
|
|
159
|
-
...Object.keys(interpolatedVars).length > 0 ? { vars: interpolatedVars } : {}
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
if ("assert" in step) {
|
|
164
|
-
if (step.assert.visible !== void 0) {
|
|
165
|
-
return { assert: { visible: interpolateString(step.assert.visible, vars).value } };
|
|
166
|
-
}
|
|
167
|
-
if (step.assert.notVisible !== void 0) {
|
|
168
|
-
return { assert: { notVisible: interpolateString(step.assert.notVisible, vars).value } };
|
|
169
|
-
}
|
|
170
|
-
if (step.assert.urlIncludes !== void 0) {
|
|
171
|
-
return { assert: { urlIncludes: interpolateString(step.assert.urlIncludes, vars).value } };
|
|
172
|
-
}
|
|
173
|
-
if (step.assert.urlEquals !== void 0) {
|
|
174
|
-
return { assert: { urlEquals: interpolateString(step.assert.urlEquals, vars).value } };
|
|
175
|
-
}
|
|
176
|
-
return step;
|
|
177
|
-
}
|
|
178
|
-
if ("wait" in step) {
|
|
179
|
-
if (typeof step.wait === "string") {
|
|
180
|
-
return { wait: interpolateString(step.wait, vars).value };
|
|
181
|
-
}
|
|
182
|
-
return {
|
|
183
|
-
wait: {
|
|
184
|
-
for: interpolateString(step.wait.for, vars).value,
|
|
185
|
-
timeout: step.wait.timeout
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
if ("waitForSelector" in step) {
|
|
190
|
-
const selectorResult = interpolateString(step.waitForSelector.selector, vars);
|
|
191
|
-
return {
|
|
192
|
-
waitForSelector: {
|
|
193
|
-
selector: selectorResult.value,
|
|
194
|
-
timeout: step.waitForSelector.timeout
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
if ("waitForUrl" in step) {
|
|
199
|
-
const valueResult = interpolateString(step.waitForUrl.value, vars);
|
|
200
|
-
return {
|
|
201
|
-
waitForUrl: {
|
|
202
|
-
value: valueResult.value,
|
|
203
|
-
timeout: step.waitForUrl.timeout
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
if ("waitForNetworkIdle" in step) {
|
|
208
|
-
return { waitForNetworkIdle: { timeout: step.waitForNetworkIdle.timeout } };
|
|
209
|
-
}
|
|
210
|
-
if ("hover" in step) {
|
|
211
|
-
const selectorResult = interpolateString(step.hover.selector, vars);
|
|
212
|
-
return { hover: { selector: selectorResult.value } };
|
|
213
|
-
}
|
|
214
|
-
if ("scroll" in step) {
|
|
215
|
-
return { scroll: { direction: step.scroll.direction, amount: step.scroll.amount } };
|
|
216
|
-
}
|
|
217
|
-
if ("scrollTo" in step) {
|
|
218
|
-
const selectorResult = interpolateString(step.scrollTo.selector, vars);
|
|
219
|
-
return { scrollTo: { selector: selectorResult.value } };
|
|
220
|
-
}
|
|
221
|
-
if ("screenshot" in step) {
|
|
222
|
-
return { screenshot: { name: step.screenshot.name } };
|
|
223
|
-
}
|
|
224
|
-
if ("if" in step) {
|
|
225
|
-
const condition = step.if;
|
|
226
|
-
const thenSteps = condition.then.map(
|
|
227
|
-
(s, i) => interpolateStep(s, vars, `${stepPath2}.if.then.${i}`, redacted)
|
|
228
|
-
);
|
|
229
|
-
const elseSteps = condition.else?.map(
|
|
230
|
-
(s, i) => interpolateStep(s, vars, `${stepPath2}.if.else.${i}`, redacted)
|
|
231
|
-
);
|
|
232
|
-
return {
|
|
233
|
-
if: {
|
|
234
|
-
...condition.visible !== void 0 ? { visible: interpolateString(condition.visible, vars).value } : {},
|
|
235
|
-
...condition.notVisible !== void 0 ? { notVisible: interpolateString(condition.notVisible, vars).value } : {},
|
|
236
|
-
then: thenSteps,
|
|
237
|
-
...elseSteps !== void 0 ? { else: elseSteps } : {}
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
if ("repeat" in step) {
|
|
242
|
-
const repeat = step.repeat;
|
|
243
|
-
const subSteps = repeat.steps.map(
|
|
244
|
-
(s, i) => interpolateStep(s, vars, `${stepPath2}.repeat.steps.${i}`, redacted)
|
|
245
|
-
);
|
|
246
|
-
return {
|
|
247
|
-
repeat: {
|
|
248
|
-
...repeat.times !== void 0 ? { times: repeat.times } : {},
|
|
249
|
-
...repeat.while !== void 0 ? {
|
|
250
|
-
while: {
|
|
251
|
-
...repeat.while.visible !== void 0 ? { visible: interpolateString(repeat.while.visible, vars).value } : {},
|
|
252
|
-
...repeat.while.notVisible !== void 0 ? { notVisible: interpolateString(repeat.while.notVisible, vars).value } : {}
|
|
253
|
-
}
|
|
254
|
-
} : {},
|
|
255
|
-
...repeat.maxIterations !== void 0 ? { maxIterations: repeat.maxIterations } : {},
|
|
256
|
-
steps: subSteps
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
if ("mockRoute" in step) {
|
|
261
|
-
const mock = step.mockRoute;
|
|
262
|
-
return {
|
|
263
|
-
mockRoute: {
|
|
264
|
-
url: interpolateString(mock.url, vars).value,
|
|
265
|
-
response: {
|
|
266
|
-
status: mock.response.status,
|
|
267
|
-
...mock.response.contentType !== void 0 ? { contentType: interpolateString(mock.response.contentType, vars).value } : {},
|
|
268
|
-
...mock.response.body !== void 0 ? { body: interpolateString(mock.response.body, vars).value } : {},
|
|
269
|
-
...mock.response.file !== void 0 ? { file: interpolateString(mock.response.file, vars).value } : {}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
if ("unmockRoute" in step) {
|
|
275
|
-
if (typeof step.unmockRoute === "string") {
|
|
276
|
-
return { unmockRoute: interpolateString(step.unmockRoute, vars).value };
|
|
277
|
-
}
|
|
278
|
-
return {
|
|
279
|
-
unmockRoute: { url: interpolateString(step.unmockRoute.url, vars).value }
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
if ("evalScript" in step) {
|
|
283
|
-
if (typeof step.evalScript === "string") {
|
|
284
|
-
return { evalScript: interpolateString(step.evalScript, vars).value };
|
|
285
|
-
}
|
|
286
|
-
return {
|
|
287
|
-
evalScript: {
|
|
288
|
-
expression: interpolateString(step.evalScript.expression, vars).value,
|
|
289
|
-
...step.evalScript.as !== void 0 ? { as: step.evalScript.as } : {}
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
if ("runScript" in step) {
|
|
294
|
-
return {
|
|
295
|
-
runScript: { file: interpolateString(step.runScript.file, vars).value }
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
if ("assertScreenshot" in step) {
|
|
299
|
-
return {
|
|
300
|
-
assertScreenshot: {
|
|
301
|
-
name: interpolateString(step.assertScreenshot.name, vars).value,
|
|
302
|
-
...step.assertScreenshot.threshold !== void 0 ? { threshold: step.assertScreenshot.threshold } : {}
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
if ("assertWithAI" in step) {
|
|
307
|
-
return {
|
|
308
|
-
assertWithAI: interpolateString(step.assertWithAI, vars).value
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
if ("copyText" in step) {
|
|
312
|
-
return {
|
|
313
|
-
copyText: {
|
|
314
|
-
selector: interpolateString(step.copyText.selector, vars).value,
|
|
315
|
-
as: step.copyText.as
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
if ("waitForDownload" in step) {
|
|
320
|
-
if (step.waitForDownload === null) {
|
|
321
|
-
return { waitForDownload: null };
|
|
322
|
-
}
|
|
323
|
-
return {
|
|
324
|
-
waitForDownload: {
|
|
325
|
-
...step.waitForDownload.filename !== void 0 ? { filename: interpolateString(step.waitForDownload.filename, vars).value } : {},
|
|
326
|
-
...step.waitForDownload.timeout !== void 0 ? { timeout: step.waitForDownload.timeout } : {}
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
return step;
|
|
331
|
-
}
|
|
332
|
-
function interpolateAssertion(assertion, vars) {
|
|
333
|
-
if ("selectorExists" in assertion) {
|
|
334
|
-
return { selectorExists: interpolateString(assertion.selectorExists, vars).value };
|
|
335
|
-
}
|
|
336
|
-
if ("selectorNotExists" in assertion) {
|
|
337
|
-
return { selectorNotExists: interpolateString(assertion.selectorNotExists, vars).value };
|
|
338
|
-
}
|
|
339
|
-
if ("urlIncludes" in assertion) {
|
|
340
|
-
return { urlIncludes: interpolateString(assertion.urlIncludes, vars).value };
|
|
341
|
-
}
|
|
342
|
-
if ("urlEquals" in assertion) {
|
|
343
|
-
return { urlEquals: interpolateString(assertion.urlEquals, vars).value };
|
|
344
|
-
}
|
|
345
|
-
if ("noConsoleErrors" in assertion) {
|
|
346
|
-
return { noConsoleErrors: assertion.noConsoleErrors };
|
|
347
|
-
}
|
|
348
|
-
if ("noNetworkErrors" in assertion) {
|
|
349
|
-
return { noNetworkErrors: assertion.noNetworkErrors };
|
|
350
|
-
}
|
|
351
|
-
return assertion;
|
|
352
|
-
}
|
|
353
|
-
function interpolateHunt(hunt, env, randomVars = generateRandomVars()) {
|
|
354
|
-
const redactedFillSteps = /* @__PURE__ */ new Set();
|
|
355
|
-
const envVars = Object.fromEntries(
|
|
356
|
-
Object.entries(env).filter(([, value]) => value !== void 0)
|
|
357
|
-
);
|
|
358
|
-
const baseVars = { ...randomVars, ...envVars };
|
|
359
|
-
const resolvedHuntVars = {};
|
|
360
|
-
for (const [key, value] of Object.entries(hunt.vars ?? {})) {
|
|
361
|
-
resolvedHuntVars[key] = interpolateString(value, baseVars).value;
|
|
362
|
-
}
|
|
363
|
-
const vars = { ...baseVars, ...resolvedHuntVars };
|
|
364
|
-
const redactionValues = /* @__PURE__ */ new Set();
|
|
365
|
-
collectInterpolatedValues(hunt.steps, vars, redactionValues);
|
|
366
|
-
collectInterpolatedValues(hunt.assertions, vars, redactionValues);
|
|
367
|
-
const steps = hunt.steps.map(
|
|
368
|
-
(step, index) => interpolateStep(step, vars, `${index}`, redactedFillSteps)
|
|
369
|
-
);
|
|
370
|
-
const assertions = hunt.assertions?.map((assertion) => interpolateAssertion(assertion, vars));
|
|
371
|
-
return {
|
|
372
|
-
hunt: {
|
|
373
|
-
...hunt,
|
|
374
|
-
steps,
|
|
375
|
-
assertions
|
|
376
|
-
},
|
|
377
|
-
redactedFillSteps,
|
|
378
|
-
randomVars,
|
|
379
|
-
redactionValues: [...redactionValues]
|
|
380
|
-
};
|
|
381
|
-
}
|
|
11
|
+
} from "./chunk-JFJQNJSJ.js";
|
|
382
12
|
|
|
383
13
|
// src/config/target.ts
|
|
384
14
|
import { execFileSync } from "child_process";
|
|
@@ -396,10 +26,17 @@ var WEB_ONLY_STEP_TYPES = /* @__PURE__ */ new Set([
|
|
|
396
26
|
"select",
|
|
397
27
|
"selectOption",
|
|
398
28
|
"setInputFiles",
|
|
399
|
-
"waitForDownload"
|
|
400
|
-
"scroll"
|
|
401
|
-
// directional scroll runs window.scrollBy (evaluate) — use scrollTo instead
|
|
29
|
+
"waitForDownload"
|
|
402
30
|
]);
|
|
31
|
+
var MACOS_UNSUPPORTED_STEP_TYPES = /* @__PURE__ */ new Set(["scroll"]);
|
|
32
|
+
function macosUnsupportedReason(step) {
|
|
33
|
+
for (const type of MACOS_UNSUPPORTED_STEP_TYPES) {
|
|
34
|
+
if (type in step) {
|
|
35
|
+
return type;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
403
40
|
function webOnlyReason(step) {
|
|
404
41
|
for (const type of WEB_ONLY_STEP_TYPES) {
|
|
405
42
|
if (type in step) {
|
|
@@ -435,6 +72,14 @@ function assertStepsSupportedByTarget(steps, target) {
|
|
|
435
72
|
`Step "${reason}" is not supported by the ${label} target. It is web-only; use a portable step (click, fill, type, press, wait, assert visible, screenshot, etc.).`
|
|
436
73
|
);
|
|
437
74
|
}
|
|
75
|
+
if (target === "macos") {
|
|
76
|
+
const macReason = macosUnsupportedReason(step);
|
|
77
|
+
if (macReason) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Step "${macReason}" is not supported by the macOS target. Directional scroll is a touch swipe available on the iOS and Android targets; there is no macOS accessibility equivalent (use scrollTo to bring a specific element into view instead).`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
438
83
|
if ("if" in step) {
|
|
439
84
|
assertStepsSupportedByTarget(step.if.then, target);
|
|
440
85
|
if (step.if.else) {
|
|
@@ -689,6 +334,9 @@ function createMacDriver(client, options = {}) {
|
|
|
689
334
|
async hover(selector) {
|
|
690
335
|
await query("hover", selector);
|
|
691
336
|
},
|
|
337
|
+
scroll() {
|
|
338
|
+
return rejectUnsupported("scroll");
|
|
339
|
+
},
|
|
692
340
|
async scrollIntoView(selector) {
|
|
693
341
|
await query("scrollTo", selector);
|
|
694
342
|
},
|
|
@@ -1295,6 +943,103 @@ function parseSnapshot(xml) {
|
|
|
1295
943
|
|
|
1296
944
|
// src/browser/android-driver.ts
|
|
1297
945
|
import fs3 from "fs";
|
|
946
|
+
|
|
947
|
+
// src/browser/touch-gestures.ts
|
|
948
|
+
var DEFAULT_SWIPE_FRACTION = 0.75;
|
|
949
|
+
var SCROLL_TO_PROBE_SWIPE_FRACTION = 0.6;
|
|
950
|
+
var MAX_SWIPE_FRACTION = 0.9;
|
|
951
|
+
var SWIPE_HOLD_MS = 100;
|
|
952
|
+
var SWIPE_MOVE_DURATION_MS = 300;
|
|
953
|
+
var SCROLL_TO_SWEEP_DEPTH = 10;
|
|
954
|
+
var SCROLL_TO_PROBE_DIRECTIONS = [
|
|
955
|
+
...Array.from({ length: SCROLL_TO_SWEEP_DEPTH }, () => "down"),
|
|
956
|
+
...Array.from({ length: SCROLL_TO_SWEEP_DEPTH * 2 }, () => "up")
|
|
957
|
+
];
|
|
958
|
+
var MAX_SCROLL_TO_SWIPES = SCROLL_TO_PROBE_DIRECTIONS.length;
|
|
959
|
+
var OPPOSITE_SWIPE_DIRECTIONS = {
|
|
960
|
+
up: "down",
|
|
961
|
+
down: "up",
|
|
962
|
+
left: "right",
|
|
963
|
+
right: "left"
|
|
964
|
+
};
|
|
965
|
+
async function probeScrollIntoView({
|
|
966
|
+
isVisible,
|
|
967
|
+
swipe,
|
|
968
|
+
directions = SCROLL_TO_PROBE_DIRECTIONS
|
|
969
|
+
}) {
|
|
970
|
+
if (await isVisible()) {
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
973
|
+
for (const direction of directions) {
|
|
974
|
+
await swipe(direction);
|
|
975
|
+
if (await isVisible()) {
|
|
976
|
+
return true;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
function toScreenSize(value, source) {
|
|
982
|
+
const record = value ?? {};
|
|
983
|
+
const width = Number(record.width);
|
|
984
|
+
const height = Number(record.height);
|
|
985
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
986
|
+
throw new Error(`${source} did not return a usable screen size`);
|
|
987
|
+
}
|
|
988
|
+
return { width, height };
|
|
989
|
+
}
|
|
990
|
+
function isVertical(direction) {
|
|
991
|
+
return direction === "up" || direction === "down";
|
|
992
|
+
}
|
|
993
|
+
function swipeDistanceFor(direction, size, amount) {
|
|
994
|
+
if (amount !== void 0 && !Number.isFinite(amount)) {
|
|
995
|
+
throw new Error("scroll amount must be a finite number");
|
|
996
|
+
}
|
|
997
|
+
const axis = isVertical(direction) ? size.height : size.width;
|
|
998
|
+
const requested = amount === void 0 ? axis * DEFAULT_SWIPE_FRACTION : Math.abs(amount);
|
|
999
|
+
const max = axis * MAX_SWIPE_FRACTION;
|
|
1000
|
+
return Math.max(1, Math.round(Math.min(requested, max)));
|
|
1001
|
+
}
|
|
1002
|
+
function scrollToProbeDistanceFor(direction, size) {
|
|
1003
|
+
const axis = isVertical(direction) ? size.height : size.width;
|
|
1004
|
+
return Math.max(1, Math.round(axis * SCROLL_TO_PROBE_SWIPE_FRACTION));
|
|
1005
|
+
}
|
|
1006
|
+
function swipeEndpoints(direction, size, distance) {
|
|
1007
|
+
const cx = Math.round(size.width / 2);
|
|
1008
|
+
const cy = Math.round(size.height / 2);
|
|
1009
|
+
const half = Math.round(distance / 2);
|
|
1010
|
+
switch (direction) {
|
|
1011
|
+
case "down":
|
|
1012
|
+
return { start: { x: cx, y: cy + half }, end: { x: cx, y: cy - half } };
|
|
1013
|
+
case "up":
|
|
1014
|
+
return { start: { x: cx, y: cy - half }, end: { x: cx, y: cy + half } };
|
|
1015
|
+
case "right":
|
|
1016
|
+
return { start: { x: cx + half, y: cy }, end: { x: cx - half, y: cy } };
|
|
1017
|
+
case "left":
|
|
1018
|
+
return { start: { x: cx - half, y: cy }, end: { x: cx + half, y: cy } };
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function buildSwipeActions(start, end) {
|
|
1022
|
+
return {
|
|
1023
|
+
type: "pointer",
|
|
1024
|
+
id: "finger1",
|
|
1025
|
+
parameters: { pointerType: "touch" },
|
|
1026
|
+
actions: [
|
|
1027
|
+
{ type: "pointerMove", duration: 0, x: start.x, y: start.y, origin: "viewport" },
|
|
1028
|
+
{ type: "pointerDown", button: 0 },
|
|
1029
|
+
{ type: "pause", duration: SWIPE_HOLD_MS },
|
|
1030
|
+
{ type: "pointerMove", duration: SWIPE_MOVE_DURATION_MS, x: end.x, y: end.y, origin: "viewport" },
|
|
1031
|
+
{ type: "pointerUp", button: 0 }
|
|
1032
|
+
]
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
function buildDirectionalSwipe(direction, size, amount) {
|
|
1036
|
+
const normalizedDirection = amount !== void 0 && amount < 0 ? OPPOSITE_SWIPE_DIRECTIONS[direction] : direction;
|
|
1037
|
+
const distance = swipeDistanceFor(normalizedDirection, size, amount);
|
|
1038
|
+
const { start, end } = swipeEndpoints(normalizedDirection, size, distance);
|
|
1039
|
+
return { actions: buildSwipeActions(start, end), distance, start, end };
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/browser/android-driver.ts
|
|
1298
1043
|
var ANDROID_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
1299
1044
|
"query",
|
|
1300
1045
|
"interact",
|
|
@@ -1409,6 +1154,11 @@ function createAndroidDriver(client, options = {}) {
|
|
|
1409
1154
|
async function fillSelector(selector, value) {
|
|
1410
1155
|
await client.setValue(await resolveOne(selector), value);
|
|
1411
1156
|
}
|
|
1157
|
+
async function swipe(direction, amount, size) {
|
|
1158
|
+
const actualSize = size ?? await client.windowSize();
|
|
1159
|
+
const { actions } = buildDirectionalSwipe(direction, actualSize, amount);
|
|
1160
|
+
await client.performActions(actions);
|
|
1161
|
+
}
|
|
1412
1162
|
return {
|
|
1413
1163
|
capabilities: ANDROID_CAPABILITIES,
|
|
1414
1164
|
// navigation -----------------------------------------------------------
|
|
@@ -1446,8 +1196,31 @@ function createAndroidDriver(client, options = {}) {
|
|
|
1446
1196
|
hover() {
|
|
1447
1197
|
return rejectUnsupported("hover");
|
|
1448
1198
|
},
|
|
1449
|
-
|
|
1450
|
-
|
|
1199
|
+
// Screen-centred swipe via the W3C actions endpoint (PROWL-080). Direction
|
|
1200
|
+
// semantics match the web step: scrolling "down" reveals lower content, so
|
|
1201
|
+
// the finger drags up. See ./touch-gestures.ts.
|
|
1202
|
+
async scroll(direction, amount) {
|
|
1203
|
+
await swipe(direction, amount);
|
|
1204
|
+
},
|
|
1205
|
+
// Resolve the element, short-circuiting if it is already present in the
|
|
1206
|
+
// hierarchy; otherwise use the shared bounded down/up mobile probe before
|
|
1207
|
+
// failing with a message naming the selector and attempts.
|
|
1208
|
+
async scrollIntoView(selector) {
|
|
1209
|
+
const query = parseAndroidSelector(selector);
|
|
1210
|
+
let probeSize;
|
|
1211
|
+
const found = await probeScrollIntoView({
|
|
1212
|
+
isVisible: async () => (await client.findElements(query)).length > 0,
|
|
1213
|
+
swipe: async (direction) => {
|
|
1214
|
+
probeSize ??= await client.windowSize();
|
|
1215
|
+
await swipe(direction, scrollToProbeDistanceFor(direction, probeSize), probeSize);
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
if (found) {
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
throw new Error(
|
|
1222
|
+
`scrollTo: element "${selector}" not visible after ${MAX_SCROLL_TO_SWIPES} scroll attempts on the Android target`
|
|
1223
|
+
);
|
|
1451
1224
|
},
|
|
1452
1225
|
setInputFiles() {
|
|
1453
1226
|
return rejectUnsupported("setInputFiles");
|
|
@@ -1919,6 +1692,13 @@ function createUia2AgentClient(transport, sessionId, options = {}) {
|
|
|
1919
1692
|
async pressKeyCode(keyCode) {
|
|
1920
1693
|
await transport.request("POST", `${base}/appium/device/press_keycode`, { keycode: keyCode });
|
|
1921
1694
|
},
|
|
1695
|
+
async windowSize() {
|
|
1696
|
+
const value = await transport.request("GET", `${base}/window/current/size`);
|
|
1697
|
+
return toScreenSize(value, "uiautomator2 /window/current/size");
|
|
1698
|
+
},
|
|
1699
|
+
async performActions(actions) {
|
|
1700
|
+
await transport.request("POST", `${base}/actions`, { actions: [actions] });
|
|
1701
|
+
},
|
|
1922
1702
|
async screenshotPng() {
|
|
1923
1703
|
const value = await transport.request("GET", `${base}/screenshot`);
|
|
1924
1704
|
if (typeof value !== "string") {
|
|
@@ -2066,6 +1846,7 @@ async function launchAndroidSession(options) {
|
|
|
2066
1846
|
readyDeadlineMs,
|
|
2067
1847
|
appPackage: pkg
|
|
2068
1848
|
});
|
|
1849
|
+
await launchPackage(runner, serial, pkg);
|
|
2069
1850
|
const driver = createAndroidDriver(client, { appLabel: pkg });
|
|
2070
1851
|
return { client, driver, package: pkg, serial, teardown };
|
|
2071
1852
|
} catch (error) {
|
|
@@ -2086,6 +1867,7 @@ var IOS_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
2086
1867
|
]);
|
|
2087
1868
|
var WAIT_POLL_INTERVAL_MS2 = 250;
|
|
2088
1869
|
var DEFAULT_WAIT_TIMEOUT_MS2 = 5e3;
|
|
1870
|
+
var DISPLAYED_CHECK_CONCURRENCY = 4;
|
|
2089
1871
|
var IOS_PRESS_KEYS = ["backspace", "del", "delete", "enter", "home", "return"];
|
|
2090
1872
|
function escapePredicateArg(value) {
|
|
2091
1873
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
@@ -2160,6 +1942,64 @@ function createIosDriver(client, options) {
|
|
|
2160
1942
|
async function fillSelector(selector, value) {
|
|
2161
1943
|
await client.setValue(await resolveOne(selector), value);
|
|
2162
1944
|
}
|
|
1945
|
+
async function swipe(direction, amount, size) {
|
|
1946
|
+
const actualSize = size ?? await client.windowSize();
|
|
1947
|
+
const { actions } = buildDirectionalSwipe(direction, actualSize, amount);
|
|
1948
|
+
await client.performActions(actions);
|
|
1949
|
+
}
|
|
1950
|
+
async function visibleElementIds(query) {
|
|
1951
|
+
const ids = await client.findElements(query);
|
|
1952
|
+
const visible = Array.from({ length: ids.length }, () => null);
|
|
1953
|
+
let nextIndex = 0;
|
|
1954
|
+
const workerCount = Math.min(DISPLAYED_CHECK_CONCURRENCY, ids.length);
|
|
1955
|
+
await Promise.all(
|
|
1956
|
+
Array.from({ length: workerCount }, async () => {
|
|
1957
|
+
for (; ; ) {
|
|
1958
|
+
const index = nextIndex;
|
|
1959
|
+
nextIndex += 1;
|
|
1960
|
+
if (index >= ids.length) {
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
const id = ids[index];
|
|
1964
|
+
if (await client.isDisplayed(id)) {
|
|
1965
|
+
visible[index] = id;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
})
|
|
1969
|
+
);
|
|
1970
|
+
return visible.filter((id) => id !== null);
|
|
1971
|
+
}
|
|
1972
|
+
async function hasVisibleElement(query) {
|
|
1973
|
+
const ids = await client.findElements(query);
|
|
1974
|
+
if (ids.length === 0) {
|
|
1975
|
+
return false;
|
|
1976
|
+
}
|
|
1977
|
+
if (await client.isDisplayed(ids[0])) {
|
|
1978
|
+
return true;
|
|
1979
|
+
}
|
|
1980
|
+
let found = false;
|
|
1981
|
+
let nextIndex = 1;
|
|
1982
|
+
const workerCount = Math.min(DISPLAYED_CHECK_CONCURRENCY, ids.length - 1);
|
|
1983
|
+
await Promise.all(
|
|
1984
|
+
Array.from({ length: workerCount }, async () => {
|
|
1985
|
+
for (; ; ) {
|
|
1986
|
+
if (found) {
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
const index = nextIndex;
|
|
1990
|
+
nextIndex += 1;
|
|
1991
|
+
if (index >= ids.length) {
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
if (await client.isDisplayed(ids[index])) {
|
|
1995
|
+
found = true;
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
})
|
|
2000
|
+
);
|
|
2001
|
+
return found;
|
|
2002
|
+
}
|
|
2163
2003
|
async function pressKey(key) {
|
|
2164
2004
|
const name = key.trim().toLowerCase();
|
|
2165
2005
|
if (name === "enter" || name === "return") {
|
|
@@ -2191,6 +2031,9 @@ function createIosDriver(client, options) {
|
|
|
2191
2031
|
async count(selector) {
|
|
2192
2032
|
return (await client.findElements(parseIosSelector(selector))).length;
|
|
2193
2033
|
},
|
|
2034
|
+
async visibleCount(selector) {
|
|
2035
|
+
return (await visibleElementIds(parseIosSelector(selector))).length;
|
|
2036
|
+
},
|
|
2194
2037
|
async textContent(selector) {
|
|
2195
2038
|
const id = await client.findElement(parseIosSelector(selector));
|
|
2196
2039
|
if (id === null) {
|
|
@@ -2215,15 +2058,38 @@ function createIosDriver(client, options) {
|
|
|
2215
2058
|
hover() {
|
|
2216
2059
|
return rejectUnsupported("hover");
|
|
2217
2060
|
},
|
|
2218
|
-
|
|
2219
|
-
|
|
2061
|
+
// Screen-centred swipe via the W3C actions endpoint (PROWL-080). Direction
|
|
2062
|
+
// semantics match the web step: scrolling "down" reveals lower content, so
|
|
2063
|
+
// the finger drags up. See ./touch-gestures.ts.
|
|
2064
|
+
async scroll(direction, amount) {
|
|
2065
|
+
await swipe(direction, amount);
|
|
2066
|
+
},
|
|
2067
|
+
// Resolve the element, short-circuiting only if WDA reports a matching
|
|
2068
|
+
// element displayed in the viewport; hierarchy-only matches can be offscreen.
|
|
2069
|
+
// Otherwise use the shared bounded down/up mobile probe before failing.
|
|
2070
|
+
async scrollIntoView(selector) {
|
|
2071
|
+
const query = parseIosSelector(selector);
|
|
2072
|
+
let probeSize;
|
|
2073
|
+
const found = await probeScrollIntoView({
|
|
2074
|
+
isVisible: () => hasVisibleElement(query),
|
|
2075
|
+
swipe: async (direction) => {
|
|
2076
|
+
probeSize ??= await client.windowSize();
|
|
2077
|
+
await swipe(direction, scrollToProbeDistanceFor(direction, probeSize), probeSize);
|
|
2078
|
+
}
|
|
2079
|
+
});
|
|
2080
|
+
if (found) {
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
throw new Error(
|
|
2084
|
+
`scrollTo: element "${selector}" not visible after ${MAX_SCROLL_TO_SWIPES} scroll attempts on the iOS target`
|
|
2085
|
+
);
|
|
2220
2086
|
},
|
|
2221
2087
|
setInputFiles() {
|
|
2222
2088
|
return rejectUnsupported("setInputFiles");
|
|
2223
2089
|
},
|
|
2224
2090
|
// semantic locators ----------------------------------------------------
|
|
2225
2091
|
async countByRole(role, name) {
|
|
2226
|
-
return (await
|
|
2092
|
+
return (await visibleElementIds({ by: "role", role, name })).length;
|
|
2227
2093
|
},
|
|
2228
2094
|
async clickFirstByRole(role, name) {
|
|
2229
2095
|
const id = await client.findElement({ by: "role", role, name });
|
|
@@ -2233,7 +2099,7 @@ function createIosDriver(client, options) {
|
|
|
2233
2099
|
await client.click(id);
|
|
2234
2100
|
},
|
|
2235
2101
|
async countByLabel(label) {
|
|
2236
|
-
return (await
|
|
2102
|
+
return (await visibleElementIds({ by: "label", value: label })).length;
|
|
2237
2103
|
},
|
|
2238
2104
|
async fillFirstByLabel(label, value) {
|
|
2239
2105
|
const id = await client.findElement({ by: "label", value: label });
|
|
@@ -2251,7 +2117,7 @@ function createIosDriver(client, options) {
|
|
|
2251
2117
|
const timeoutMs = waitOptions?.timeout ?? DEFAULT_WAIT_TIMEOUT_MS2;
|
|
2252
2118
|
const deadline = Date.now() + timeoutMs;
|
|
2253
2119
|
for (; ; ) {
|
|
2254
|
-
if (
|
|
2120
|
+
if (await hasVisibleElement(query)) {
|
|
2255
2121
|
return;
|
|
2256
2122
|
}
|
|
2257
2123
|
if (Date.now() >= deadline) {
|
|
@@ -2744,12 +2610,29 @@ function createWdaAgentClient(transport, sessionId) {
|
|
|
2744
2610
|
const value = await transport.request("GET", `${base}/element/${elementId}/text`);
|
|
2745
2611
|
return typeof value === "string" ? value : value == null ? null : String(value);
|
|
2746
2612
|
},
|
|
2613
|
+
async isDisplayed(elementId) {
|
|
2614
|
+
try {
|
|
2615
|
+
return await transport.request("GET", `${base}/element/${elementId}/displayed`) === true;
|
|
2616
|
+
} catch (error) {
|
|
2617
|
+
if (isNoSuchElement2(error)) {
|
|
2618
|
+
return false;
|
|
2619
|
+
}
|
|
2620
|
+
throw error;
|
|
2621
|
+
}
|
|
2622
|
+
},
|
|
2747
2623
|
async sendKeys(keys) {
|
|
2748
2624
|
await transport.request("POST", `${base}/wda/keys`, { value: keys });
|
|
2749
2625
|
},
|
|
2750
2626
|
async homescreen() {
|
|
2751
2627
|
await transport.request("POST", "/wda/homescreen", {});
|
|
2752
2628
|
},
|
|
2629
|
+
async windowSize() {
|
|
2630
|
+
const value = await transport.request("GET", `${base}/window/size`);
|
|
2631
|
+
return toScreenSize(value, "WebDriverAgent /window/size");
|
|
2632
|
+
},
|
|
2633
|
+
async performActions(actions) {
|
|
2634
|
+
await transport.request("POST", `${base}/actions`, { actions: [actions] });
|
|
2635
|
+
},
|
|
2753
2636
|
async source() {
|
|
2754
2637
|
const value = await transport.request("GET", "/source");
|
|
2755
2638
|
if (typeof value !== "string") {
|
|
@@ -3404,6 +3287,16 @@ function createPlaywrightDriver(page) {
|
|
|
3404
3287
|
async hover(selector) {
|
|
3405
3288
|
await page.locator(selector).hover();
|
|
3406
3289
|
},
|
|
3290
|
+
async scroll(direction, amount = 500) {
|
|
3291
|
+
const deltas = {
|
|
3292
|
+
up: [0, -amount],
|
|
3293
|
+
down: [0, amount],
|
|
3294
|
+
left: [-amount, 0],
|
|
3295
|
+
right: [amount, 0]
|
|
3296
|
+
};
|
|
3297
|
+
const [x, y] = deltas[direction];
|
|
3298
|
+
await page.evaluate(([sx, sy]) => window.scrollBy(sx, sy), [x, y]);
|
|
3299
|
+
},
|
|
3407
3300
|
async scrollIntoView(selector) {
|
|
3408
3301
|
await page.locator(selector).scrollIntoViewIfNeeded();
|
|
3409
3302
|
},
|
|
@@ -4129,11 +4022,14 @@ function toVisibilitySelector(value) {
|
|
|
4129
4022
|
if (looksLikeSelector(value)) return value;
|
|
4130
4023
|
return textContainsSelector(value);
|
|
4131
4024
|
}
|
|
4025
|
+
function countVisible(driver, selector) {
|
|
4026
|
+
return driver.visibleCount?.(selector) ?? driver.count(selector);
|
|
4027
|
+
}
|
|
4132
4028
|
async function runInlineAssert(driver, policy, assertion) {
|
|
4133
4029
|
if (assertion.visible !== void 0) {
|
|
4134
4030
|
const selector = toVisibilitySelector(assertion.visible);
|
|
4135
4031
|
policy.assertAllowedSelector(selector);
|
|
4136
|
-
const count = await driver
|
|
4032
|
+
const count = await countVisible(driver, selector);
|
|
4137
4033
|
if (count === 0) {
|
|
4138
4034
|
throw new Error(`Expected visible: ${assertion.visible}`);
|
|
4139
4035
|
}
|
|
@@ -4142,7 +4038,7 @@ async function runInlineAssert(driver, policy, assertion) {
|
|
|
4142
4038
|
if (assertion.notVisible !== void 0) {
|
|
4143
4039
|
const selector = toVisibilitySelector(assertion.notVisible);
|
|
4144
4040
|
policy.assertAllowedSelector(selector);
|
|
4145
|
-
const count = await driver
|
|
4041
|
+
const count = await countVisible(driver, selector);
|
|
4146
4042
|
if (count > 0) {
|
|
4147
4043
|
throw new Error(`Expected not visible: ${assertion.notVisible}`);
|
|
4148
4044
|
}
|
|
@@ -4532,25 +4428,21 @@ var STEP_HANDLERS = {
|
|
|
4532
4428
|
}
|
|
4533
4429
|
},
|
|
4534
4430
|
scroll: {
|
|
4535
|
-
|
|
4431
|
+
// Dispatched through the driver's `scroll` verb (interact), so native mobile
|
|
4432
|
+
// targets synthesize a touch swipe while web keeps its `window.scrollBy`
|
|
4433
|
+
// behavior. The per-target step gate rejects `scroll` on macOS before here.
|
|
4434
|
+
capabilities: ["interact"],
|
|
4536
4435
|
run: async (h) => {
|
|
4537
4436
|
if (!("scroll" in h.step)) unknownStep();
|
|
4538
|
-
const amount = h.step.scroll
|
|
4539
|
-
|
|
4540
|
-
up: [0, -amount],
|
|
4541
|
-
down: [0, amount],
|
|
4542
|
-
left: [-amount, 0],
|
|
4543
|
-
right: [amount, 0]
|
|
4544
|
-
};
|
|
4545
|
-
const [x, y] = scrollMap[h.step.scroll.direction];
|
|
4546
|
-
await h.driver.evaluate(([sx, sy]) => window.scrollBy(sx, sy), [x, y]);
|
|
4437
|
+
const { direction, amount } = h.step.scroll;
|
|
4438
|
+
await h.driver.scroll(direction, amount);
|
|
4547
4439
|
return {
|
|
4548
4440
|
kind: "result",
|
|
4549
4441
|
result: {
|
|
4550
4442
|
type: "scroll",
|
|
4551
4443
|
status: "pass",
|
|
4552
4444
|
durationMs: Date.now() - h.stepStart,
|
|
4553
|
-
value: `${
|
|
4445
|
+
value: amount === void 0 ? direction : `${direction} ${amount}px`
|
|
4554
4446
|
}
|
|
4555
4447
|
};
|
|
4556
4448
|
}
|
|
@@ -4596,7 +4488,7 @@ var STEP_HANDLERS = {
|
|
|
4596
4488
|
const condition = h.step.if;
|
|
4597
4489
|
const selector = condition.visible ?? condition.notVisible;
|
|
4598
4490
|
h.policy.assertAllowedSelector(selector);
|
|
4599
|
-
const count = await h.driver
|
|
4491
|
+
const count = await countVisible(h.driver, selector);
|
|
4600
4492
|
const conditionMet = condition.visible !== void 0 ? count > 0 : count === 0;
|
|
4601
4493
|
if (conditionMet) {
|
|
4602
4494
|
const subResult = await h.executeNested({
|
|
@@ -4683,7 +4575,7 @@ var STEP_HANDLERS = {
|
|
|
4683
4575
|
const whileSelector = repeat.while.visible ?? repeat.while.notVisible;
|
|
4684
4576
|
h.policy.assertAllowedSelector(whileSelector);
|
|
4685
4577
|
for (let i = 0; i < maxIter; i++) {
|
|
4686
|
-
const whileCount = await h.driver
|
|
4578
|
+
const whileCount = await countVisible(h.driver, whileSelector);
|
|
4687
4579
|
const shouldContinue = repeat.while.visible !== void 0 ? whileCount > 0 : whileCount === 0;
|
|
4688
4580
|
if (!shouldContinue) break;
|
|
4689
4581
|
totalSubSteps += repeat.steps.length;
|
|
@@ -7433,7 +7325,6 @@ function tccGuidance() {
|
|
|
7433
7325
|
}
|
|
7434
7326
|
|
|
7435
7327
|
export {
|
|
7436
|
-
interpolateHunt,
|
|
7437
7328
|
WEB_ONLY_STEP_TYPES,
|
|
7438
7329
|
webOnlyReason,
|
|
7439
7330
|
assertStepsSupportedByTarget,
|
|
@@ -7591,4 +7482,4 @@ export {
|
|
|
7591
7482
|
collectMacdriverStatus,
|
|
7592
7483
|
tccGuidance
|
|
7593
7484
|
};
|
|
7594
|
-
//# sourceMappingURL=chunk-
|
|
7485
|
+
//# sourceMappingURL=chunk-CWLRDV5P.js.map
|