browser-pilot 0.0.11 → 0.0.13
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 +44 -8
- package/dist/actions.cjs +686 -32
- package/dist/actions.d.cts +3 -3
- package/dist/actions.d.ts +3 -3
- package/dist/actions.mjs +2 -1
- package/dist/browser.cjs +3415 -2324
- package/dist/browser.d.cts +9 -3
- package/dist/browser.d.ts +9 -3
- package/dist/browser.mjs +4 -3
- package/dist/cdp.cjs +19 -4
- package/dist/cdp.d.cts +1 -1
- package/dist/cdp.d.ts +1 -1
- package/dist/cdp.mjs +4 -2
- package/dist/chunk-A2ZRAEO3.mjs +1711 -0
- package/dist/{chunk-BCOZUKWS.mjs → chunk-HP6R3W32.mjs} +22 -16
- package/dist/chunk-JXAUPHZM.mjs +15 -0
- package/dist/{chunk-JHAF52FA.mjs → chunk-VDAMDOS6.mjs} +1014 -738
- package/dist/cli.mjs +4998 -3259
- package/dist/{client-7Nqka5MV.d.ts → client-DRqxBdHv.d.cts} +1 -1
- package/dist/{client-7Nqka5MV.d.cts → client-DRqxBdHv.d.ts} +1 -1
- package/dist/index.cjs +4555 -3314
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +6 -4
- package/dist/{types-GWuQJs_e.d.cts → types-BXMGFtnB.d.cts} +96 -9
- package/dist/{types-DtGF3yGl.d.ts → types-CzgQjai9.d.ts} +96 -9
- package/package.json +6 -2
- package/dist/chunk-FAUNIZR7.mjs +0 -751
package/dist/chunk-FAUNIZR7.mjs
DELETED
|
@@ -1,751 +0,0 @@
|
|
|
1
|
-
// src/browser/types.ts
|
|
2
|
-
var ElementNotFoundError = class extends Error {
|
|
3
|
-
selectors;
|
|
4
|
-
hints;
|
|
5
|
-
constructor(selectors, hints) {
|
|
6
|
-
const selectorList = Array.isArray(selectors) ? selectors : [selectors];
|
|
7
|
-
let msg = `Element not found: ${selectorList.join(", ")}`;
|
|
8
|
-
if (hints?.length) {
|
|
9
|
-
msg += `. Did you mean: ${hints.slice(0, 3).map((h) => `${h.element.ref} (${h.element.role} "${h.element.name}")`).join(", ")}`;
|
|
10
|
-
}
|
|
11
|
-
msg += `. Run 'bp snapshot' to see available elements.`;
|
|
12
|
-
super(msg);
|
|
13
|
-
this.name = "ElementNotFoundError";
|
|
14
|
-
this.selectors = selectorList;
|
|
15
|
-
this.hints = hints;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
var TimeoutError = class extends Error {
|
|
19
|
-
constructor(message = "Operation timed out") {
|
|
20
|
-
const msg = message.includes("bp snapshot") ? message : `${message}. Run 'bp snapshot' to check current page state.`;
|
|
21
|
-
super(msg);
|
|
22
|
-
this.name = "TimeoutError";
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
var NavigationError = class extends Error {
|
|
26
|
-
constructor(message) {
|
|
27
|
-
super(message);
|
|
28
|
-
this.name = "NavigationError";
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// src/actions/executor.ts
|
|
33
|
-
var DEFAULT_TIMEOUT = 3e4;
|
|
34
|
-
var BatchExecutor = class {
|
|
35
|
-
page;
|
|
36
|
-
constructor(page) {
|
|
37
|
-
this.page = page;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Execute a batch of steps
|
|
41
|
-
*/
|
|
42
|
-
async execute(steps, options = {}) {
|
|
43
|
-
const { timeout = DEFAULT_TIMEOUT, onFail = "stop" } = options;
|
|
44
|
-
const results = [];
|
|
45
|
-
const startTime = Date.now();
|
|
46
|
-
for (let i = 0; i < steps.length; i++) {
|
|
47
|
-
const step = steps[i];
|
|
48
|
-
const stepStart = Date.now();
|
|
49
|
-
try {
|
|
50
|
-
const result = await this.executeStep(step, timeout);
|
|
51
|
-
results.push({
|
|
52
|
-
index: i,
|
|
53
|
-
action: step.action,
|
|
54
|
-
selector: step.selector,
|
|
55
|
-
selectorUsed: result.selectorUsed,
|
|
56
|
-
success: true,
|
|
57
|
-
durationMs: Date.now() - stepStart,
|
|
58
|
-
result: result.value,
|
|
59
|
-
text: result.text
|
|
60
|
-
});
|
|
61
|
-
} catch (error) {
|
|
62
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
63
|
-
const hints = error instanceof ElementNotFoundError ? error.hints : void 0;
|
|
64
|
-
results.push({
|
|
65
|
-
index: i,
|
|
66
|
-
action: step.action,
|
|
67
|
-
selector: step.selector,
|
|
68
|
-
success: false,
|
|
69
|
-
durationMs: Date.now() - stepStart,
|
|
70
|
-
error: errorMessage,
|
|
71
|
-
hints
|
|
72
|
-
});
|
|
73
|
-
if (onFail === "stop" && !step.optional) {
|
|
74
|
-
return {
|
|
75
|
-
success: false,
|
|
76
|
-
stoppedAtIndex: i,
|
|
77
|
-
steps: results,
|
|
78
|
-
totalDurationMs: Date.now() - startTime
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
const allSuccess = results.every((r) => r.success || steps[r.index]?.optional);
|
|
84
|
-
return {
|
|
85
|
-
success: allSuccess,
|
|
86
|
-
steps: results,
|
|
87
|
-
totalDurationMs: Date.now() - startTime
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Execute a single step
|
|
92
|
-
*/
|
|
93
|
-
async executeStep(step, defaultTimeout) {
|
|
94
|
-
const timeout = step.timeout ?? defaultTimeout;
|
|
95
|
-
const optional = step.optional ?? false;
|
|
96
|
-
switch (step.action) {
|
|
97
|
-
case "goto": {
|
|
98
|
-
if (!step.url) throw new Error("goto requires url");
|
|
99
|
-
await this.page.goto(step.url, { timeout, optional });
|
|
100
|
-
return {};
|
|
101
|
-
}
|
|
102
|
-
case "click": {
|
|
103
|
-
if (!step.selector) throw new Error("click requires selector");
|
|
104
|
-
if (step.waitForNavigation === true) {
|
|
105
|
-
const navPromise = this.page.waitForNavigation({ timeout, optional });
|
|
106
|
-
await this.page.click(step.selector, { timeout, optional });
|
|
107
|
-
await navPromise;
|
|
108
|
-
} else {
|
|
109
|
-
await this.page.click(step.selector, { timeout, optional });
|
|
110
|
-
}
|
|
111
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
112
|
-
}
|
|
113
|
-
case "fill": {
|
|
114
|
-
if (!step.selector) throw new Error("fill requires selector");
|
|
115
|
-
if (typeof step.value !== "string") throw new Error("fill requires string value");
|
|
116
|
-
await this.page.fill(step.selector, step.value, {
|
|
117
|
-
timeout,
|
|
118
|
-
optional,
|
|
119
|
-
blur: step.blur
|
|
120
|
-
});
|
|
121
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
122
|
-
}
|
|
123
|
-
case "type": {
|
|
124
|
-
if (!step.selector) throw new Error("type requires selector");
|
|
125
|
-
if (typeof step.value !== "string") throw new Error("type requires string value");
|
|
126
|
-
await this.page.type(step.selector, step.value, {
|
|
127
|
-
timeout,
|
|
128
|
-
optional,
|
|
129
|
-
delay: step.delay ?? 50
|
|
130
|
-
});
|
|
131
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
132
|
-
}
|
|
133
|
-
case "select": {
|
|
134
|
-
if (step.trigger && step.option && typeof step.value === "string") {
|
|
135
|
-
await this.page.select(
|
|
136
|
-
{
|
|
137
|
-
trigger: step.trigger,
|
|
138
|
-
option: step.option,
|
|
139
|
-
value: step.value,
|
|
140
|
-
match: step.match
|
|
141
|
-
},
|
|
142
|
-
{ timeout, optional }
|
|
143
|
-
);
|
|
144
|
-
return { selectorUsed: this.getUsedSelector(step.trigger) };
|
|
145
|
-
}
|
|
146
|
-
if (!step.selector) throw new Error("select requires selector");
|
|
147
|
-
if (!step.value) throw new Error("select requires value");
|
|
148
|
-
await this.page.select(step.selector, step.value, { timeout, optional });
|
|
149
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
150
|
-
}
|
|
151
|
-
case "check": {
|
|
152
|
-
if (!step.selector) throw new Error("check requires selector");
|
|
153
|
-
await this.page.check(step.selector, { timeout, optional });
|
|
154
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
155
|
-
}
|
|
156
|
-
case "uncheck": {
|
|
157
|
-
if (!step.selector) throw new Error("uncheck requires selector");
|
|
158
|
-
await this.page.uncheck(step.selector, { timeout, optional });
|
|
159
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
160
|
-
}
|
|
161
|
-
case "submit": {
|
|
162
|
-
if (!step.selector) throw new Error("submit requires selector");
|
|
163
|
-
await this.page.submit(step.selector, {
|
|
164
|
-
timeout,
|
|
165
|
-
optional,
|
|
166
|
-
method: step.method ?? "enter+click",
|
|
167
|
-
waitForNavigation: step.waitForNavigation
|
|
168
|
-
});
|
|
169
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
170
|
-
}
|
|
171
|
-
case "press": {
|
|
172
|
-
if (!step.key) throw new Error("press requires key");
|
|
173
|
-
await this.page.press(step.key);
|
|
174
|
-
return {};
|
|
175
|
-
}
|
|
176
|
-
case "focus": {
|
|
177
|
-
if (!step.selector) throw new Error("focus requires selector");
|
|
178
|
-
await this.page.focus(step.selector, { timeout, optional });
|
|
179
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
180
|
-
}
|
|
181
|
-
case "hover": {
|
|
182
|
-
if (!step.selector) throw new Error("hover requires selector");
|
|
183
|
-
await this.page.hover(step.selector, { timeout, optional });
|
|
184
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
185
|
-
}
|
|
186
|
-
case "scroll": {
|
|
187
|
-
if (step.x !== void 0 || step.y !== void 0) {
|
|
188
|
-
await this.page.scroll("body", { x: step.x, y: step.y, timeout, optional });
|
|
189
|
-
return {};
|
|
190
|
-
}
|
|
191
|
-
if (!step.selector && (step.direction || step.amount !== void 0)) {
|
|
192
|
-
const amount = step.amount ?? 500;
|
|
193
|
-
const direction = step.direction ?? "down";
|
|
194
|
-
const deltaY = direction === "down" ? amount : direction === "up" ? -amount : 0;
|
|
195
|
-
const deltaX = direction === "right" ? amount : direction === "left" ? -amount : 0;
|
|
196
|
-
await this.page.evaluate(`window.scrollBy(${deltaX}, ${deltaY})`);
|
|
197
|
-
return {};
|
|
198
|
-
}
|
|
199
|
-
if (!step.selector) throw new Error("scroll requires selector, coordinates, or direction");
|
|
200
|
-
await this.page.scroll(step.selector, { timeout, optional });
|
|
201
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
202
|
-
}
|
|
203
|
-
case "wait": {
|
|
204
|
-
if (!step.selector && !step.waitFor) {
|
|
205
|
-
const delay = step.timeout ?? 1e3;
|
|
206
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
207
|
-
return {};
|
|
208
|
-
}
|
|
209
|
-
if (step.waitFor === "navigation") {
|
|
210
|
-
await this.page.waitForNavigation({ timeout, optional });
|
|
211
|
-
return {};
|
|
212
|
-
}
|
|
213
|
-
if (step.waitFor === "networkIdle") {
|
|
214
|
-
await this.page.waitForNetworkIdle({ timeout, optional });
|
|
215
|
-
return {};
|
|
216
|
-
}
|
|
217
|
-
if (!step.selector)
|
|
218
|
-
throw new Error(
|
|
219
|
-
"wait requires selector (or waitFor: navigation/networkIdle, or timeout for simple delay)"
|
|
220
|
-
);
|
|
221
|
-
await this.page.waitFor(step.selector, {
|
|
222
|
-
timeout,
|
|
223
|
-
optional,
|
|
224
|
-
state: step.waitFor ?? "visible"
|
|
225
|
-
});
|
|
226
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
227
|
-
}
|
|
228
|
-
case "snapshot": {
|
|
229
|
-
const snapshot = await this.page.snapshot();
|
|
230
|
-
return { value: snapshot };
|
|
231
|
-
}
|
|
232
|
-
case "screenshot": {
|
|
233
|
-
const data = await this.page.screenshot({
|
|
234
|
-
format: step.format,
|
|
235
|
-
quality: step.quality,
|
|
236
|
-
fullPage: step.fullPage
|
|
237
|
-
});
|
|
238
|
-
return { value: data };
|
|
239
|
-
}
|
|
240
|
-
case "evaluate": {
|
|
241
|
-
if (typeof step.value !== "string")
|
|
242
|
-
throw new Error("evaluate requires string value (expression)");
|
|
243
|
-
const result = await this.page.evaluate(step.value);
|
|
244
|
-
return { value: result };
|
|
245
|
-
}
|
|
246
|
-
case "text": {
|
|
247
|
-
const selector = Array.isArray(step.selector) ? step.selector[0] : step.selector;
|
|
248
|
-
const text = await this.page.text(selector);
|
|
249
|
-
return { text, selectorUsed: selector };
|
|
250
|
-
}
|
|
251
|
-
case "switchFrame": {
|
|
252
|
-
if (!step.selector) throw new Error("switchFrame requires selector");
|
|
253
|
-
await this.page.switchToFrame(step.selector, { timeout, optional });
|
|
254
|
-
return { selectorUsed: this.getUsedSelector(step.selector) };
|
|
255
|
-
}
|
|
256
|
-
case "switchToMain": {
|
|
257
|
-
await this.page.switchToMain();
|
|
258
|
-
return {};
|
|
259
|
-
}
|
|
260
|
-
default: {
|
|
261
|
-
const action = step.action;
|
|
262
|
-
const aliases = {
|
|
263
|
-
execute: "evaluate",
|
|
264
|
-
navigate: "goto",
|
|
265
|
-
input: "fill",
|
|
266
|
-
tap: "click",
|
|
267
|
-
go: "goto",
|
|
268
|
-
run: "evaluate",
|
|
269
|
-
capture: "screenshot",
|
|
270
|
-
inspect: "snapshot",
|
|
271
|
-
enter: "press",
|
|
272
|
-
open: "goto",
|
|
273
|
-
visit: "goto",
|
|
274
|
-
eval: "evaluate",
|
|
275
|
-
js: "evaluate",
|
|
276
|
-
snap: "snapshot",
|
|
277
|
-
frame: "switchFrame"
|
|
278
|
-
};
|
|
279
|
-
const suggestion = aliases[action.toLowerCase()];
|
|
280
|
-
const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
|
|
281
|
-
const valid = "goto, click, fill, type, select, check, uncheck, submit, press, focus, hover, scroll, wait, snapshot, screenshot, evaluate, text, switchFrame, switchToMain";
|
|
282
|
-
throw new Error(`Unknown action "${action}".${hint}
|
|
283
|
-
|
|
284
|
-
Valid actions: ${valid}`);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Get the actual selector that matched the element.
|
|
290
|
-
* Uses the last matched selector tracked by Page, falls back to first selector if unavailable.
|
|
291
|
-
*/
|
|
292
|
-
getUsedSelector(selector) {
|
|
293
|
-
const matched = this.page.getLastMatchedSelector();
|
|
294
|
-
if (matched) return matched;
|
|
295
|
-
return Array.isArray(selector) ? selector[0] : selector;
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
function addBatchToPage(page) {
|
|
299
|
-
const executor = new BatchExecutor(page);
|
|
300
|
-
return Object.assign(page, {
|
|
301
|
-
batch: (steps, options) => executor.execute(steps, options)
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// src/actions/validate.ts
|
|
306
|
-
function levenshtein(a, b) {
|
|
307
|
-
const m = a.length;
|
|
308
|
-
const n = b.length;
|
|
309
|
-
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
310
|
-
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
311
|
-
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
312
|
-
for (let i = 1; i <= m; i++) {
|
|
313
|
-
for (let j = 1; j <= n; j++) {
|
|
314
|
-
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
return dp[m][n];
|
|
318
|
-
}
|
|
319
|
-
var ACTION_ALIASES = {
|
|
320
|
-
execute: "evaluate",
|
|
321
|
-
navigate: "goto",
|
|
322
|
-
input: "fill",
|
|
323
|
-
tap: "click",
|
|
324
|
-
go: "goto",
|
|
325
|
-
run: "evaluate",
|
|
326
|
-
capture: "screenshot",
|
|
327
|
-
inspect: "snapshot",
|
|
328
|
-
enter: "press",
|
|
329
|
-
keypress: "press",
|
|
330
|
-
nav: "goto",
|
|
331
|
-
open: "goto",
|
|
332
|
-
visit: "goto",
|
|
333
|
-
browse: "goto",
|
|
334
|
-
load: "goto",
|
|
335
|
-
write: "fill",
|
|
336
|
-
set: "fill",
|
|
337
|
-
pick: "select",
|
|
338
|
-
choose: "select",
|
|
339
|
-
send: "press",
|
|
340
|
-
eval: "evaluate",
|
|
341
|
-
js: "evaluate",
|
|
342
|
-
script: "evaluate",
|
|
343
|
-
snap: "snapshot",
|
|
344
|
-
accessibility: "snapshot",
|
|
345
|
-
a11y: "snapshot",
|
|
346
|
-
image: "screenshot",
|
|
347
|
-
pic: "screenshot",
|
|
348
|
-
frame: "switchFrame",
|
|
349
|
-
iframe: "switchFrame"
|
|
350
|
-
};
|
|
351
|
-
var PROPERTY_ALIASES = {
|
|
352
|
-
expression: "value",
|
|
353
|
-
href: "url",
|
|
354
|
-
target: "selector",
|
|
355
|
-
element: "selector",
|
|
356
|
-
code: "value",
|
|
357
|
-
script: "value",
|
|
358
|
-
src: "url",
|
|
359
|
-
link: "url",
|
|
360
|
-
char: "key",
|
|
361
|
-
text: "value",
|
|
362
|
-
query: "selector",
|
|
363
|
-
el: "selector",
|
|
364
|
-
elem: "selector",
|
|
365
|
-
css: "selector",
|
|
366
|
-
xpath: "selector",
|
|
367
|
-
input: "value",
|
|
368
|
-
content: "value",
|
|
369
|
-
keys: "key",
|
|
370
|
-
button: "key",
|
|
371
|
-
address: "url",
|
|
372
|
-
page: "url",
|
|
373
|
-
path: "url"
|
|
374
|
-
};
|
|
375
|
-
var ACTION_RULES = {
|
|
376
|
-
goto: {
|
|
377
|
-
required: { url: { type: "string" } },
|
|
378
|
-
optional: {}
|
|
379
|
-
},
|
|
380
|
-
click: {
|
|
381
|
-
required: { selector: { type: "string|string[]" } },
|
|
382
|
-
optional: {
|
|
383
|
-
waitForNavigation: { type: "boolean" }
|
|
384
|
-
}
|
|
385
|
-
},
|
|
386
|
-
fill: {
|
|
387
|
-
required: { selector: { type: "string|string[]" }, value: { type: "string" } },
|
|
388
|
-
optional: {
|
|
389
|
-
blur: { type: "boolean" }
|
|
390
|
-
}
|
|
391
|
-
},
|
|
392
|
-
type: {
|
|
393
|
-
required: { selector: { type: "string|string[]" }, value: { type: "string" } },
|
|
394
|
-
optional: {
|
|
395
|
-
delay: { type: "number" }
|
|
396
|
-
}
|
|
397
|
-
},
|
|
398
|
-
select: {
|
|
399
|
-
required: {},
|
|
400
|
-
optional: {
|
|
401
|
-
selector: { type: "string|string[]" },
|
|
402
|
-
value: { type: "string|string[]" },
|
|
403
|
-
trigger: { type: "string|string[]" },
|
|
404
|
-
option: { type: "string|string[]" },
|
|
405
|
-
match: { type: "string", enum: ["text", "value", "contains"] }
|
|
406
|
-
}
|
|
407
|
-
},
|
|
408
|
-
check: {
|
|
409
|
-
required: { selector: { type: "string|string[]" } },
|
|
410
|
-
optional: {}
|
|
411
|
-
},
|
|
412
|
-
uncheck: {
|
|
413
|
-
required: { selector: { type: "string|string[]" } },
|
|
414
|
-
optional: {}
|
|
415
|
-
},
|
|
416
|
-
submit: {
|
|
417
|
-
required: { selector: { type: "string|string[]" } },
|
|
418
|
-
optional: {
|
|
419
|
-
method: { type: "string", enum: ["enter", "click", "enter+click"] },
|
|
420
|
-
waitForNavigation: { type: "boolean|auto" }
|
|
421
|
-
}
|
|
422
|
-
},
|
|
423
|
-
press: {
|
|
424
|
-
required: { key: { type: "string" } },
|
|
425
|
-
optional: {}
|
|
426
|
-
},
|
|
427
|
-
focus: {
|
|
428
|
-
required: { selector: { type: "string|string[]" } },
|
|
429
|
-
optional: {}
|
|
430
|
-
},
|
|
431
|
-
hover: {
|
|
432
|
-
required: { selector: { type: "string|string[]" } },
|
|
433
|
-
optional: {}
|
|
434
|
-
},
|
|
435
|
-
scroll: {
|
|
436
|
-
required: {},
|
|
437
|
-
optional: {
|
|
438
|
-
selector: { type: "string|string[]" },
|
|
439
|
-
x: { type: "number" },
|
|
440
|
-
y: { type: "number" },
|
|
441
|
-
direction: { type: "string", enum: ["up", "down", "left", "right"] },
|
|
442
|
-
amount: { type: "number" }
|
|
443
|
-
}
|
|
444
|
-
},
|
|
445
|
-
wait: {
|
|
446
|
-
required: {},
|
|
447
|
-
optional: {
|
|
448
|
-
selector: { type: "string|string[]" },
|
|
449
|
-
waitFor: {
|
|
450
|
-
type: "string",
|
|
451
|
-
enum: ["visible", "hidden", "attached", "detached", "navigation", "networkIdle"]
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
snapshot: {
|
|
456
|
-
required: {},
|
|
457
|
-
optional: {}
|
|
458
|
-
},
|
|
459
|
-
screenshot: {
|
|
460
|
-
required: {},
|
|
461
|
-
optional: {
|
|
462
|
-
format: { type: "string", enum: ["png", "jpeg", "webp"] },
|
|
463
|
-
quality: { type: "number" },
|
|
464
|
-
fullPage: { type: "boolean" }
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
evaluate: {
|
|
468
|
-
required: { value: { type: "string" } },
|
|
469
|
-
optional: {}
|
|
470
|
-
},
|
|
471
|
-
text: {
|
|
472
|
-
required: {},
|
|
473
|
-
optional: {
|
|
474
|
-
selector: { type: "string|string[]" }
|
|
475
|
-
}
|
|
476
|
-
},
|
|
477
|
-
switchFrame: {
|
|
478
|
-
required: { selector: { type: "string|string[]" } },
|
|
479
|
-
optional: {}
|
|
480
|
-
},
|
|
481
|
-
switchToMain: {
|
|
482
|
-
required: {},
|
|
483
|
-
optional: {}
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
var VALID_ACTIONS = Object.keys(ACTION_RULES);
|
|
487
|
-
var VALID_ACTIONS_LIST = VALID_ACTIONS.join(", ");
|
|
488
|
-
var KNOWN_STEP_FIELDS = /* @__PURE__ */ new Set([
|
|
489
|
-
"action",
|
|
490
|
-
"selector",
|
|
491
|
-
"url",
|
|
492
|
-
"value",
|
|
493
|
-
"key",
|
|
494
|
-
"waitFor",
|
|
495
|
-
"timeout",
|
|
496
|
-
"optional",
|
|
497
|
-
"method",
|
|
498
|
-
"blur",
|
|
499
|
-
"delay",
|
|
500
|
-
"waitForNavigation",
|
|
501
|
-
"trigger",
|
|
502
|
-
"option",
|
|
503
|
-
"match",
|
|
504
|
-
"x",
|
|
505
|
-
"y",
|
|
506
|
-
"direction",
|
|
507
|
-
"amount",
|
|
508
|
-
"format",
|
|
509
|
-
"quality",
|
|
510
|
-
"fullPage"
|
|
511
|
-
]);
|
|
512
|
-
function resolveAction(name) {
|
|
513
|
-
if (VALID_ACTIONS.includes(name)) {
|
|
514
|
-
return { action: name };
|
|
515
|
-
}
|
|
516
|
-
const lower = name.toLowerCase();
|
|
517
|
-
if (ACTION_ALIASES[lower]) {
|
|
518
|
-
return {
|
|
519
|
-
action: ACTION_ALIASES[lower],
|
|
520
|
-
suggestion: `Did you mean "${ACTION_ALIASES[lower]}"?`
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
let best = null;
|
|
524
|
-
let bestDist = Infinity;
|
|
525
|
-
for (const valid of VALID_ACTIONS) {
|
|
526
|
-
const dist = levenshtein(lower, valid);
|
|
527
|
-
if (dist < bestDist) {
|
|
528
|
-
bestDist = dist;
|
|
529
|
-
best = valid;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
if (best && bestDist <= 2) {
|
|
533
|
-
return { action: best, suggestion: `Did you mean "${best}"?` };
|
|
534
|
-
}
|
|
535
|
-
return null;
|
|
536
|
-
}
|
|
537
|
-
function suggestProperty(name) {
|
|
538
|
-
if (PROPERTY_ALIASES[name]) {
|
|
539
|
-
return PROPERTY_ALIASES[name];
|
|
540
|
-
}
|
|
541
|
-
let best = null;
|
|
542
|
-
let bestDist = Infinity;
|
|
543
|
-
for (const known of KNOWN_STEP_FIELDS) {
|
|
544
|
-
if (known === "action") continue;
|
|
545
|
-
const dist = levenshtein(name, known);
|
|
546
|
-
if (dist < bestDist) {
|
|
547
|
-
bestDist = dist;
|
|
548
|
-
best = known;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
if (best && bestDist <= 2) {
|
|
552
|
-
return best;
|
|
553
|
-
}
|
|
554
|
-
return void 0;
|
|
555
|
-
}
|
|
556
|
-
function checkFieldType(value, rule) {
|
|
557
|
-
switch (rule.type) {
|
|
558
|
-
case "string":
|
|
559
|
-
if (typeof value !== "string") return `expected string, got ${typeof value}`;
|
|
560
|
-
if (rule.enum && !rule.enum.includes(value)) {
|
|
561
|
-
return `must be one of: ${rule.enum.join(", ")}`;
|
|
562
|
-
}
|
|
563
|
-
return null;
|
|
564
|
-
case "string|string[]":
|
|
565
|
-
if (typeof value !== "string" && !Array.isArray(value)) {
|
|
566
|
-
return `expected string or string[], got ${typeof value}`;
|
|
567
|
-
}
|
|
568
|
-
if (Array.isArray(value) && value.some((v) => typeof v !== "string")) {
|
|
569
|
-
return "array elements must be strings";
|
|
570
|
-
}
|
|
571
|
-
return null;
|
|
572
|
-
case "number":
|
|
573
|
-
if (typeof value !== "number") return `expected number, got ${typeof value}`;
|
|
574
|
-
return null;
|
|
575
|
-
case "boolean":
|
|
576
|
-
if (typeof value !== "boolean") return `expected boolean, got ${typeof value}`;
|
|
577
|
-
return null;
|
|
578
|
-
case "boolean|auto":
|
|
579
|
-
if (typeof value !== "boolean" && value !== "auto") {
|
|
580
|
-
return `expected boolean or "auto", got ${typeof value}`;
|
|
581
|
-
}
|
|
582
|
-
return null;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
function validateSteps(steps) {
|
|
586
|
-
const errors = [];
|
|
587
|
-
for (let i = 0; i < steps.length; i++) {
|
|
588
|
-
const step = steps[i];
|
|
589
|
-
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
590
|
-
errors.push({
|
|
591
|
-
stepIndex: i,
|
|
592
|
-
field: "step",
|
|
593
|
-
message: "step must be a JSON object."
|
|
594
|
-
});
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
const obj = step;
|
|
598
|
-
if (!("action" in obj)) {
|
|
599
|
-
errors.push({
|
|
600
|
-
stepIndex: i,
|
|
601
|
-
field: "action",
|
|
602
|
-
message: 'missing required "action" field.'
|
|
603
|
-
});
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
const actionName = obj["action"];
|
|
607
|
-
if (typeof actionName !== "string") {
|
|
608
|
-
errors.push({
|
|
609
|
-
stepIndex: i,
|
|
610
|
-
field: "action",
|
|
611
|
-
message: `"action" must be a string, got ${typeof actionName}.`
|
|
612
|
-
});
|
|
613
|
-
continue;
|
|
614
|
-
}
|
|
615
|
-
const resolved = resolveAction(actionName);
|
|
616
|
-
if (!resolved) {
|
|
617
|
-
errors.push({
|
|
618
|
-
stepIndex: i,
|
|
619
|
-
field: "action",
|
|
620
|
-
message: `unknown action "${actionName}".`,
|
|
621
|
-
suggestion: `Valid actions: ${VALID_ACTIONS_LIST}`
|
|
622
|
-
});
|
|
623
|
-
continue;
|
|
624
|
-
}
|
|
625
|
-
if (resolved.suggestion) {
|
|
626
|
-
errors.push({
|
|
627
|
-
stepIndex: i,
|
|
628
|
-
field: "action",
|
|
629
|
-
message: `unknown action "${actionName}". ${resolved.suggestion}`,
|
|
630
|
-
suggestion: resolved.suggestion
|
|
631
|
-
});
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
const action = resolved.action;
|
|
635
|
-
const rule = ACTION_RULES[action];
|
|
636
|
-
for (const key of Object.keys(obj)) {
|
|
637
|
-
if (key === "action") continue;
|
|
638
|
-
if (!KNOWN_STEP_FIELDS.has(key)) {
|
|
639
|
-
const suggestion = suggestProperty(key);
|
|
640
|
-
errors.push({
|
|
641
|
-
stepIndex: i,
|
|
642
|
-
field: key,
|
|
643
|
-
message: suggestion ? `unknown property "${key}". Did you mean "${suggestion}"?` : `unknown property "${key}".`,
|
|
644
|
-
suggestion: suggestion ? `Did you mean "${suggestion}"?` : void 0
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
for (const [field, fieldRule] of Object.entries(rule.required)) {
|
|
649
|
-
if (!(field in obj) || obj[field] === void 0) {
|
|
650
|
-
errors.push({
|
|
651
|
-
stepIndex: i,
|
|
652
|
-
field,
|
|
653
|
-
message: `missing required "${field}" (${fieldRule.type}).`
|
|
654
|
-
});
|
|
655
|
-
} else {
|
|
656
|
-
const typeErr = checkFieldType(obj[field], fieldRule);
|
|
657
|
-
if (typeErr) {
|
|
658
|
-
errors.push({
|
|
659
|
-
stepIndex: i,
|
|
660
|
-
field,
|
|
661
|
-
message: `"${field}" ${typeErr}.`
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
for (const [field, fieldRule] of Object.entries(rule.optional)) {
|
|
667
|
-
if (field in obj && obj[field] !== void 0) {
|
|
668
|
-
const typeErr = checkFieldType(obj[field], fieldRule);
|
|
669
|
-
if (typeErr) {
|
|
670
|
-
errors.push({
|
|
671
|
-
stepIndex: i,
|
|
672
|
-
field,
|
|
673
|
-
message: `"${field}" ${typeErr}.`
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
if ("timeout" in obj && obj["timeout"] !== void 0) {
|
|
679
|
-
if (typeof obj["timeout"] !== "number") {
|
|
680
|
-
errors.push({
|
|
681
|
-
stepIndex: i,
|
|
682
|
-
field: "timeout",
|
|
683
|
-
message: `"timeout" expected number, got ${typeof obj["timeout"]}.`
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
if ("optional" in obj && obj["optional"] !== void 0) {
|
|
688
|
-
if (typeof obj["optional"] !== "boolean") {
|
|
689
|
-
errors.push({
|
|
690
|
-
stepIndex: i,
|
|
691
|
-
field: "optional",
|
|
692
|
-
message: `"optional" expected boolean, got ${typeof obj["optional"]}.`
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
if (action === "select") {
|
|
697
|
-
const hasNative = "selector" in obj && "value" in obj;
|
|
698
|
-
const hasCustom = "trigger" in obj && "option" in obj && "value" in obj;
|
|
699
|
-
if (!hasNative && !hasCustom) {
|
|
700
|
-
errors.push({
|
|
701
|
-
stepIndex: i,
|
|
702
|
-
field: "selector",
|
|
703
|
-
message: "select requires either (selector + value) for native select, or (trigger + option + value) for custom select."
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
return {
|
|
709
|
-
valid: errors.length === 0,
|
|
710
|
-
errors,
|
|
711
|
-
formatted() {
|
|
712
|
-
if (errors.length === 0) return "";
|
|
713
|
-
const lines = [`Validation failed (${errors.length} error${errors.length > 1 ? "s" : ""}):`];
|
|
714
|
-
for (const err of errors) {
|
|
715
|
-
const stepLabel = err.field === "action" || err.field === "step" ? `Step ${err.stepIndex}` : `Step ${err.stepIndex}`;
|
|
716
|
-
lines.push("");
|
|
717
|
-
lines.push(` ${stepLabel}: ${err.message}`);
|
|
718
|
-
if (err.suggestion && !err.message.includes(err.suggestion)) {
|
|
719
|
-
lines.push(` ${err.suggestion}`);
|
|
720
|
-
}
|
|
721
|
-
const step = steps[err.stepIndex];
|
|
722
|
-
if (step && typeof step === "object") {
|
|
723
|
-
lines.push(` Got: ${JSON.stringify(step)}`);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
const hasEvaluateError = errors.some((err) => {
|
|
727
|
-
const step = steps[err.stepIndex];
|
|
728
|
-
return step && typeof step === "object" && step["action"] === "evaluate";
|
|
729
|
-
});
|
|
730
|
-
if (hasEvaluateError) {
|
|
731
|
-
lines.push("");
|
|
732
|
-
lines.push(
|
|
733
|
-
"Tip: For JavaScript evaluation, use 'bp eval' instead \u2014 no JSON wrapping needed:"
|
|
734
|
-
);
|
|
735
|
-
lines.push(" bp eval 'your.expression.here'");
|
|
736
|
-
}
|
|
737
|
-
lines.push("");
|
|
738
|
-
lines.push(`Valid actions: ${VALID_ACTIONS_LIST}`);
|
|
739
|
-
return lines.join("\n");
|
|
740
|
-
}
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
export {
|
|
745
|
-
ElementNotFoundError,
|
|
746
|
-
TimeoutError,
|
|
747
|
-
NavigationError,
|
|
748
|
-
BatchExecutor,
|
|
749
|
-
addBatchToPage,
|
|
750
|
-
validateSteps
|
|
751
|
-
};
|