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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { BrowserContext, Page, CDPSession } from 'playwright-core';
1
+ import { BrowserContext, Page, Locator, CDPSession } from 'playwright-core';
2
2
  import { lookup } from 'node:dns';
3
3
  import { lookup as lookup$1 } from 'node:dns/promises';
4
4
 
@@ -517,6 +517,21 @@ interface DialogOptions {
517
517
  /** Timeout for waiting for the dialog. Default: `30000` */
518
518
  timeoutMs?: number;
519
519
  }
520
+ /** Information about a dialog event passed to `onDialog` handlers. */
521
+ interface DialogEvent {
522
+ /** Dialog type: `'alert'`, `'confirm'`, `'prompt'`, or `'beforeunload'` */
523
+ type: string;
524
+ /** The message displayed in the dialog */
525
+ message: string;
526
+ /** The default prompt value (for prompt dialogs) */
527
+ defaultValue: string;
528
+ /** Accept the dialog (optionally with prompt text) */
529
+ accept: (promptText?: string) => Promise<void>;
530
+ /** Dismiss the dialog */
531
+ dismiss: () => Promise<void>;
532
+ }
533
+ /** Callback for persistent dialog handling. */
534
+ type DialogHandler = (event: DialogEvent) => void | Promise<void>;
520
535
  /** Result of intercepting a response body. */
