browserclaw 0.8.0 → 0.8.2

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
@@ -1929,6 +1929,41 @@ function ensurePageState(page) {
1929
1929
  });
1930
1930
  page.on("dialog", (dialog) => {
1931
1931
  if (state.armIdDialog > 0) return;
1932
+ if (state.dialogHandler) {
1933
+ let handled = false;
1934
+ const event = {
1935
+ type: dialog.type(),
1936
+ message: dialog.message(),
1937
+ defaultValue: dialog.defaultValue(),
1938
+ accept: (promptText) => {
1939
+ handled = true;
1940
+ return dialog.accept(promptText);
1941
+ },
1942
+ dismiss: () => {
1943
+ handled = true;
1944
+ return dialog.dismiss();
1945
+ }
1946
+ };
1947
+ Promise.resolve(state.dialogHandler(event)).then(() => {
1948
+ if (!handled) {
1949
+ dialog.dismiss().catch((err) => {
1950
+ console.warn(
1951
+ `[browserclaw] Failed to auto-dismiss dialog: ${err instanceof Error ? err.message : String(err)}`
1952
+ );
1953
+ });
1954
+ }
1955
+ }).catch((err) => {
1956
+ console.warn(`[browserclaw] onDialog handler error: ${err instanceof Error ? err.message : String(err)}`);
1957
+ if (!handled) {
1958
+ dialog.dismiss().catch((dismissErr) => {
1959
+ console.warn(
1960
+ `[browserclaw] Failed to dismiss dialog after handler error: ${dismissErr instanceof Error ? dismissErr.message : String(dismissErr)}`
1961
+ );
1962
+ });
1963
+ }
1964
+ });
1965
+ return;
1966
+ }
1932
1967
  dialog.dismiss().catch((err) => {
1933
1968
  console.warn(`[browserclaw] Failed to dismiss dialog: ${err instanceof Error ? err.message : String(err)}`);
1934
1969
  });
@@ -1940,6 +1975,11 @@ function ensurePageState(page) {
1940
1975
  }
1941
1976
  return state;
1942
1977
  }
1978
+ async function setDialogHandler(opts) {
1979
+ const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1980
+ const state = ensurePageState(page);
1981
+ state.dialogHandler = opts.handler;
1982
+ }
1943
1983
  function applyStealthToPage(page) {
1944
1984
  page.evaluate(STEALTH_SCRIPT).catch((e) => {
1945
1985
  if (process.env.DEBUG !== void 0 && process.env.DEBUG !== "")
@@ -2543,7 +2583,17 @@ var BLOCKED_IPV4_RANGES = /* @__PURE__ */ new Set([
2543
2583
  "private",
2544
2584
  "reserved"
2545
2585
  ]);
2546
- var BLOCKED_IPV6_RANGES = /* @__PURE__ */ new Set(["unspecified", "loopback", "linkLocal", "uniqueLocal", "multicast"]);
2586
+ var BLOCKED_IPV6_RANGES = /* @__PURE__ */ new Set([
2587
+ "unspecified",
2588
+ "loopback",
2589
+ "linkLocal",
2590
+ "uniqueLocal",
2591
+ "multicast",
2592
+ "reserved",
2593
+ "benchmarking",
2594
+ "discard",
2595
+ "orchid2"
2596
+ ]);
2547
2597
  var RFC2544_BENCHMARK_PREFIX = [ipaddr.IPv4.parse("198.18.0.0"), 15];
2548
2598
  var EMBEDDED_IPV4_SENTINEL_RULES = [
2549
2599
  // IPv4-compatible (::a.b.c.d)
@@ -4207,6 +4257,7 @@ var CONTENT_ROLES = /* @__PURE__ */ new Set([
4207
4257
  var STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
4208
4258
  "generic",
4209
4259
  "group",
4260
+ "ignored",
4210
4261
  "list",
4211
4262
  "table",
4212
4263
  "row",
@@ -4822,6 +4873,32 @@ var CrawlPage = class {
4822
4873
  timeoutMs: opts?.timeoutMs
4823
4874
  });
4824
4875
  }
4876
+ /**
4877
+ * Click an element by CSS selector (no snapshot/ref needed).
4878
+ *
4879
+ * Finds and clicks atomically — no stale ref problem.
4880
+ *
4881
+ * @param selector - CSS selector (e.g. `'#submit-btn'`, `'.modal button'`)
4882
+ * @param opts - Click options (double-click, button, modifiers)
4883
+ *
4884
+ * @example
4885
+ * ```ts
4886
+ * await page.clickBySelector('#submit-btn');
4887
+ * await page.clickBySelector('.modal .close', { button: 'right' });
4888
+ * ```
4889
+ */
4890
+ async clickBySelector(selector, opts) {
4891
+ return clickViaPlaywright({
4892
+ cdpUrl: this.cdpUrl,
4893
+ targetId: this.targetId,
4894
+ selector,
4895
+ doubleClick: opts?.doubleClick,
4896
+ button: opts?.button,
4897
+ modifiers: opts?.modifiers,
4898
+ delayMs: opts?.delayMs,
4899
+ timeoutMs: opts?.timeoutMs
4900
+ });
4901
+ }
4825
4902
  /**
4826
4903
  * Click at specific page coordinates.
4827
4904
  *
@@ -5065,6 +5142,48 @@ var CrawlPage = class {
5065
5142
  timeoutMs: opts.timeoutMs
5066
5143
  });
5067
5144
  }
5145
+ /**
5146
+ * Register a persistent dialog handler for all dialogs (alert, confirm, prompt, beforeunload).
5147
+ *
5148
+ * Unlike `armDialog()` which handles a single expected dialog, `onDialog()` handles
5149
+ * every dialog that appears until cleared. This prevents unexpected dialogs from
5150
+ * blocking the page.
5151
+ *
5152
+ * The handler receives a `DialogEvent` with `accept()` and `dismiss()` methods.
5153
+ * If the handler throws or doesn't call either, the dialog is auto-dismissed.
5154
+ *
5155
+ * Pass `undefined` or `null` to clear the handler and restore default auto-dismiss.
5156
+ *
5157
+ * Note: `armDialog()` takes priority — if a one-shot handler is armed, it handles
5158
+ * the next dialog instead of the persistent handler.
5159
+ *
5160
+ * @param handler - Callback for dialog events, or `undefined`/`null` to clear
5161
+ *
5162
+ * @example
5163
+ * ```ts
5164
+ * // Accept all confirm dialogs, dismiss everything else
5165
+ * page.onDialog((event) => {
5166
+ * if (event.type === 'confirm') event.accept();
5167
+ * else event.dismiss();
5168
+ * });
5169
+ *
5170
+ * // Log and auto-accept all dialogs
5171
+ * page.onDialog(async (event) => {
5172
+ * console.log(`Dialog: ${event.type} — ${event.message}`);
5173
+ * await event.accept();
5174
+ * });
5175
+ *
5176
+ * // Clear the handler (restore default auto-dismiss)
5177
+ * page.onDialog(undefined);
5178
+ * ```
5179
+ */
5180
+ async onDialog(handler) {
5181
+ return setDialogHandler({
5182
+ cdpUrl: this.cdpUrl,
5183
+ targetId: this.targetId,
5184
+ handler: handler ?? void 0
5185
+ });
5186
+ }
5068
5187
  /**
5069
5188
  * Arm a one-shot file chooser handler.
5070
5189
  *
@@ -5732,6 +5851,58 @@ var CrawlPage = class {
5732
5851
  pollMs: opts?.pollMs
5733
5852
  });
5734
5853
  }
5854
+ // ── Playwright Escape Hatches ─────────────────────────────────
5855
+ /**
5856
+ * Get the underlying Playwright `Page` object for this tab.
5857
+ *
5858
+ * Use this when browserclaw's API doesn't cover your use case and you need
5859
+ * direct access to Playwright's full API (custom locator strategies,
5860
+ * frame manipulation, request interception, etc.).
5861
+ *
5862
+ * **Warning:** Modifications made via the raw Playwright page may conflict
5863
+ * with browserclaw's internal state (e.g. ref tracking). Use with care.
5864
+ *
5865
+ * @returns The Playwright `Page` instance
5866
+ *
5867
+ * @example
5868
+ * ```ts
5869
+ * const pwPage = await page.playwrightPage();
5870
+ *
5871
+ * // Use Playwright's full API directly
5872
+ * await pwPage.locator('.my-component').waitFor({ state: 'visible' });
5873
+ * await pwPage.route('**\/api/**', route => route.fulfill({ body: '{}' }));
5874
+ *
5875
+ * // Access frames
5876
+ * const frame = pwPage.frameLocator('#my-iframe');
5877
+ * ```
5878
+ */
5879
+ async playwrightPage() {
5880
+ return getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.targetId });
5881
+ }
5882
+ /**
5883
+ * Create a Playwright `Locator` for a CSS selector on this page.
5884
+ *
5885
+ * Convenience method that returns a Playwright locator without needing
5886
+ * to first obtain the Page object. Useful for one-off Playwright operations.
5887
+ *
5888
+ * @param selector - CSS selector or Playwright selector string
5889
+ * @returns A Playwright `Locator` instance
5890
+ *
5891
+ * @example
5892
+ * ```ts
5893
+ * const loc = await page.locator('.modal-dialog button.confirm');
5894
+ * await loc.waitFor({ state: 'visible' });
5895
+ * await loc.click();
5896
+ *
5897
+ * // Use Playwright selectors
5898
+ * const input = await page.locator('input[name="email"]');
5899
+ * await input.fill('test@example.com');
5900
+ * ```
5901
+ */
5902
+ async locator(selector) {
5903
+ const pwPage = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.targetId });
5904
+ return pwPage.locator(selector);
5905
+ }
5735
5906
  };
5736
5907
  var BrowserClaw = class _BrowserClaw {
5737
5908
  cdpUrl;
@@ -5925,6 +6096,7 @@ exports.resolvePageByTargetIdOrThrow = resolvePageByTargetIdOrThrow;
5925
6096
  exports.resolvePinnedHostnameWithPolicy = resolvePinnedHostnameWithPolicy;
5926
6097
  exports.resolveStrictExistingUploadPaths = resolveStrictExistingUploadPaths;
5927
6098
  exports.sanitizeUntrustedFileName = sanitizeUntrustedFileName;
6099
+ exports.setDialogHandler = setDialogHandler;
5928
6100
  exports.waitForChallengeViaPlaywright = waitForChallengeViaPlaywright;
5929
6101
  exports.withBrowserNavigationPolicy = withBrowserNavigationPolicy;
5930
6102
  exports.withPageScopedCdpClient = withPageScopedCdpClient;