browserclaw 0.9.7 → 0.10.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/index.cjs +58 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +58 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -184,6 +184,8 @@ interface LaunchOptions {
|
|
|
184
184
|
profileColor?: string;
|
|
185
185
|
/** Additional Chrome command-line arguments (e.g. `['--start-maximized']`). */
|
|
186
186
|
chromeArgs?: string[];
|
|
187
|
+
/** Ignore HTTPS certificate errors (e.g. expired local dev certs). Default: `false` */
|
|
188
|
+
ignoreHTTPSErrors?: boolean;
|
|
187
189
|
/**
|
|
188
190
|
* SSRF policy controlling which URLs navigation is allowed to reach.
|
|
189
191
|
* Defaults to trusted-network mode (private/internal addresses allowed).
|
|
@@ -249,6 +251,10 @@ interface RoleRefInfo {
|
|
|
249
251
|
name?: string;
|
|
250
252
|
/** Disambiguation index when multiple elements share the same role + name */
|
|
251
253
|
nth?: number;
|
|
254
|
+
/** Whether the element is disabled (from `[disabled]` or `aria-disabled`) */
|
|
255
|
+
disabled?: boolean;
|
|
256
|
+
/** Whether a checkbox/radio/switch is checked */
|
|
257
|
+
checked?: boolean | 'mixed';
|
|
252
258
|
}
|
|
253
259
|
/** Map of ref IDs (e.g. `'e1'`, `'e2'`) to their element information. */
|
|
254
260
|
type RoleRefs = Record<string, RoleRefInfo>;
|
|
@@ -375,6 +381,8 @@ interface ClickOptions {
|
|
|
375
381
|
delayMs?: number;
|
|
376
382
|
/** Timeout in milliseconds. Default: `8000` */
|
|
377
383
|
timeoutMs?: number;
|
|
384
|
+
/** Force click even if element is hidden or covered. Dispatches the event regardless of visibility. */
|
|
385
|
+
force?: boolean;
|
|
378
386
|
}
|
|
379
387
|
/** Options for type actions. */
|
|
380
388
|
interface TypeOptions {
|
|
@@ -402,8 +410,8 @@ interface WaitOptions {
|
|
|
402
410
|
url?: string;
|
|
403
411
|
/** Wait for a specific page load state */
|
|
404
412
|
loadState?: 'load' | 'domcontentloaded' | 'networkidle';
|
|
405
|
-
/** Wait until a JavaScript function returns truthy (evaluated in browser context) */
|
|
406
|
-
fn?: string;
|
|
413
|
+
/** Wait until a JavaScript function returns truthy (evaluated in browser context). Accepts a string or a serializable function. */
|
|
414
|
+
fn?: string | (() => unknown);
|
|
407
415
|
/** Timeout for each condition in milliseconds. Default: `20000` */
|
|
408
416
|
timeoutMs?: number;
|
|
409
417
|
}
|
|
@@ -774,6 +782,7 @@ declare class CrawlPage {
|
|
|
774
782
|
* ```
|
|
775
783
|
*/
|
|
776
784
|
clickByRole(role: string, name?: string, opts?: {
|
|
785
|
+
index?: number;
|
|
777
786
|
button?: 'left' | 'right' | 'middle';
|
|
778
787
|
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[];
|
|
779
788
|
timeoutMs?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -184,6 +184,8 @@ interface LaunchOptions {
|
|
|
184
184
|
profileColor?: string;
|
|
185
185
|
/** Additional Chrome command-line arguments (e.g. `['--start-maximized']`). */
|
|
186
186
|
chromeArgs?: string[];
|
|
187
|
+
/** Ignore HTTPS certificate errors (e.g. expired local dev certs). Default: `false` */
|
|
188
|
+
ignoreHTTPSErrors?: boolean;
|
|
187
189
|
/**
|
|
188
190
|
* SSRF policy controlling which URLs navigation is allowed to reach.
|
|
189
191
|
* Defaults to trusted-network mode (private/internal addresses allowed).
|
|
@@ -249,6 +251,10 @@ interface RoleRefInfo {
|
|
|
249
251
|
name?: string;
|
|
250
252
|
/** Disambiguation index when multiple elements share the same role + name */
|
|
251
253
|
nth?: number;
|
|
254
|
+
/** Whether the element is disabled (from `[disabled]` or `aria-disabled`) */
|
|
255
|
+
disabled?: boolean;
|
|
256
|
+
/** Whether a checkbox/radio/switch is checked */
|
|
257
|
+
checked?: boolean | 'mixed';
|
|
252
258
|
}
|
|
253
259
|
/** Map of ref IDs (e.g. `'e1'`, `'e2'`) to their element information. */
|
|
254
260
|
type RoleRefs = Record<string, RoleRefInfo>;
|
|
@@ -375,6 +381,8 @@ interface ClickOptions {
|
|
|
375
381
|
delayMs?: number;
|
|
376
382
|
/** Timeout in milliseconds. Default: `8000` */
|
|
377
383
|
timeoutMs?: number;
|
|
384
|
+
/** Force click even if element is hidden or covered. Dispatches the event regardless of visibility. */
|
|
385
|
+
force?: boolean;
|
|
378
386
|
}
|
|
379
387
|
/** Options for type actions. */
|
|
380
388
|
interface TypeOptions {
|
|
@@ -402,8 +410,8 @@ interface WaitOptions {
|
|
|
402
410
|
url?: string;
|
|
403
411
|
/** Wait for a specific page load state */
|
|
404
412
|
loadState?: 'load' | 'domcontentloaded' | 'networkidle';
|
|
405
|
-
/** Wait until a JavaScript function returns truthy (evaluated in browser context) */
|
|
406
|
-
fn?: string;
|
|
413
|
+
/** Wait until a JavaScript function returns truthy (evaluated in browser context). Accepts a string or a serializable function. */
|
|
414
|
+
fn?: string | (() => unknown);
|
|
407
415
|
/** Timeout for each condition in milliseconds. Default: `20000` */
|
|
408
416
|
timeoutMs?: number;
|
|
409
417
|
}
|
|
@@ -774,6 +782,7 @@ declare class CrawlPage {
|
|
|
774
782
|
* ```
|
|
775
783
|
*/
|
|
776
784
|
clickByRole(role: string, name?: string, opts?: {
|
|
785
|
+
index?: number;
|
|
777
786
|
button?: 'left' | 'right' | 'middle';
|
|
778
787
|
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[];
|
|
779
788
|
timeoutMs?: number;
|
package/dist/index.js
CHANGED
|
@@ -1396,6 +1396,9 @@ async function launchChrome(opts = {}) {
|
|
|
1396
1396
|
if (opts.noSandbox === true) {
|
|
1397
1397
|
args.push("--no-sandbox", "--disable-setuid-sandbox");
|
|
1398
1398
|
}
|
|
1399
|
+
if (opts.ignoreHTTPSErrors === true) {
|
|
1400
|
+
args.push("--ignore-certificate-errors");
|
|
1401
|
+
}
|
|
1399
1402
|
if (process.platform === "linux") args.push("--disable-dev-shm-usage");
|
|
1400
1403
|
const extraArgs = Array.isArray(opts.chromeArgs) ? opts.chromeArgs.filter((a) => typeof a === "string" && a.trim().length > 0) : [];
|
|
1401
1404
|
if (extraArgs.length) args.push(...extraArgs);
|
|
@@ -2364,7 +2367,14 @@ function toAIFriendlyError(error, selector) {
|
|
|
2364
2367
|
`Element "${selector}" is not interactable (hidden or covered). Try scrolling it into view, closing overlays, or re-snapshotting.`
|
|
2365
2368
|
);
|
|
2366
2369
|
}
|
|
2367
|
-
|
|
2370
|
+
const timeoutMatch = /Timeout (\d+)ms exceeded/.exec(message);
|
|
2371
|
+
if (timeoutMatch) {
|
|
2372
|
+
return new Error(
|
|
2373
|
+
`Element "${selector}" timed out after ${timeoutMatch[1]}ms \u2014 element may be hidden or not interactable. Run a new snapshot to see current page elements.`
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
const cleaned = message.replace(/locator\([^)]*\)\./g, "").replace(/waiting for locator\([^)]*\)/g, "").trim();
|
|
2377
|
+
return new Error(cleaned || message);
|
|
2368
2378
|
}
|
|
2369
2379
|
function normalizeTimeoutMs(timeoutMs, fallback, maxMs = 12e4) {
|
|
2370
2380
|
return Math.max(500, Math.min(maxMs, timeoutMs ?? fallback));
|
|
@@ -3062,6 +3072,7 @@ function requiresInspectableBrowserNavigationRedirects(ssrfPolicy) {
|
|
|
3062
3072
|
|
|
3063
3073
|
// src/actions/interaction.ts
|
|
3064
3074
|
var MAX_CLICK_DELAY_MS = 5e3;
|
|
3075
|
+
var DEFAULT_SCROLL_TIMEOUT_MS = 2e4;
|
|
3065
3076
|
var CHECKABLE_ROLES = /* @__PURE__ */ new Set(["menuitemcheckbox", "menuitemradio", "checkbox", "switch"]);
|
|
3066
3077
|
function resolveLocator(page, resolved) {
|
|
3067
3078
|
if (resolved.ref !== void 0 && resolved.ref !== "") return refLocator(page, resolved.ref);
|
|
@@ -3096,8 +3107,9 @@ async function pressAndHoldViaCdp(opts) {
|
|
|
3096
3107
|
async function clickByTextViaPlaywright(opts) {
|
|
3097
3108
|
const page = await getRestoredPageForTarget(opts);
|
|
3098
3109
|
const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
|
|
3110
|
+
const locator = page.getByText(opts.text, { exact: opts.exact }).and(page.locator(":visible")).or(page.getByTitle(opts.text, { exact: opts.exact })).first();
|
|
3099
3111
|
try {
|
|
3100
|
-
await
|
|
3112
|
+
await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
3101
3113
|
} catch (err) {
|
|
3102
3114
|
throw toAIFriendlyError(err, `text="${opts.text}"`);
|
|
3103
3115
|
}
|
|
@@ -3105,13 +3117,12 @@ async function clickByTextViaPlaywright(opts) {
|
|
|
3105
3117
|
async function clickByRoleViaPlaywright(opts) {
|
|
3106
3118
|
const page = await getRestoredPageForTarget(opts);
|
|
3107
3119
|
const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
|
|
3120
|
+
const label = `role=${opts.role}${opts.name !== void 0 && opts.name !== "" ? ` name="${opts.name}"` : ""}`;
|
|
3121
|
+
const locator = page.getByRole(opts.role, { name: opts.name }).nth(opts.index ?? 0);
|
|
3108
3122
|
try {
|
|
3109
|
-
await
|
|
3123
|
+
await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
3110
3124
|
} catch (err) {
|
|
3111
|
-
throw toAIFriendlyError(
|
|
3112
|
-
err,
|
|
3113
|
-
`role=${opts.role}${opts.name !== void 0 && opts.name !== "" ? ` name="${opts.name}"` : ""}`
|
|
3114
|
-
);
|
|
3125
|
+
throw toAIFriendlyError(err, label);
|
|
3115
3126
|
}
|
|
3116
3127
|
}
|
|
3117
3128
|
async function clickViaPlaywright(opts) {
|
|
@@ -3132,7 +3143,7 @@ async function clickViaPlaywright(opts) {
|
|
|
3132
3143
|
try {
|
|
3133
3144
|
const delayMs = resolveBoundedDelayMs(opts.delayMs, "click delayMs", MAX_CLICK_DELAY_MS);
|
|
3134
3145
|
if (delayMs > 0) {
|
|
3135
|
-
await locator.hover({ timeout });
|
|
3146
|
+
await locator.hover({ timeout, force: opts.force });
|
|
3136
3147
|
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
3137
3148
|
}
|
|
3138
3149
|
let ariaCheckedBefore;
|
|
@@ -3140,9 +3151,9 @@ async function clickViaPlaywright(opts) {
|
|
|
3140
3151
|
ariaCheckedBefore = await locator.getAttribute("aria-checked", { timeout }).catch(() => void 0);
|
|
3141
3152
|
}
|
|
3142
3153
|
if (opts.doubleClick === true) {
|
|
3143
|
-
await locator.dblclick({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
3154
|
+
await locator.dblclick({ timeout, button: opts.button, modifiers: opts.modifiers, force: opts.force });
|
|
3144
3155
|
} else {
|
|
3145
|
-
await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
3156
|
+
await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers, force: opts.force });
|
|
3146
3157
|
}
|
|
3147
3158
|
if (checkableRole && opts.doubleClick !== true && ariaCheckedBefore !== void 0) {
|
|
3148
3159
|
const POLL_INTERVAL_MS = 50;
|
|
@@ -3236,7 +3247,7 @@ async function fillFormViaPlaywright(opts) {
|
|
|
3236
3247
|
if (type === "checkbox" || type === "radio") {
|
|
3237
3248
|
const checked = rawValue === true || rawValue === 1 || rawValue === "1" || rawValue === "true";
|
|
3238
3249
|
try {
|
|
3239
|
-
await locator.setChecked(checked, { timeout });
|
|
3250
|
+
await locator.setChecked(checked, { timeout, force: true });
|
|
3240
3251
|
} catch (err) {
|
|
3241
3252
|
throw toAIFriendlyError(err, ref);
|
|
3242
3253
|
}
|
|
@@ -3255,7 +3266,13 @@ async function scrollIntoViewViaPlaywright(opts) {
|
|
|
3255
3266
|
const label = resolved.ref ?? resolved.selector ?? "";
|
|
3256
3267
|
const locator = resolveLocator(page, resolved);
|
|
3257
3268
|
try {
|
|
3258
|
-
await locator.
|
|
3269
|
+
await locator.waitFor({
|
|
3270
|
+
state: "attached",
|
|
3271
|
+
timeout: normalizeTimeoutMs(opts.timeoutMs, DEFAULT_SCROLL_TIMEOUT_MS)
|
|
3272
|
+
});
|
|
3273
|
+
await locator.evaluate((el) => {
|
|
3274
|
+
el.scrollIntoView({ block: "center", behavior: "instant" });
|
|
3275
|
+
});
|
|
3259
3276
|
} catch (err) {
|
|
3260
3277
|
throw toAIFriendlyError(err, label);
|
|
3261
3278
|
}
|
|
@@ -3531,9 +3548,13 @@ async function waitForViaPlaywright(opts) {
|
|
|
3531
3548
|
if (opts.loadState !== void 0) {
|
|
3532
3549
|
await page.waitForLoadState(opts.loadState, { timeout });
|
|
3533
3550
|
}
|
|
3534
|
-
if (opts.fn !== void 0
|
|
3535
|
-
|
|
3536
|
-
|
|
3551
|
+
if (opts.fn !== void 0) {
|
|
3552
|
+
if (typeof opts.fn === "function") {
|
|
3553
|
+
await page.waitForFunction(opts.fn, { timeout });
|
|
3554
|
+
} else {
|
|
3555
|
+
const fn = opts.fn.trim();
|
|
3556
|
+
if (fn !== "") await page.waitForFunction(fn, void 0, { timeout });
|
|
3557
|
+
}
|
|
3537
3558
|
}
|
|
3538
3559
|
}
|
|
3539
3560
|
|
|
@@ -4243,6 +4264,13 @@ async function traceStopViaPlaywright(opts) {
|
|
|
4243
4264
|
}
|
|
4244
4265
|
|
|
4245
4266
|
// src/snapshot/ref-map.ts
|
|
4267
|
+
function parseStateFromSuffix(suffix) {
|
|
4268
|
+
const state = {};
|
|
4269
|
+
if (/\[disabled\]/i.test(suffix)) state.disabled = true;
|
|
4270
|
+
if (/\[checked\s*=\s*"?mixed"?\]/i.test(suffix)) state.checked = "mixed";
|
|
4271
|
+
else if (/\[checked\]/i.test(suffix)) state.checked = true;
|
|
4272
|
+
return state;
|
|
4273
|
+
}
|
|
4246
4274
|
var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
|
|
4247
4275
|
"button",
|
|
4248
4276
|
"link",
|
|
@@ -4401,7 +4429,8 @@ function buildRoleSnapshotFromAriaSnapshot(ariaSnapshot, options = {}) {
|
|
|
4401
4429
|
const ref = nextRef();
|
|
4402
4430
|
const nth = tracker.getNextIndex(role, name);
|
|
4403
4431
|
tracker.trackRef(role, name, ref);
|
|
4404
|
-
|
|
4432
|
+
const state = parseStateFromSuffix(suffix);
|
|
4433
|
+
refs[ref] = { role, name, nth, ...state };
|
|
4405
4434
|
let enhanced = `${prefix}${roleRaw}`;
|
|
4406
4435
|
if (name !== void 0 && name !== "") enhanced += ` "${name}"`;
|
|
4407
4436
|
enhanced += ` [ref=${ref}]`;
|
|
@@ -4438,7 +4467,8 @@ function buildRoleSnapshotFromAriaSnapshot(ariaSnapshot, options = {}) {
|
|
|
4438
4467
|
const ref = nextRef();
|
|
4439
4468
|
const nth = tracker.getNextIndex(role, name);
|
|
4440
4469
|
tracker.trackRef(role, name, ref);
|
|
4441
|
-
|
|
4470
|
+
const state = parseStateFromSuffix(suffix);
|
|
4471
|
+
refs[ref] = { role, name, nth, ...state };
|
|
4442
4472
|
let enhanced = `${prefix}${roleRaw}`;
|
|
4443
4473
|
if (name !== "") enhanced += ` "${name}"`;
|
|
4444
4474
|
enhanced += ` [ref=${ref}]`;
|
|
@@ -4476,12 +4506,13 @@ function buildRoleSnapshotFromAiSnapshot(aiSnapshot, options = {}) {
|
|
|
4476
4506
|
if (!INTERACTIVE_ROLES.has(role)) continue;
|
|
4477
4507
|
const ref = parseAiSnapshotRef(suffix);
|
|
4478
4508
|
const prefix = /^(\s*-\s*)/.exec(line)?.[1] ?? "";
|
|
4509
|
+
const state = parseStateFromSuffix(suffix);
|
|
4479
4510
|
if (ref !== null) {
|
|
4480
|
-
refs[ref] = { role, ...name !== void 0 && name !== "" ? { name } : {} };
|
|
4511
|
+
refs[ref] = { role, ...name !== void 0 && name !== "" ? { name } : {}, ...state };
|
|
4481
4512
|
out2.push(`${prefix}${roleRaw}${name !== void 0 && name !== "" ? ` "${name}"` : ""}${suffix}`);
|
|
4482
4513
|
} else {
|
|
4483
4514
|
const generatedRef = nextInteractiveRef();
|
|
4484
|
-
refs[generatedRef] = { role, ...name !== void 0 && name !== "" ? { name } : {} };
|
|
4515
|
+
refs[generatedRef] = { role, ...name !== void 0 && name !== "" ? { name } : {}, ...state };
|
|
4485
4516
|
let enhanced = `${prefix}${roleRaw}`;
|
|
4486
4517
|
if (name !== void 0 && name !== "") enhanced += ` "${name}"`;
|
|
4487
4518
|
enhanced += ` [ref=${generatedRef}]`;
|
|
@@ -4519,12 +4550,13 @@ function buildRoleSnapshotFromAiSnapshot(aiSnapshot, options = {}) {
|
|
|
4519
4550
|
const isStructural = STRUCTURAL_ROLES.has(role);
|
|
4520
4551
|
if (options.compact === true && isStructural && name === "") continue;
|
|
4521
4552
|
const ref = parseAiSnapshotRef(suffix);
|
|
4553
|
+
const state = parseStateFromSuffix(suffix);
|
|
4522
4554
|
if (ref !== null) {
|
|
4523
|
-
refs[ref] = { role, ...name !== "" ? { name } : {} };
|
|
4555
|
+
refs[ref] = { role, ...name !== "" ? { name } : {}, ...state };
|
|
4524
4556
|
out.push(line);
|
|
4525
4557
|
} else if (INTERACTIVE_ROLES.has(role)) {
|
|
4526
4558
|
const generatedRef = nextGeneratedRef();
|
|
4527
|
-
refs[generatedRef] = { role, ...name !== "" ? { name } : {} };
|
|
4559
|
+
refs[generatedRef] = { role, ...name !== "" ? { name } : {}, ...state };
|
|
4528
4560
|
let enhanced = `${prefix}${roleRaw}`;
|
|
4529
4561
|
if (name !== "") enhanced += ` "${name}"`;
|
|
4530
4562
|
enhanced += ` [ref=${generatedRef}]`;
|
|
@@ -4890,7 +4922,8 @@ var CrawlPage = class {
|
|
|
4890
4922
|
button: opts?.button,
|
|
4891
4923
|
modifiers: opts?.modifiers,
|
|
4892
4924
|
delayMs: opts?.delayMs,
|
|
4893
|
-
timeoutMs: opts?.timeoutMs
|
|
4925
|
+
timeoutMs: opts?.timeoutMs,
|
|
4926
|
+
force: opts?.force
|
|
4894
4927
|
});
|
|
4895
4928
|
}
|
|
4896
4929
|
/**
|
|
@@ -4916,7 +4949,8 @@ var CrawlPage = class {
|
|
|
4916
4949
|
button: opts?.button,
|
|
4917
4950
|
modifiers: opts?.modifiers,
|
|
4918
4951
|
delayMs: opts?.delayMs,
|
|
4919
|
-
timeoutMs: opts?.timeoutMs
|
|
4952
|
+
timeoutMs: opts?.timeoutMs,
|
|
4953
|
+
force: opts?.force
|
|
4920
4954
|
});
|
|
4921
4955
|
}
|
|
4922
4956
|
/**
|
|
@@ -5019,6 +5053,7 @@ var CrawlPage = class {
|
|
|
5019
5053
|
targetId: this.targetId,
|
|
5020
5054
|
role,
|
|
5021
5055
|
name,
|
|
5056
|
+
index: opts?.index,
|
|
5022
5057
|
button: opts?.button,
|
|
5023
5058
|
modifiers: opts?.modifiers,
|
|
5024
5059
|
timeoutMs: opts?.timeoutMs
|