browserclaw 0.8.0 → 0.8.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
@@ -1929,6 +1929,37 @@ 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(`[browserclaw] Failed to auto-dismiss dialog: ${err instanceof Error ? err.message : String(err)}`);
1951
+ });
1952
+ }
1953
+ }).catch((err) => {
1954
+ console.warn(`[browserclaw] onDialog handler error: ${err instanceof Error ? err.message : String(err)}`);
1955
+ if (!handled) {
1956
+ dialog.dismiss().catch((dismissErr) => {
1957
+ console.warn(`[browserclaw] Failed to dismiss dialog after handler error: ${dismissErr instanceof Error ? dismissErr.message : String(dismissErr)}`);
1958
+ });
1959
+ }
1960
+ });
1961
+ return;
1962
+ }
1932
1963
  dialog.dismiss().catch((err) => {
1933
1964
  console.warn(`[browserclaw] Failed to dismiss dialog: ${err instanceof Error ? err.message : String(err)}`);
1934
1965
  });
@@ -1940,6 +1971,11 @@ function ensurePageState(page) {
1940
1971
  }
1941
1972
  return state;
1942
1973
  }
1974
+ async function setDialogHandler(opts) {
1975
+ const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1976
+ const state = ensurePageState(page);
1977
+ state.dialogHandler = opts.handler;
1978
+ }
1943
1979
  function applyStealthToPage(page) {
1944
1980
  page.evaluate(STEALTH_SCRIPT).catch((e) => {
1945
1981
  if (process.env.DEBUG !== void 0 && process.env.DEBUG !== "")
@@ -4822,6 +4858,32 @@ var CrawlPage = class {
4822
4858
  timeoutMs: opts?.timeoutMs
4823
4859
  });
4824
4860
  }
4861
+ /**
4862
+ * Click an element by CSS selector (no snapshot/ref needed).
4863
+ *
4864
+ * Finds and clicks atomically — no stale ref problem.
4865
+ *
4866
+ * @param selector - CSS selector (e.g. `'#submit-btn'`, `'.modal button'`)
4867
+ * @param opts - Click options (double-click, button, modifiers)
4868
+ *
4869
+ * @example
4870
+ * ```ts
4871
+ * await page.clickBySelector('#submit-btn');
4872
+ * await page.clickBySelector('.modal .close', { button: 'right' });
4873
+ * ```
4874
+ */
4875
+ async clickBySelector(selector, opts) {
4876
+ return clickViaPlaywright({
4877
+ cdpUrl: this.cdpUrl,
4878
+ targetId: this.targetId,
4879
+ selector,
4880
+ doubleClick: opts?.doubleClick,
4881
+ button: opts?.button,
4882
+ modifiers: opts?.modifiers,
4883
+ delayMs: opts?.delayMs,
4884
+ timeoutMs: opts?.timeoutMs
4885
+ });
4886
+ }
4825
4887
  /**
4826
4888
  * Click at specific page coordinates.
4827
4889
  *
@@ -5065,6 +5127,48 @@ var CrawlPage = class {
5065
5127
  timeoutMs: opts.timeoutMs
5066
5128
  });
5067
5129
  }
5130
+ /**
5131
+ * Register a persistent dialog handler for all dialogs (alert, confirm, prompt, beforeunload).
5132
+ *
5133
+ * Unlike `armDialog()` which handles a single expected dialog, `onDialog()` handles
5134
+ * every dialog that appears until cleared. This prevents unexpected dialogs from
5135
+ * blocking the page.
5136
+ *
5137
+ * The handler receives a `DialogEvent` with `accept()` and `dismiss()` methods.
5138
+ * If the handler throws or doesn't call either, the dialog is auto-dismissed.
5139
+ *
5140
+ * Pass `undefined` or `null` to clear the handler and restore default auto-dismiss.
5141
+ *
5142
+ * Note: `armDialog()` takes priority — if a one-shot handler is armed, it handles
5143
+ * the next dialog instead of the persistent handler.
5144
+ *
5145
+ * @param handler - Callback for dialog events, or `undefined`/`null` to clear
5146
+ *
5147
+ * @example
5148
+ * ```ts
5149
+ * // Accept all confirm dialogs, dismiss everything else
5150
+ * page.onDialog((event) => {
5151
+ * if (event.type === 'confirm') event.accept();
5152
+ * else event.dismiss();
5153
+ * });
5154
+ *
5155
+ * // Log and auto-accept all dialogs
5156
+ * page.onDialog(async (event) => {
5157
+ * console.log(`Dialog: ${event.type} — ${event.message}`);
5158
+ * await event.accept();
5159
+ * });
5160
+ *
5161
+ * // Clear the handler (restore default auto-dismiss)
5162
+ * page.onDialog(undefined);
5163
+ * ```
5164
+ */
5165
+ async onDialog(handler) {
5166
+ return setDialogHandler({
5167
+ cdpUrl: this.cdpUrl,
5168
+ targetId: this.targetId,
5169
+ handler: handler ?? void 0
5170
+ });
5171
+ }
5068
5172
  /**
5069
5173
  * Arm a one-shot file chooser handler.
5070
5174
  *
@@ -5732,6 +5836,58 @@ var CrawlPage = class {
5732
5836
  pollMs: opts?.pollMs
5733
5837
  });
5734
5838
  }
5839
+ // ── Playwright Escape Hatches ─────────────────────────────────
5840
+ /**
5841
+ * Get the underlying Playwright `Page` object for this tab.
5842
+ *
5843
+ * Use this when browserclaw's API doesn't cover your use case and you need
5844
+ * direct access to Playwright's full API (custom locator strategies,
5845
+ * frame manipulation, request interception, etc.).
5846
+ *
5847
+ * **Warning:** Modifications made via the raw Playwright page may conflict
5848
+ * with browserclaw's internal state (e.g. ref tracking). Use with care.
5849
+ *
5850
+ * @returns The Playwright `Page` instance
5851
+ *
5852
+ * @example
5853
+ * ```ts
5854
+ * const pwPage = await page.playwrightPage();
5855
+ *
5856
+ * // Use Playwright's full API directly
5857
+ * await pwPage.locator('.my-component').waitFor({ state: 'visible' });
5858
+ * await pwPage.route('**\/api/**', route => route.fulfill({ body: '{}' }));
5859
+ *
5860
+ * // Access frames
5861
+ * const frame = pwPage.frameLocator('#my-iframe');
5862
+ * ```
5863
+ */
5864
+ async playwrightPage() {
5865
+ return getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.targetId });
5866
+ }
5867
+ /**
5868
+ * Create a Playwright `Locator` for a CSS selector on this page.
5869
+ *
5870
+ * Convenience method that returns a Playwright locator without needing
5871
+ * to first obtain the Page object. Useful for one-off Playwright operations.
5872
+ *
5873
+ * @param selector - CSS selector or Playwright selector string
5874
+ * @returns A Playwright `Locator` instance
5875
+ *
5876
+ * @example
5877
+ * ```ts
5878
+ * const loc = await page.locator('.modal-dialog button.confirm');
5879
+ * await loc.waitFor({ state: 'visible' });
5880
+ * await loc.click();
5881
+ *
5882
+ * // Use Playwright selectors
5883
+ * const input = await page.locator('input[name="email"]');
5884
+ * await input.fill('test@example.com');
5885
+ * ```
5886
+ */
5887
+ async locator(selector) {
5888
+ const pwPage = await getRestoredPageForTarget({ cdpUrl: this.cdpUrl, targetId: this.targetId });
5889
+ return pwPage.locator(selector);
5890
+ }
5735
5891
  };
5736
5892
  var BrowserClaw = class _BrowserClaw {
5737
5893
  cdpUrl;
@@ -5925,6 +6081,7 @@ exports.resolvePageByTargetIdOrThrow = resolvePageByTargetIdOrThrow;
5925
6081
  exports.resolvePinnedHostnameWithPolicy = resolvePinnedHostnameWithPolicy;
5926
6082
  exports.resolveStrictExistingUploadPaths = resolveStrictExistingUploadPaths;
5927
6083
  exports.sanitizeUntrustedFileName = sanitizeUntrustedFileName;
6084
+ exports.setDialogHandler = setDialogHandler;
5928
6085
  exports.waitForChallengeViaPlaywright = waitForChallengeViaPlaywright;
5929
6086
  exports.withBrowserNavigationPolicy = withBrowserNavigationPolicy;
5930
6087
  exports.withPageScopedCdpClient = withPageScopedCdpClient;