521
536
  interface ResponseBodyResult {
522
537
  /** The response URL */
@@ -666,6 +681,21 @@ declare class CrawlPage {
666
681
  * ```
667
682
  */
668
683
  click(ref: string, opts?: ClickOptions): Promise<void>;
684
+ /**
685
+ * Click an element by CSS selector (no snapshot/ref needed).
686
+ *
687
+ * Finds and clicks atomically — no stale ref problem.
688
+ *
689
+ * @param selector - CSS selector (e.g. `'#submit-btn'`, `'.modal button'`)
690
+ * @param opts - Click options (double-click, button, modifiers)
691
+ *
692
+ * @example
693
+ * ```ts
694
+ * await page.clickBySelector('#submit-btn');
695
+ * await page.clickBySelector('.modal .close', { button: 'right' });
696
+ * ```
697
+ */
698
+ clickBySelector(selector: string, opts?: ClickOptions): Promise<void>;
669
699
  /**
670
700
  * Click at specific page coordinates.
671
701
  *
@@ -832,6 +862,42 @@ declare class CrawlPage {
832
862
  * ```
833
863
  */
834
864
  armDialog(opts: DialogOptions): Promise<void>;
865
+ /**
866
+ * Register a persistent dialog handler for all dialogs (alert, confirm, prompt, beforeunload).
867
+ *
868
+ * Unlike `armDialog()` which handles a single expected dialog, `onDialog()` handles
869
+ * every dialog that appears until cleared. This prevents unexpected dialogs from
870
+ * blocking the page.
871
+ *
872
+ * The handler receives a `DialogEvent` with `accept()` and `dismiss()` methods.
873
+ * If the handler throws or doesn't call either, the dialog is auto-dismissed.
874
+ *
875
+ * Pass `undefined` or `null` to clear the handler and restore default auto-dismiss.
876
+ *
877
+ * Note: `armDialog()` takes priority — if a one-shot handler is armed, it handles
878
+ * the next dialog instead of the persistent handler.
879
+ *
880
+ * @param handler - Callback for dialog events, or `undefined`/`null` to clear
881
+ *
882
+ * @example
883
+ * ```ts
884
+ * // Accept all confirm dialogs, dismiss everything else
885
+ * page.onDialog((event) => {
886
+ * if (event.type === 'confirm') event.accept();
887
+ * else event.dismiss();
888
+ * });
889
+ *
890
+ * // Log and auto-accept all dialogs
891
+ * page.onDialog(async (event) => {
892
+ * console.log(`Dialog: ${event.type} — ${event.message}`);
893
+ * await event.accept();
894
+ * });
895
+ *
896
+ * // Clear the handler (restore default auto-dismiss)
897
+ * page.onDialog(undefined);
898
+ * ```
899
+ */
900
+ onDialog(handler: DialogHandler | undefined | null): Promise<void>;
835
901
  /**
836
902
  * Arm a one-shot file chooser handler.
837
903
  *
@@ -1302,6 +1368,52 @@ declare class CrawlPage {
1302
1368
  timeoutMs?: number;
1303
1369
  pollMs?: number;
1304
1370
  }): Promise<ChallengeWaitResult>;
1371
+ /**
1372
+ * Get the underlying Playwright `Page` object for this tab.
1373
+ *
1374
+ * Use this when browserclaw's API doesn't cover your use case and you need
1375
+ * direct access to Playwright's full API (custom locator strategies,
1376
+ * frame manipulation, request interception, etc.).
1377
+ *
1378
+ * **Warning:** Modifications made via the raw Playwright page may conflict
1379
+ * with browserclaw's internal state (e.g. ref tracking). Use with care.
1380
+ *
1381
+ * @returns The Playwright `Page` instance
1382
+ *
1383
+ * @example
1384
+ * ```ts
1385
+ * const pwPage = await page.playwrightPage();
1386
+ *
1387
+ * // Use Playwright's full API directly
1388
+ * await pwPage.locator('.my-component').waitFor({ state: 'visible' });
1389
+ * await pwPage.route('**\/api/**', route => route.fulfill({ body: '{}' }));
1390
+ *
1391
+ * // Access frames
1392
+ * const frame = pwPage.frameLocator('#my-iframe');
1393
+ * ```
1394
+ */
1395
+ playwrightPage(): Promise<Page>;
1396
+ /**
1397
+ * Create a Playwright `Locator` for a CSS selector on this page.
1398
+ *
1399
+ * Convenience method that returns a Playwright locator without needing
1400
+ * to first obtain the Page object. Useful for one-off Playwright operations.
1401
+ *
1402
+ * @param selector - CSS selector or Playwright selector string
1403
+ * @returns A Playwright `Locator` instance
1404
+ *
1405
+ * @example
1406
+ * ```ts
1407
+ * const loc = await page.locator('.modal-dialog button.confirm');
1408
+ * await loc.waitFor({ state: 'visible' });
1409
+ * await loc.click();
1410
+ *
1411
+ * // Use Playwright selectors
1412
+ * const input = await page.locator('input[name="email"]');
1413
+ * await input.fill('test@example.com');
1414
+ * ```
1415
+ */
1416
+ locator(selector: string): Promise<Locator>;
1305
1417
  }
1306
1418
  /**
1307
1419
  * Main entry point for browserclaw.
@@ -1548,6 +1660,16 @@ declare function withPageScopedCdpClient<T>(opts: {
1548
1660
  fn: (send: (method: string, params?: Record<string, unknown>) => Promise<unknown>) => Promise<T>;
1549
1661
  }): Promise<T>;
1550
1662
  declare function ensureContextState(context: BrowserContext): ContextState;
1663
+ /**
1664
+ * Set or clear a persistent dialog handler for a page.
1665
+ * When set, this handler is called for every dialog that is not covered by armDialog().
1666
+ * Pass `undefined` to clear the handler and restore default auto-dismiss.
1667
+ */
1668
+ declare function setDialogHandler(opts: {
1669
+ cdpUrl: string;
1670
+ targetId?: string;
1671
+ handler?: DialogHandler;
1672
+ }): Promise<void>;
1551
1673
  /**
1552
1674
  * Force-disconnect a Playwright browser connection for a given CDP target.
1553
1675
  * Clears the connection cache, sends Runtime.terminateExecution via raw CDP
@@ -1633,4 +1755,4 @@ declare function waitForChallengeViaPlaywright(opts: {
1633
1755
  pollMs?: number;
1634
1756
  }): Promise<ChallengeWaitResult>;
1635
1757
 
1636
- export { type AriaNode, type AriaSnapshotResult, type BatchAction, type BatchActionResult, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserNavigationRequestLike, type BrowserTab, BrowserTabNotFoundError, type ChallengeInfo, type ChallengeKind, type ChallengeWaitResult, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type ContextState, type CookieData, CrawlPage, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type PinnedHostname, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, STEALTH_SCRIPT, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, assertSafeUploadPaths, batchViaPlaywright, createPinnedLookup, detectChallengeViaPlaywright, ensureContextState, executeSingleAction, forceDisconnectPlaywrightForTarget, getChromeWebSocketUrl, getRestoredPageForTarget, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, parseRoleRef, requireRef, requireRefOrSelector, requiresInspectableBrowserNavigationRedirects, resolveBoundedDelayMs, resolveInteractionTimeoutMs, resolvePageByTargetIdOrThrow, resolvePinnedHostnameWithPolicy, resolveStrictExistingUploadPaths, sanitizeUntrustedFileName, waitForChallengeViaPlaywright, withBrowserNavigationPolicy, withPageScopedCdpClient, withPlaywrightPageCdpSession, writeViaSiblingTempPath };
1758
+ export { type AriaNode, type AriaSnapshotResult, type BatchAction, type BatchActionResult, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserNavigationRequestLike, type BrowserTab, BrowserTabNotFoundError, type ChallengeInfo, type ChallengeKind, type ChallengeWaitResult, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type ContextState, type CookieData, CrawlPage, type DialogEvent, type DialogHandler, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type PinnedHostname, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, STEALTH_SCRIPT, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, assertSafeUploadPaths, batchViaPlaywright, createPinnedLookup, detectChallengeViaPlaywright, ensureContextState, executeSingleAction, forceDisconnectPlaywrightForTarget, getChromeWebSocketUrl, getRestoredPageForTarget, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, parseRoleRef, requireRef, requireRefOrSelector, requiresInspectableBrowserNavigationRedirects, resolveBoundedDelayMs, resolveInteractionTimeoutMs, resolvePageByTargetIdOrThrow, resolvePinnedHostnameWithPolicy, resolveStrictExistingUploadPaths, sanitizeUntrustedFileName, setDialogHandler, waitForChallengeViaPlaywright, withBrowserNavigationPolicy, withPageScopedCdpClient, withPlaywrightPageCdpSession, writeViaSiblingTempPath };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BrowserContext, Page, CDPSession } from 'playwright-core';
1
+ import { BrowserContext, Page, Locator, CDPSession } from 'playwright-core';
2
2
  import { lookup } from 'node:dns';
3
3
  import { lookup as lookup$1 } from 'node:dns/promises';
4
4
 
@@ -517,6 +517,21 @@ interface DialogOptions {
517
517
  /** Timeout for waiting for the dialog. Default: `30000` */
518
518
  timeoutMs?: number;
519
519
  }
