browserclaw 0.9.6 → 0.10.0
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 +72 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -4
- package/dist/index.d.ts +15 -4
- package/dist/index.js +72 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1407,6 +1407,9 @@ async function launchChrome(opts = {}) {
|
|
|
1407
1407
|
if (opts.noSandbox === true) {
|
|
1408
1408
|
args.push("--no-sandbox", "--disable-setuid-sandbox");
|
|
1409
1409
|
}
|
|
1410
|
+
if (opts.ignoreHTTPSErrors === true) {
|
|
1411
|
+
args.push("--ignore-certificate-errors");
|
|
1412
|
+
}
|
|
1410
1413
|
if (process.platform === "linux") args.push("--disable-dev-shm-usage");
|
|
1411
1414
|
const extraArgs = Array.isArray(opts.chromeArgs) ? opts.chromeArgs.filter((a) => typeof a === "string" && a.trim().length > 0) : [];
|
|
1412
1415
|
if (extraArgs.length) args.push(...extraArgs);
|
|
@@ -1730,9 +1733,11 @@ async function withPlaywrightPageCdpSession(page, fn) {
|
|
|
1730
1733
|
const CDP_SESSION_TIMEOUT_MS = 1e4;
|
|
1731
1734
|
const session = await Promise.race([
|
|
1732
1735
|
page.context().newCDPSession(page),
|
|
1733
|
-
new Promise(
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
+
new Promise((_, reject) => {
|
|
1737
|
+
setTimeout(() => {
|
|
1738
|
+
reject(new Error("newCDPSession timed out after 10s"));
|
|
1739
|
+
}, CDP_SESSION_TIMEOUT_MS);
|
|
1740
|
+
})
|
|
1736
1741
|
]);
|
|
1737
1742
|
try {
|
|
1738
1743
|
return await fn(session);
|
|
@@ -2373,7 +2378,14 @@ function toAIFriendlyError(error, selector) {
|
|
|
2373
2378
|
`Element "${selector}" is not interactable (hidden or covered). Try scrolling it into view, closing overlays, or re-snapshotting.`
|
|
2374
2379
|
);
|
|
2375
2380
|
}
|
|
2376
|
-
|
|
2381
|
+
const timeoutMatch = /Timeout (\d+)ms exceeded/.exec(message);
|
|
2382
|
+
if (timeoutMatch) {
|
|
2383
|
+
return new Error(
|
|
2384
|
+
`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.`
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
const cleaned = message.replace(/locator\([^)]*\)\./g, "").replace(/waiting for locator\([^)]*\)/g, "").trim();
|
|
2388
|
+
return new Error(cleaned || message);
|
|
2377
2389
|
}
|
|
2378
2390
|
function normalizeTimeoutMs(timeoutMs, fallback, maxMs = 12e4) {
|
|
2379
2391
|
return Math.max(500, Math.min(maxMs, timeoutMs ?? fallback));
|
|
@@ -3071,6 +3083,7 @@ function requiresInspectableBrowserNavigationRedirects(ssrfPolicy) {
|
|
|
3071
3083
|
|
|
3072
3084
|
// src/actions/interaction.ts
|
|
3073
3085
|
var MAX_CLICK_DELAY_MS = 5e3;
|
|
3086
|
+
var DEFAULT_SCROLL_TIMEOUT_MS = 2e4;
|
|
3074
3087
|
var CHECKABLE_ROLES = /* @__PURE__ */ new Set(["menuitemcheckbox", "menuitemradio", "checkbox", "switch"]);
|
|
3075
3088
|
function resolveLocator(page, resolved) {
|
|
3076
3089
|
if (resolved.ref !== void 0 && resolved.ref !== "") return refLocator(page, resolved.ref);
|
|
@@ -3085,38 +3098,29 @@ async function mouseClickViaPlaywright(opts) {
|
|
|
3085
3098
|
delay: opts.delayMs
|
|
3086
3099
|
});
|
|
3087
3100
|
}
|
|
3088
|
-
var MAX_HOLD_MS = 3e4;
|
|
3089
3101
|
async function pressAndHoldViaCdp(opts) {
|
|
3090
|
-
const holdMs = resolveBoundedDelayMs(opts.holdMs ?? 1e3, "holdMs", MAX_HOLD_MS);
|
|
3091
3102
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
3092
3103
|
ensurePageState(page);
|
|
3093
|
-
const
|
|
3094
|
-
const btn = { button: "left", clickCount: 1 };
|
|
3104
|
+
const { x, y } = opts;
|
|
3095
3105
|
await withPageScopedCdpClient({
|
|
3096
3106
|
cdpUrl: opts.cdpUrl,
|
|
3097
3107
|
page,
|
|
3098
3108
|
targetId: opts.targetId,
|
|
3099
3109
|
fn: async (send) => {
|
|
3100
|
-
await send("Input.dispatchMouseEvent", { type: "mouseMoved",
|
|
3101
|
-
await
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
});
|
|
3105
|
-
await send("Input.dispatchMouseEvent", { type: "mousePressed", ...pos, ...btn });
|
|
3106
|
-
await new Promise((r) => setTimeout(r, holdMs));
|
|
3107
|
-
await send("Input.dispatchMouseEvent", { type: "mouseReleased", ...pos, ...btn });
|
|
3108
|
-
await send("Input.dispatchTouchEvent", {
|
|
3109
|
-
type: "touchEnd",
|
|
3110
|
-
touchPoints: [{ x: pos.x, y: pos.y, id: 1 }]
|
|
3111
|
-
});
|
|
3110
|
+
await send("Input.dispatchMouseEvent", { type: "mouseMoved", x, y, button: "none" });
|
|
3111
|
+
if (opts.delay !== void 0 && opts.delay !== 0) await new Promise((r) => setTimeout(r, opts.delay));
|
|
3112
|
+
await send("Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", clickCount: 1 });
|
|
3113
|
+
if (opts.holdMs !== void 0 && opts.holdMs !== 0) await new Promise((r) => setTimeout(r, opts.holdMs));
|
|
3114
|
+
await send("Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
|
|
3112
3115
|
}
|
|
3113
3116
|
});
|
|
3114
3117
|
}
|
|
3115
3118
|
async function clickByTextViaPlaywright(opts) {
|
|
3116
3119
|
const page = await getRestoredPageForTarget(opts);
|
|
3117
3120
|
const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
|
|
3121
|
+
const locator = page.getByText(opts.text, { exact: opts.exact }).or(page.getByTitle(opts.text, { exact: opts.exact })).first();
|
|
3118
3122
|
try {
|
|
3119
|
-
await
|
|
3123
|
+
await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
3120
3124
|
} catch (err) {
|
|
3121
3125
|
throw toAIFriendlyError(err, `text="${opts.text}"`);
|
|
3122
3126
|
}
|
|
@@ -3124,13 +3128,12 @@ async function clickByTextViaPlaywright(opts) {
|
|
|
3124
3128
|
async function clickByRoleViaPlaywright(opts) {
|
|
3125
3129
|
const page = await getRestoredPageForTarget(opts);
|
|
3126
3130
|
const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
|
|
3131
|
+
const label = `role=${opts.role}${opts.name !== void 0 && opts.name !== "" ? ` name="${opts.name}"` : ""}`;
|
|
3132
|
+
const locator = page.getByRole(opts.role, { name: opts.name }).nth(opts.index ?? 0);
|
|
3127
3133
|
try {
|
|
3128
|
-
await
|
|
3134
|
+
await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
3129
3135
|
} catch (err) {
|
|
3130
|
-
throw toAIFriendlyError(
|
|
3131
|
-
err,
|
|
3132
|
-
`role=${opts.role}${opts.name !== void 0 && opts.name !== "" ? ` name="${opts.name}"` : ""}`
|
|
3133
|
-
);
|
|
3136
|
+
throw toAIFriendlyError(err, label);
|
|
3134
3137
|
}
|
|
3135
3138
|
}
|
|
3136
3139
|
async function clickViaPlaywright(opts) {
|
|
@@ -3151,7 +3154,7 @@ async function clickViaPlaywright(opts) {
|
|
|
3151
3154
|
try {
|
|
3152
3155
|
const delayMs = resolveBoundedDelayMs(opts.delayMs, "click delayMs", MAX_CLICK_DELAY_MS);
|
|
3153
3156
|
if (delayMs > 0) {
|
|
3154
|
-
await locator.hover({ timeout });
|
|
3157
|
+
await locator.hover({ timeout, force: opts.force });
|
|
3155
3158
|
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
3156
3159
|
}
|
|
3157
3160
|
let ariaCheckedBefore;
|
|
@@ -3159,9 +3162,9 @@ async function clickViaPlaywright(opts) {
|
|
|
3159
3162
|
ariaCheckedBefore = await locator.getAttribute("aria-checked", { timeout }).catch(() => void 0);
|
|
3160
3163
|
}
|
|
3161
3164
|
if (opts.doubleClick === true) {
|
|
3162
|
-
await locator.dblclick({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
3165
|
+
await locator.dblclick({ timeout, button: opts.button, modifiers: opts.modifiers, force: opts.force });
|
|
3163
3166
|
} else {
|
|
3164
|
-
await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
3167
|
+
await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers, force: opts.force });
|
|
3165
3168
|
}
|
|
3166
3169
|
if (checkableRole && opts.doubleClick !== true && ariaCheckedBefore !== void 0) {
|
|
3167
3170
|
const POLL_INTERVAL_MS = 50;
|
|
@@ -3255,7 +3258,7 @@ async function fillFormViaPlaywright(opts) {
|
|
|
3255
3258
|
if (type === "checkbox" || type === "radio") {
|
|
3256
3259
|
const checked = rawValue === true || rawValue === 1 || rawValue === "1" || rawValue === "true";
|
|
3257
3260
|
try {
|
|
3258
|
-
await locator.setChecked(checked, { timeout });
|
|
3261
|
+
await locator.setChecked(checked, { timeout, force: true });
|
|
3259
3262
|
} catch (err) {
|
|
3260
3263
|
throw toAIFriendlyError(err, ref);
|
|
3261
3264
|
}
|
|
@@ -3274,7 +3277,13 @@ async function scrollIntoViewViaPlaywright(opts) {
|
|
|
3274
3277
|
const label = resolved.ref ?? resolved.selector ?? "";
|
|
3275
3278
|
const locator = resolveLocator(page, resolved);
|
|
3276
3279
|
try {
|
|
3277
|
-
await locator.
|
|
3280
|
+
await locator.waitFor({
|
|
3281
|
+
state: "attached",
|
|
3282
|
+
timeout: normalizeTimeoutMs(opts.timeoutMs, DEFAULT_SCROLL_TIMEOUT_MS)
|
|
3283
|
+
});
|
|
3284
|
+
await locator.evaluate((el) => {
|
|
3285
|
+
el.scrollIntoView({ block: "center", behavior: "instant" });
|
|
3286
|
+
});
|
|
3278
3287
|
} catch (err) {
|
|
3279
3288
|
throw toAIFriendlyError(err, label);
|
|
3280
3289
|
}
|
|
@@ -3550,9 +3559,13 @@ async function waitForViaPlaywright(opts) {
|
|
|
3550
3559
|
if (opts.loadState !== void 0) {
|
|
3551
3560
|
await page.waitForLoadState(opts.loadState, { timeout });
|
|
3552
3561
|
}
|
|
3553
|
-
if (opts.fn !== void 0
|
|
3554
|
-
|
|
3555
|
-
|
|
3562
|
+
if (opts.fn !== void 0) {
|
|
3563
|
+
if (typeof opts.fn === "function") {
|
|
3564
|
+
await page.waitForFunction(opts.fn, void 0, { timeout });
|
|
3565
|
+
} else {
|
|
3566
|
+
const fn = opts.fn.trim();
|
|
3567
|
+
if (fn !== "") await page.waitForFunction(fn, void 0, { timeout });
|
|
3568
|
+
}
|
|
3556
3569
|
}
|
|
3557
3570
|
}
|
|
3558
3571
|
|
|
@@ -4262,6 +4275,13 @@ async function traceStopViaPlaywright(opts) {
|
|
|
4262
4275
|
}
|
|
4263
4276
|
|
|
4264
4277
|
// src/snapshot/ref-map.ts
|
|
4278
|
+
function parseStateFromSuffix(suffix) {
|
|
4279
|
+
const state = {};
|
|
4280
|
+
if (/\[disabled\]/i.test(suffix)) state.disabled = true;
|
|
4281
|
+
if (/\[checked\s*=\s*"?mixed"?\]/i.test(suffix)) state.checked = "mixed";
|
|
4282
|
+
else if (/\[checked\]/i.test(suffix)) state.checked = true;
|
|
4283
|
+
return state;
|
|
4284
|
+
}
|
|
4265
4285
|
var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
|
|
4266
4286
|
"button",
|
|
4267
4287
|
"link",
|
|
@@ -4420,7 +4440,8 @@ function buildRoleSnapshotFromAriaSnapshot(ariaSnapshot, options = {}) {
|
|
|
4420
4440
|
const ref = nextRef();
|
|
4421
4441
|
const nth = tracker.getNextIndex(role, name);
|
|
4422
4442
|
tracker.trackRef(role, name, ref);
|
|
4423
|
-
|
|
4443
|
+
const state = parseStateFromSuffix(suffix);
|
|
4444
|
+
refs[ref] = { role, name, nth, ...state };
|
|
4424
4445
|
let enhanced = `${prefix}${roleRaw}`;
|
|
4425
4446
|
if (name !== void 0 && name !== "") enhanced += ` "${name}"`;
|
|
4426
4447
|
enhanced += ` [ref=${ref}]`;
|
|
@@ -4457,7 +4478,8 @@ function buildRoleSnapshotFromAriaSnapshot(ariaSnapshot, options = {}) {
|
|
|
4457
4478
|
const ref = nextRef();
|
|
4458
4479
|
const nth = tracker.getNextIndex(role, name);
|
|
4459
4480
|
tracker.trackRef(role, name, ref);
|
|
4460
|
-
|
|
4481
|
+
const state = parseStateFromSuffix(suffix);
|
|
4482
|
+
refs[ref] = { role, name, nth, ...state };
|
|
4461
4483
|
let enhanced = `${prefix}${roleRaw}`;
|
|
4462
4484
|
if (name !== "") enhanced += ` "${name}"`;
|
|
4463
4485
|
enhanced += ` [ref=${ref}]`;
|
|
@@ -4495,12 +4517,13 @@ function buildRoleSnapshotFromAiSnapshot(aiSnapshot, options = {}) {
|
|
|
4495
4517
|
if (!INTERACTIVE_ROLES.has(role)) continue;
|
|
4496
4518
|
const ref = parseAiSnapshotRef(suffix);
|
|
4497
4519
|
const prefix = /^(\s*-\s*)/.exec(line)?.[1] ?? "";
|
|
4520
|
+
const state = parseStateFromSuffix(suffix);
|
|
4498
4521
|
if (ref !== null) {
|
|
4499
|
-
refs[ref] = { role, ...name !== void 0 && name !== "" ? { name } : {} };
|
|
4522
|
+
refs[ref] = { role, ...name !== void 0 && name !== "" ? { name } : {}, ...state };
|
|
4500
4523
|
out2.push(`${prefix}${roleRaw}${name !== void 0 && name !== "" ? ` "${name}"` : ""}${suffix}`);
|
|
4501
4524
|
} else {
|
|
4502
4525
|
const generatedRef = nextInteractiveRef();
|
|
4503
|
-
refs[generatedRef] = { role, ...name !== void 0 && name !== "" ? { name } : {} };
|
|
4526
|
+
refs[generatedRef] = { role, ...name !== void 0 && name !== "" ? { name } : {}, ...state };
|
|
4504
4527
|
let enhanced = `${prefix}${roleRaw}`;
|
|
4505
4528
|
if (name !== void 0 && name !== "") enhanced += ` "${name}"`;
|
|
4506
4529
|
enhanced += ` [ref=${generatedRef}]`;
|
|
@@ -4538,12 +4561,13 @@ function buildRoleSnapshotFromAiSnapshot(aiSnapshot, options = {}) {
|
|
|
4538
4561
|
const isStructural = STRUCTURAL_ROLES.has(role);
|
|
4539
4562
|
if (options.compact === true && isStructural && name === "") continue;
|
|
4540
4563
|
const ref = parseAiSnapshotRef(suffix);
|
|
4564
|
+
const state = parseStateFromSuffix(suffix);
|
|
4541
4565
|
if (ref !== null) {
|
|
4542
|
-
refs[ref] = { role, ...name !== "" ? { name } : {} };
|
|
4566
|
+
refs[ref] = { role, ...name !== "" ? { name } : {}, ...state };
|
|
4543
4567
|
out.push(line);
|
|
4544
4568
|
} else if (INTERACTIVE_ROLES.has(role)) {
|
|
4545
4569
|
const generatedRef = nextGeneratedRef();
|
|
4546
|
-
refs[generatedRef] = { role, ...name !== "" ? { name } : {} };
|
|
4570
|
+
refs[generatedRef] = { role, ...name !== "" ? { name } : {}, ...state };
|
|
4547
4571
|
let enhanced = `${prefix}${roleRaw}`;
|
|
4548
4572
|
if (name !== "") enhanced += ` "${name}"`;
|
|
4549
4573
|
enhanced += ` [ref=${generatedRef}]`;
|
|
@@ -4909,7 +4933,8 @@ var CrawlPage = class {
|
|
|
4909
4933
|
button: opts?.button,
|
|
4910
4934
|
modifiers: opts?.modifiers,
|
|
4911
4935
|
delayMs: opts?.delayMs,
|
|
4912
|
-
timeoutMs: opts?.timeoutMs
|
|
4936
|
+
timeoutMs: opts?.timeoutMs,
|
|
4937
|
+
force: opts?.force
|
|
4913
4938
|
});
|
|
4914
4939
|
}
|
|
4915
4940
|
/**
|
|
@@ -4935,7 +4960,8 @@ var CrawlPage = class {
|
|
|
4935
4960
|
button: opts?.button,
|
|
4936
4961
|
modifiers: opts?.modifiers,
|
|
4937
4962
|
delayMs: opts?.delayMs,
|
|
4938
|
-
timeoutMs: opts?.timeoutMs
|
|
4963
|
+
timeoutMs: opts?.timeoutMs,
|
|
4964
|
+
force: opts?.force
|
|
4939
4965
|
});
|
|
4940
4966
|
}
|
|
4941
4967
|
/**
|
|
@@ -4974,11 +5000,11 @@ var CrawlPage = class {
|
|
|
4974
5000
|
*
|
|
4975
5001
|
* @param x - X coordinate in CSS pixels
|
|
4976
5002
|
* @param y - Y coordinate in CSS pixels
|
|
4977
|
-
* @param opts - Options (
|
|
5003
|
+
* @param opts - Options (delay: ms before press, holdMs: hold duration)
|
|
4978
5004
|
*
|
|
4979
5005
|
* @example
|
|
4980
5006
|
* ```ts
|
|
4981
|
-
* await page.pressAndHold(400, 300, { holdMs: 5000 });
|
|
5007
|
+
* await page.pressAndHold(400, 300, { delay: 150, holdMs: 5000 });
|
|
4982
5008
|
* ```
|
|
4983
5009
|
*/
|
|
4984
5010
|
async pressAndHold(x, y, opts) {
|
|
@@ -4987,6 +5013,7 @@ var CrawlPage = class {
|
|
|
4987
5013
|
targetId: this.targetId,
|
|
4988
5014
|
x,
|
|
4989
5015
|
y,
|
|
5016
|
+
delay: opts?.delay,
|
|
4990
5017
|
holdMs: opts?.holdMs
|
|
4991
5018
|
});
|
|
4992
5019
|
}
|
|
@@ -5037,6 +5064,7 @@ var CrawlPage = class {
|
|
|
5037
5064
|
targetId: this.targetId,
|
|
5038
5065
|
role,
|
|
5039
5066
|
name,
|
|
5067
|
+
index: opts?.index,
|
|
5040
5068
|
button: opts?.button,
|
|
5041
5069
|
modifiers: opts?.modifiers,
|
|
5042
5070
|
timeoutMs: opts?.timeoutMs
|