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 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);
@@ -2375,7 +2378,14 @@ function toAIFriendlyError(error, selector) {
2375
2378
  `Element "${selector}" is not interactable (hidden or covered). Try scrolling it into view, closing overlays, or re-snapshotting.`
2376
2379
  );
2377
2380
  }
2378
- return error instanceof Error ? error : new Error(message);
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);
2379
2389
  }
2380
2390
  function normalizeTimeoutMs(timeoutMs, fallback, maxMs = 12e4) {
2381
2391
  return Math.max(500, Math.min(maxMs, timeoutMs ?? fallback));
@@ -3073,6 +3083,7 @@ function requiresInspectableBrowserNavigationRedirects(ssrfPolicy) {
3073
3083
 
3074
3084
  // src/actions/interaction.ts
3075
3085
  var MAX_CLICK_DELAY_MS = 5e3;
3086
+ var DEFAULT_SCROLL_TIMEOUT_MS = 2e4;
3076
3087
  var CHECKABLE_ROLES = /* @__PURE__ */ new Set(["menuitemcheckbox", "menuitemradio", "checkbox", "switch"]);
3077
3088
  function resolveLocator(page, resolved) {
3078
3089
  if (resolved.ref !== void 0 && resolved.ref !== "") return refLocator(page, resolved.ref);
@@ -3107,8 +3118,9 @@ async function pressAndHoldViaCdp(opts) {
3107
3118
  async function clickByTextViaPlaywright(opts) {
3108
3119
  const page = await getRestoredPageForTarget(opts);
3109
3120
  const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
3121
+ const locator = page.getByText(opts.text, { exact: opts.exact }).and(page.locator(":visible")).or(page.getByTitle(opts.text, { exact: opts.exact })).first();
3110
3122
  try {
3111
- await page.getByText(opts.text, { exact: opts.exact }).click({ timeout, button: opts.button, modifiers: opts.modifiers });
3123
+ await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
3112
3124
  } catch (err) {
3113
3125
  throw toAIFriendlyError(err, `text="${opts.text}"`);
3114
3126
  }
@@ -3116,13 +3128,12 @@ async function clickByTextViaPlaywright(opts) {
3116
3128
  async function clickByRoleViaPlaywright(opts) {
3117
3129
  const page = await getRestoredPageForTarget(opts);
3118
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);
3119
3133
  try {
3120
- await page.getByRole(opts.role, { name: opts.name }).click({ timeout, button: opts.button, modifiers: opts.modifiers });
3134
+ await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
3121
3135
  } catch (err) {
3122
- throw toAIFriendlyError(
3123
- err,
3124
- `role=${opts.role}${opts.name !== void 0 && opts.name !== "" ? ` name="${opts.name}"` : ""}`
3125
- );
3136
+ throw toAIFriendlyError(err, label);
3126
3137
  }
3127
3138
  }
3128
3139
  async function clickViaPlaywright(opts) {
@@ -3143,7 +3154,7 @@ async function clickViaPlaywright(opts) {
3143
3154
  try {
3144
3155
  const delayMs = resolveBoundedDelayMs(opts.delayMs, "click delayMs", MAX_CLICK_DELAY_MS);
3145
3156
  if (delayMs > 0) {
3146
- await locator.hover({ timeout });
3157
+ await locator.hover({ timeout, force: opts.force });
3147
3158
  await new Promise((resolve2) => setTimeout(resolve2, delayMs));
3148
3159
  }
3149
3160
  let ariaCheckedBefore;
@@ -3151,9 +3162,9 @@ async function clickViaPlaywright(opts) {
3151
3162
  ariaCheckedBefore = await locator.getAttribute("aria-checked", { timeout }).catch(() => void 0);
3152
3163
  }
3153
3164
  if (opts.doubleClick === true) {
3154
- await locator.dblclick({ timeout, button: opts.button, modifiers: opts.modifiers });
3165
+ await locator.dblclick({ timeout, button: opts.button, modifiers: opts.modifiers, force: opts.force });
3155
3166
  } else {
3156
- await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
3167
+ await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers, force: opts.force });
3157
3168
  }
3158
3169
  if (checkableRole && opts.doubleClick !== true && ariaCheckedBefore !== void 0) {
3159
3170
  const POLL_INTERVAL_MS = 50;
@@ -3247,7 +3258,7 @@ async function fillFormViaPlaywright(opts) {
3247
3258
  if (type === "checkbox" || type === "radio") {
3248
3259
  const checked = rawValue === true || rawValue === 1 || rawValue === "1" || rawValue === "true";
3249
3260
  try {
3250
- await locator.setChecked(checked, { timeout });
3261
+ await locator.setChecked(checked, { timeout, force: true });
3251
3262
  } catch (err) {
3252
3263
  throw toAIFriendlyError(err, ref);
3253
3264
  }
@@ -3266,7 +3277,13 @@ async function scrollIntoViewViaPlaywright(opts) {
3266
3277
  const label = resolved.ref ?? resolved.selector ?? "";
3267
3278
  const locator = resolveLocator(page, resolved);
3268
3279
  try {
3269
- await locator.scrollIntoViewIfNeeded({ timeout: normalizeTimeoutMs(opts.timeoutMs, 2e4) });
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
+ });
3270
3287
  } catch (err) {
3271
3288
  throw toAIFriendlyError(err, label);
3272
3289
  }
@@ -3542,9 +3559,13 @@ async function waitForViaPlaywright(opts) {
3542
3559
  if (opts.loadState !== void 0) {
3543
3560
  await page.waitForLoadState(opts.loadState, { timeout });
3544
3561
  }
3545
- if (opts.fn !== void 0 && opts.fn !== "") {
3546
- const fn = opts.fn.trim();
3547
- if (fn !== "") await page.waitForFunction(fn, void 0, { timeout });
3562
+ if (opts.fn !== void 0) {
3563
+ if (typeof opts.fn === "function") {
3564
+ await page.waitForFunction(opts.fn, { timeout });
3565
+ } else {
3566
+ const fn = opts.fn.trim();
3567
+ if (fn !== "") await page.waitForFunction(fn, void 0, { timeout });
3568
+ }
3548
3569
  }
3549
3570
  }
3550
3571
 
@@ -4254,6 +4275,13 @@ async function traceStopViaPlaywright(opts) {
4254
4275
  }
4255
4276
 
4256
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
+ }
4257
4285
  var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
4258
4286
  "button",
4259
4287
  "link",
@@ -4412,7 +4440,8 @@ function buildRoleSnapshotFromAriaSnapshot(ariaSnapshot, options = {}) {
4412
4440
  const ref = nextRef();
4413
4441
  const nth = tracker.getNextIndex(role, name);
4414
4442
  tracker.trackRef(role, name, ref);
4415
- refs[ref] = { role, name, nth };
4443
+ const state = parseStateFromSuffix(suffix);
4444
+ refs[ref] = { role, name, nth, ...state };
4416
4445
  let enhanced = `${prefix}${roleRaw}`;
4417
4446
  if (name !== void 0 && name !== "") enhanced += ` "${name}"`;
4418
4447
  enhanced += ` [ref=${ref}]`;
@@ -4449,7 +4478,8 @@ function buildRoleSnapshotFromAriaSnapshot(ariaSnapshot, options = {}) {
4449
4478
  const ref = nextRef();
4450
4479
  const nth = tracker.getNextIndex(role, name);
4451
4480
  tracker.trackRef(role, name, ref);
4452
- refs[ref] = { role, name, nth };
4481
+ const state = parseStateFromSuffix(suffix);
4482
+ refs[ref] = { role, name, nth, ...state };
4453
4483
  let enhanced = `${prefix}${roleRaw}`;
4454
4484
  if (name !== "") enhanced += ` "${name}"`;
4455
4485
  enhanced += ` [ref=${ref}]`;
@@ -4487,12 +4517,13 @@ function buildRoleSnapshotFromAiSnapshot(aiSnapshot, options = {}) {
4487
4517
  if (!INTERACTIVE_ROLES.has(role)) continue;
4488
4518
  const ref = parseAiSnapshotRef(suffix);
4489
4519
  const prefix = /^(\s*-\s*)/.exec(line)?.[1] ?? "";
4520
+ const state = parseStateFromSuffix(suffix);
4490
4521
  if (ref !== null) {
4491
- refs[ref] = { role, ...name !== void 0 && name !== "" ? { name } : {} };
4522
+ refs[ref] = { role, ...name !== void 0 && name !== "" ? { name } : {}, ...state };
4492
4523
  out2.push(`${prefix}${roleRaw}${name !== void 0 && name !== "" ? ` "${name}"` : ""}${suffix}`);
4493
4524
  } else {
4494
4525
  const generatedRef = nextInteractiveRef();
4495
- refs[generatedRef] = { role, ...name !== void 0 && name !== "" ? { name } : {} };
4526
+ refs[generatedRef] = { role, ...name !== void 0 && name !== "" ? { name } : {}, ...state };
4496
4527
  let enhanced = `${prefix}${roleRaw}`;
4497
4528
  if (name !== void 0 && name !== "") enhanced += ` "${name}"`;
4498
4529
  enhanced += ` [ref=${generatedRef}]`;
@@ -4530,12 +4561,13 @@ function buildRoleSnapshotFromAiSnapshot(aiSnapshot, options = {}) {
4530
4561
  const isStructural = STRUCTURAL_ROLES.has(role);
4531
4562
  if (options.compact === true && isStructural && name === "") continue;
4532
4563
  const ref = parseAiSnapshotRef(suffix);
4564
+ const state = parseStateFromSuffix(suffix);
4533
4565
  if (ref !== null) {
4534
- refs[ref] = { role, ...name !== "" ? { name } : {} };
4566
+ refs[ref] = { role, ...name !== "" ? { name } : {}, ...state };
4535
4567
  out.push(line);
4536
4568
  } else if (INTERACTIVE_ROLES.has(role)) {
4537
4569
  const generatedRef = nextGeneratedRef();
4538
- refs[generatedRef] = { role, ...name !== "" ? { name } : {} };
4570
+ refs[generatedRef] = { role, ...name !== "" ? { name } : {}, ...state };
4539
4571
  let enhanced = `${prefix}${roleRaw}`;
4540
4572
  if (name !== "") enhanced += ` "${name}"`;
4541
4573
  enhanced += ` [ref=${generatedRef}]`;
@@ -4901,7 +4933,8 @@ var CrawlPage = class {
4901
4933
  button: opts?.button,
4902
4934
  modifiers: opts?.modifiers,
4903
4935
  delayMs: opts?.delayMs,
4904
- timeoutMs: opts?.timeoutMs
4936
+ timeoutMs: opts?.timeoutMs,
4937
+ force: opts?.force
4905
4938
  });
4906
4939
  }
4907
4940
  /**
@@ -4927,7 +4960,8 @@ var CrawlPage = class {
4927
4960
  button: opts?.button,
4928
4961
  modifiers: opts?.modifiers,
4929
4962
  delayMs: opts?.delayMs,
4930
- timeoutMs: opts?.timeoutMs
4963
+ timeoutMs: opts?.timeoutMs,
4964
+ force: opts?.force
4931
4965
  });
4932
4966
  }
4933
4967
  /**
@@ -5030,6 +5064,7 @@ var CrawlPage = class {
5030
5064
  targetId: this.targetId,
5031
5065
  role,
5032
5066
  name,
5067
+ index: opts?.index,
5033
5068
  button: opts?.button,
5034
5069
  modifiers: opts?.modifiers,
5035
5070
  timeoutMs: opts?.timeoutMs