520
+ /** Information about a dialog event passed to `onDialog` handlers. */
521
+ interface DialogEvent {
522
+ /** Dialog type: `'alert'`, `'confirm'`, `'prompt'`, or `'beforeunload'` */
523
+ type: string;
524
+ /** The message displayed in the dialog */
525
+ message: string;
526
+ /** The default prompt value (for prompt dialogs) */
527
+ defaultValue: string;
528
+ /** Accept the dialog (optionally with prompt text) */
529
+ accept: (promptText?: string) => Promise<void>;
530
+ /** Dismiss the dialog */
531
+ dismiss: () => Promise<void>;
532
+ }
533
+ /** Callback for persistent dialog handling. */
534
+ type DialogHandler = (event: DialogEvent) => void | Promise<void>;
520
535
  /** Result of intercepting a response body. */
521
536
  interface ResponseBodyResult {
522
537
  /** The response URL */
@@ -666,6 +681,21 @@ declare class CrawlPage {
666
681
  * ```
667
682
  */
668
683
  click(ref: string, opts?: ClickOptions): Promise<void>;
684
+ /**
685
+ * Click an element by CSS selector (no snapshot/ref needed).
686
+ *
687
+ * Finds and clicks atomically — no stale ref problem.
688
+ *
689
+ * @param selector - CSS selector (e.g. `'#submit-btn'`, `'.modal button'`)
690
+ * @param opts - Click options (double-click, button, modifiers)
691
+ *
692
+ * @example
693
+ * ```ts
694
+ * await page.clickBySelector('#submit-btn');
695
+ * await page.clickBySelector('.modal .close', { button: 'right' });
696
+ * ```
697
+ */
698
+ clickBySelector(selector: string, opts?: ClickOptions): Promise<void>;
669
699
  /**
670
700
  * Click at specific page coordinates.
671
701
  *
@@ -832,6 +862,42 @@ declare class CrawlPage {
832
862
  * ```
833
863
  */
834
864
  armDialog(opts: DialogOptions): Promise<void>;
865
+ /**
866
+ * Register a persistent dialog handler for all dialogs (alert, confirm, prompt, beforeunload).
867
+ *
868
+ * Unlike `armDialog()` which handles a single expected dialog, `onDialog()` handles
869
+ * every dialog that appears until cleared. This prevents unexpected dialogs from
870
+ * blocking the page.
871
+ *
872
+ * The handler receives a `DialogEvent` with `accept()` and `dismiss()` methods.
873
+ * If the handler throws or doesn't call either, the dialog is auto-dismissed.
874
+ *
875
+ * Pass `undefined` or `null` to clear the handler and restore default auto-dismiss.
876
+ *
877
+ * Note: `armDialog()` takes priority — if a one-shot handler is armed, it handles
878
+ * the next dialog instead of the persistent handler.
879
+ *
880
+ * @param handler - Callback for dialog events, or `undefined`/`null` to clear
881
+ *
882
+ * @example
883
+ * ```ts
884
+ * // Accept all confirm dialogs, dismiss everything else
885
+ * page.onDialog((event) => {
886
+ * if (event.type === 'confirm') event.accept();
887
+ * else event.dismiss();
888
+ * });
889
+ *
890
+ * // Log and auto-accept all dialogs
891
+ * page.onDialog(async (event) => {
892
+ * console.log(`Dialog: ${event.type} — ${event.message}`);
893
+ * await event.accept();
894
+ * });
895
+ *
896
+ * // Clear the handler (restore default auto-dismiss)
897
+ * page.onDialog(undefined);
898
+ * ```
899
+ */
900
+ onDialog(handler: DialogHandler | undefined | null): Promise<void>;
835
901
  /**
836
902
  * Arm a one-shot file chooser handler.
837
903
  *
@@ -1302,6 +1368,52 @@ declare class CrawlPage {
1302
1368
  timeoutMs?: number;
1303
1369
  pollMs?: number;
1304
1370
  }): Promise<ChallengeWaitResult>;
1371
+ /**
1372
+ * Get the underlying Playwright `Page` object for this tab.
1373
+ *
1374
+ * Use this when browserclaw's API doesn't cover your use case and you need
1375
+ * direct access to Playwright's full API (custom locator strategies,
1376
+ * frame manipulation, request interception, etc.).
1377
+ *
1378
+ * **Warning:** Modifications made via the raw Playwright page may conflict
1379
+ * with browserclaw's internal state (e.g. ref tracking). Use with care.
1380
+ *
1381
+ * @returns The Playwright `Page` instance
1382
+ *
1383
+ * @example
1384
+ * ```ts
1385
+ * const pwPage = await page.playwrightPage();
1386
+ *
1387
+ * // Use Playwright's full API directly
1388
+ * await pwPage.locator('.my-component').waitFor({ state: 'visible' });
1389
+ * await pwPage.route('**\/api/**', route => route.fulfill({ body: '{}' }));
1390
+ *
1391
+ * // Access frames
1392
+ * const frame = pwPage.frameLocator('#my-iframe');
1393
+ * ```
1394
+ */
1395
+ playwrightPage(): Promise<Page>;
1396
+ /**
1397
+ * Create a Playwright `Locator` for a CSS selector on this page.
1398
+ *
1399
+ * Convenience method that returns a Playwright locator without needing
1400
+ * to first obtain the Page object. Useful for one-off Playwright operations.
1401
+ *
1402
+ * @param selector - CSS selector or Playwright selector string
1403
+ * @returns A Playwright `Locator` instance
1404
+ *
1405
+ * @example
1406
+ * ```ts
1407
+ * const loc = await page.locator('.modal-dialog button.confirm');
1408
+ * await loc.waitFor({ state: 'visible' });
1409
+ * await loc.click();
1410
+ *
1411
+ * // Use Playwright selectors
1412
+ * const input = await page.locator('input[name="email"]');
1413
+ * await input.fill('test@example.com');
1414
+ * ```
1415
+ */
1416
+ locator(selector: string): Promise<Locator>;
1305
1417
  }
1306
1418
  /**
1307
1419
  * Main entry point for browserclaw.
@@ -1548,6 +1660,16 @@ declare function withPageScopedCdpClient<T>(opts: {
1548
1660
  fn: (send: (method: string, params?: Record<string, unknown>) => Promise<unknown>) => Promise<T>;
1549
1661
  }): Promise<T>;
1550
1662
  declare function ensureContextState(context: BrowserContext): ContextState;
1663
+ /**
1664
+ * Set or clear a persistent dialog handler for a page.
1665
+ * When set, this handler is called for every dialog that is not covered by armDialog().
1666
+ * Pass `undefined` to clear the handler and restore default auto-dismiss.
1667
+ */
1668
+ declare function setDialogHandler(opts: {
1669
+ cdpUrl: string;
1670
+ targetId?: string;
1671
+ handler?: DialogHandler;
1672
+ }): Promise<void>;
1551
1673
  /**
1552
1674
  * Force-disconnect a Playwright browser connection for a given CDP target.
1553
1675
  * Clears the connection cache, sends Runtime.terminateExecution via raw CDP
@@ -1633,4 +1755,4 @@ declare function waitForChallengeViaPlaywright(opts: {
1633
1755
  pollMs?: number;
1634
1756
  }): Promise<ChallengeWaitResult>;
1635
1757
 
1636
- export { type AriaNode, type AriaSnapshotResult, type BatchAction, type BatchActionResult, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserNavigationRequestLike, type BrowserTab, BrowserTabNotFoundError, type ChallengeInfo, type ChallengeKind, type ChallengeWaitResult, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type ContextState, type CookieData, CrawlPage, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type PinnedHostname, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, STEALTH_SCRIPT, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, assertSafeUploadPaths, batchViaPlaywright, createPinnedLookup, detectChallengeViaPlaywright, ensureContextState, executeSingleAction, forceDisconnectPlaywrightForTarget, getChromeWebSocketUrl, getRestoredPageForTarget, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, parseRoleRef, requireRef, requireRefOrSelector, requiresInspectableBrowserNavigationRedirects, resolveBoundedDelayMs, resolveInteractionTimeoutMs, resolvePageByTargetIdOrThrow, resolvePinnedHostnameWithPolicy, resolveStrictExistingUploadPaths, sanitizeUntrustedFileName, waitForChallengeViaPlaywright, withBrowserNavigationPolicy, withPageScopedCdpClient, withPlaywrightPageCdpSession, writeViaSiblingTempPath };
1758
+ export { type AriaNode, type AriaSnapshotResult, type BatchAction, type BatchActionResult, BrowserClaw, type BrowserNavigationPolicyOptions, type BrowserNavigationRequestLike, type BrowserTab, BrowserTabNotFoundError, type ChallengeInfo, type ChallengeKind, type ChallengeWaitResult, type ChromeExecutable, type ChromeKind, type ClickOptions, type ColorScheme, type ConnectOptions, type ConsoleMessage, type ContextState, type CookieData, CrawlPage, type DialogEvent, type DialogHandler, type DialogOptions, type DownloadResult, type FormField, type FrameEvalResult, type GeolocationOptions, type HttpCredentials, InvalidBrowserNavigationUrlError, type LaunchOptions, type LookupFn, type NetworkRequest, type PageError, type PinnedHostname, type ResponseBodyResult, type RoleRefInfo, type RoleRefs, STEALTH_SCRIPT, type ScreenshotOptions, type SnapshotOptions, type SnapshotResult, type SnapshotStats, type SsrfPolicy, type StorageKind, type TraceStartOptions, type TypeOptions, type UntrustedContentMeta, type WaitOptions, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, assertSafeUploadPaths, batchViaPlaywright, createPinnedLookup, detectChallengeViaPlaywright, ensureContextState, executeSingleAction, forceDisconnectPlaywrightForTarget, getChromeWebSocketUrl, getRestoredPageForTarget, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, parseRoleRef, requireRef, requireRefOrSelector, requiresInspectableBrowserNavigationRedirects, resolveBoundedDelayMs, resolveInteractionTimeoutMs, resolvePageByTargetIdOrThrow, resolvePinnedHostnameWithPolicy, resolveStrictExistingUploadPaths, sanitizeUntrustedFileName, setDialogHandler, waitForChallengeViaPlaywright, withBrowserNavigationPolicy, withPageScopedCdpClient, withPlaywrightPageCdpSession, writeViaSiblingTempPath };
package/dist/index.js CHANGED
@@ -1918,6 +1918,41 @@ function ensurePageState(page) {
1918
1918
  });
1919
1919
  page.on("dialog", (dialog) => {
1920
1920
  if (state.armIdDialog > 0) return;
1921
+ if (state.dialogHandler) {
1922
+ let handled = false;
1923
+ const event = {
1924
+ type: dialog.type(),
1925
+ message: dialog.message(),
1926
+ defaultValue: dialog.defaultValue(),
1927
+ accept: (promptText) => {
1928
+ handled = true;
1929
+ return dialog.accept(promptText);
1930
+ },
1931
+ dismiss: () => {
1932
+ handled = true;
1933
+ return dialog.dismiss();
1934
+ }
1935
+ };
1936
+ Promise.resolve(state.dialogHandler(event)).then(() => {
1937
+ if (!handled) {
1938
+ dialog.dismiss().catch((err) => {
1939
+ console.warn(
1940
+ `[browserclaw] Failed to auto-dismiss dialog: ${err instanceof Error ? err.message : String(err)}`
1941
+ );
1942
+ });
1943
+ }
1944
+ }).catch((err) => {
1945
+ console.warn(`[browserclaw] onDialog handler error: ${err instanceof Error ? err.message : String(err)}`);
1946
+ if (!handled) {
1947
+ dialog.dismiss().catch((dismissErr) => {
1948
+ console.warn(
1949
+ `[browserclaw] Failed to dismiss dialog after handler error: ${dismissErr instanceof Error ? dismissErr.message : String(dismissErr)}`
1950
+ );
1951
+ });
1952
+ }
1953
+ });
1954
+ return;
1955
+ }
1921
1956
  dialog.dismiss().catch((err) => {
1922
1957
  console.warn(`[browserclaw] Failed to dismiss dialog: ${err instanceof Error ? err.message : String(err)}`);
1923
1958
  });
@@ -1929,6 +1964,11 @@ function ensurePageState(page) {
1929
1964
  }
1930
1965
  return state;
1931
1966
  }
1967
+ async function setDialogHandler(opts) {
1968
+ const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1969
+ const state = ensurePageState(page);
1970
+ state.dialogHandler = opts.handler;
1971
+ }
1932
1972
  function applyStealthToPage(page) {
1933
1973
  page.evaluate(STEALTH_SCRIPT).catch((e) => {
1934
1974
  if (process.env.DEBUG !== void 0 && process.env.DEBUG !== "")
@@ -2532,7 +2572,17 @@ var BLOCKED_IPV4_RANGES = /* @__PURE__ */ new Set([
2532
2572
  "private",
2533
2573
  "reserved"
2534
2574
  ]);
2535
- var BLOCKED_IPV6_RANGES = /* @__PURE__ */ new Set(["unspecified", "loopback", "linkLocal", "uniqueLocal", "multicast"]);
2575
+ var BLOCKED_IPV6_RANGES = /* @__PURE__ */ new Set([
2576
+ "unspecified",
2577
+ "loopback",
2578
+ "linkLocal",
2579
+ "uniqueLocal",
2580
+ "multicast",
2581
+ "reserved",
2582
+ "benchmarking",
2583
+ "discard",
2584
+ "orchid2"
2585
+ ]);
2536
2586
  var RFC2544_BENCHMARK_PREFIX = [ipaddr.IPv4.parse("198.18.0.0"), 15];
2537
2587
  var EMBEDDED_IPV4_SENTINEL_RULES = [
2538
2588
  // IPv4-compatible (::a.b.c.d)
@@ -4196,6 +4246,7 @@ var CONTENT_ROLES = /* @__PURE__ */ new Set([
4196
4246
  var STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
4197
4247
  "generic",
4198
4248
  "group",
4249
+ "ignored",
4199
4250
  "list",
4200
4251
  "table",
4201
4252
  "row",
@@ -4811,6 +4862,32 @@ var CrawlPage = class {
4811
4862
  timeoutMs: opts?.timeoutMs
4812
4863
  });
4813
4864
  }
4865
+ /**
4866
+ * Click an element by CSS selector (no snapshot/ref needed).
4867
+ *
4868
+ * Finds and clicks atomically — no stale ref problem.
4869
+ *
4870
+ * @param selector - CSS selector (e.g. `'#submit-btn'`, `'.modal button'`)
4871
+ * @param opts - Click options (double-click, button, modifiers)
4872
+ *
4873
+ * @example
4874
+ * ```ts
4875
+ * await page.clickBySelector('#submit-btn');
4876
+ * await page.clickBySelector('.modal .close', { button: 'right' });
4877
+ * ```
4878
+ */
4879
+ async clickBySelector(selector, opts) {
4880
+ return clickViaPlaywright({
4881
+ cdpUrl: this.cdpUrl,
4882
+ targetId: this.targetId,
4883
+ selector,
4884
+ doubleClick: opts?.doubleClick,
4885
+ button: opts?.button,
4886
+ modifiers: opts?.modifiers,
4887
+ delayMs: opts?.delayMs,
4888
+ timeoutMs: opts?.timeoutMs
4889
+ });
4890
+ }
4814
4891
  /**
4815
4892
  * Click at specific page coordinates.
4816
4893
  *
@@ -5054,6 +5131,48 @@ var CrawlPage = class {
5054
5131
  timeoutMs: opts.timeoutMs
5055
5132
  });
5056
5133
  }
5134
+ /**
5135
+ * Register a persistent dialog handler for all dialogs (alert, confirm, prompt, beforeunload).
5136
+ *
5137
+ * Unlike `armDialog()` which handles a single expected dialog, `onDialog()` handles
5138
+ * every dialog that appears until cleared. This prevents unexpected dialogs from
5139
+ * blocking the page.
5140
+ *
5141
+ * The handler receives a `DialogEvent` with `accept()` and `dismiss()` methods.
5142
+ * If the handler throws or doesn't call either, the dialog is auto-dismissed.
5143
+ *
5144
+ * Pass `undefined` or `null` to clear the handler and restore default auto-dismiss.
5145
+ *
5146
+ * Note: `armDialog()` takes priority — if a one-shot handler is armed, it handles
5147
+ * the next dialog instead of the persistent handler.
5148
+ *
5149
+ * @param handler - Callback for dialog events, or `undefined`/`null` to clear
5150
+ *
5151
+ * @example
5152
+ * ```ts
5153
+ * // Accept all confirm dialogs, dismiss everything else
5154
+ * page.onDialog((event) => {
5155
+ * if (event.type === 'confirm') event.accept();
5156
+ * else event.dismiss();
5157
+ * });
5158
+ *
5159
+ * // Log and auto-accept all dialogs
5160
+ * page.onDialog(async (event) => {
5161
+ * console.log(`Dialog: ${event.type} — ${event.message}`);
5162
+ * await event.accept();
5163
+ * });
5164
+ *
5165
+ * // Clear the handler (restore default auto-dismiss)
5166
+ * page.onDialog(undefined);
5167
+ * ```
5168
+ */
5169
+ async onDialog(handler) {
5170
+ return setDialogHandler({
5171
+ cdpUrl: this.cdpUrl,
5172
+ targetId: this.targetId,
5173
+ handler: handler ?? void 0
5174
+ });
5175
+ }
5057
5176
  /**
5058
5177
  * Arm a one-shot file chooser handler.
5059
5178
  *
@@ -5721,6 +5840,58 @@ var CrawlPage = class {
5721
5840
  pollMs: opts?.pollMs
5722
5841
  });
5723
5842
  }
5843
+ // ── Playwright Escape Hatches ─────────────────────────────────
5844
+ /**
5845
+ * Get the underlying Playwright `Page` object for this tab.
5846
+ *
5847
+ * Use this when browserclaw's API doesn't cover your use case and you need
5848
+ * direct access to Playwright's full API (custom locator strategies,
5849
+ * frame manipulation, request interception, etc.).
5850
+ *
5851
+ * **Warning:** Modifications made via the raw Playwright page may conflict
5852
+ * with browserclaw's internal state (e.g. ref tracking). Use with care.
5853
+ *
5854
+ * @returns The Playwright `Page` instance
5855
+ *
5856
+ * @example
5857
+ * ```ts
5858
+ * const pwPage = await page.playwrightPage();
5859
+ *
5860
+ * // Use Playwright's full API directly
5861
+ * await pwPage.locator('.my-component').waitFor({ state: 'visible' });
5862
+ * await pwPage.route('**\/api/**', route => route.fulfill({ body: '{}' }));
5863
+ *
5864
+ * // Access frames
5865
+ * const frame = pwPage.frameLocator('#my-iframe');
5866
+ * ```
5867
+ */
5868
+ async playwrightPage() {
5869
+ return getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.targetId });
5870
+ }
5871
+ /**
5872
+ * Create a Playwright `Locator` for a CSS selector on this page.
5873
+ *
5874
+ * Convenience method that returns a Playwright locator without needing
5875
+ * to first obtain the Page object. Useful for one-off Playwright operations.
5876
+ *
5877
+ * @param selector - CSS selector or Playwright selector string
5878
+ * @returns A Playwright `Locator` instance
5879
+ *
5880
+ * @example
5881
+ * ```ts
5882
+ * const loc = await page.locator('.modal-dialog button.confirm');
5883
+ * await loc.waitFor({ state: 'visible' });
5884
+ * await loc.click();
5885
+ *
5886
+ * // Use Playwright selectors
5887
+ * const input = await page.locator('input[name="email"]');
5888
+ * await input.fill('test@example.com');
5889
+ * ```
5890
+ */
5891
+ async locator(selector) {
5892
+ const pwPage = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.targetId });
5893
+ return pwPage.locator(selector);
5894
+ }
5724
5895
  };
5725
5896
  var BrowserClaw = class _BrowserClaw {
5726
5897
  cdpUrl;
@@ -5884,6 +6055,6 @@ var BrowserClaw = class _BrowserClaw {
5884
6055
  }
5885
6056
  };
5886
6057
 
5887
- export { BrowserClaw, BrowserTabNotFoundError, CrawlPage, InvalidBrowserNavigationUrlError, STEALTH_SCRIPT, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, assertSafeUploadPaths, batchViaPlaywright, createPinnedLookup, detectChallengeViaPlaywright, ensureContextState, executeSingleAction, forceDisconnectPlaywrightForTarget, getChromeWebSocketUrl, getRestoredPageForTarget, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, parseRoleRef, requireRef, requireRefOrSelector, requiresInspectableBrowserNavigationRedirects, resolveBoundedDelayMs, resolveInteractionTimeoutMs, resolvePageByTargetIdOrThrow, resolvePinnedHostnameWithPolicy, resolveStrictExistingUploadPaths, sanitizeUntrustedFileName, waitForChallengeViaPlaywright, withBrowserNavigationPolicy, withPageScopedCdpClient, withPlaywrightPageCdpSession, writeViaSiblingTempPath };
6058
+ export { BrowserClaw, BrowserTabNotFoundError, CrawlPage, InvalidBrowserNavigationUrlError, STEALTH_SCRIPT, assertBrowserNavigationAllowed, assertBrowserNavigationRedirectChainAllowed, assertBrowserNavigationResultAllowed, assertSafeUploadPaths, batchViaPlaywright, createPinnedLookup, detectChallengeViaPlaywright, ensureContextState, executeSingleAction, forceDisconnectPlaywrightForTarget, getChromeWebSocketUrl, getRestoredPageForTarget, isChromeCdpReady, isChromeReachable, normalizeCdpHttpBaseForJsonEndpoints, parseRoleRef, requireRef, requireRefOrSelector, requiresInspectableBrowserNavigationRedirects, resolveBoundedDelayMs, resolveInteractionTimeoutMs, resolvePageByTargetIdOrThrow, resolvePinnedHostnameWithPolicy, resolveStrictExistingUploadPaths, sanitizeUntrustedFileName, setDialogHandler, waitForChallengeViaPlaywright, withBrowserNavigationPolicy, withPageScopedCdpClient, withPlaywrightPageCdpSession, writeViaSiblingTempPath };
5888
6059
  //# sourceMappingURL=index.js.map
5889
6060
  //# sourceMappingURL=index.js.map