How to Handle JavaScript Dialogs in ego-lite: Detection and Resolution Patterns

Ego-lite tracks browser-side JavaScript dialogs through CDP Page events and exposes them via pendingDialog() and pageInfo(), allowing you to accept, dismiss, or respond to prompts using the cdp() helper.

The citrolabs/ego-lite repository provides a lightweight browser automation framework that intercepts JavaScript dialogs (alert, confirm, prompt, and beforeunload) through Chrome DevTools Protocol (CDP) integration. Understanding how to handle JavaScript dialogs in ego-lite is essential for building automation scripts that don't hang on unexpected modal interactions. The framework stores dialog state in a per-session map and exposes helper functions that let you check for pending dialogs before performing actions like navigation or screenshots.

How Dialog Detection Works Internally

Ego-lite monitors dialog events at the browser runtime level through two CDP Page domain events: javascriptDialogOpening and javascriptDialogClosed. When the browser runtime receives an opening event, it stores the dialog description in an internal pendingDialogs map keyed by session ID.

The CDP Event Infrastructure

The runtime subscribes to dialog events when the session initializes. If the target browser does not support Page.enable, the runtime silently skips dialog tracking (see src/browser-runtime.ts lines 13-15), ensuring your scripts degrade gracefully rather than throwing initialization errors.

The pendingDialog() Implementation

The core detection mechanism lives in src/browser-runtime.ts lines 198-200. The pendingDialog() function returns a copy of the stored dialog object for the current session:

export function pendingDialog(sessionId = state.sessionId) {
  if (sessionId && pendingDialogs.has(sessionId)) {
    return { ...pendingDialogs.get(sessionId) };
  }
  return null;
}

This function is the primary interface used throughout the codebase to check whether a modal dialog is blocking page execution.

Checking for Active Dialogs in Your Scripts

High-level helpers automatically check for pending dialogs before executing operations that would otherwise fail or hang.

Using pageInfo() for Dialog Detection

In src/driver/nav.ts lines 84-90, the pageInfo() function short-circuits its normal execution when it detects a dialog:

export async function pageInfo() {
  if (isBrowserRuntime()) {
    await ensureSession();
    const dialog = pendingDialog();
    if (dialog) {
      return { dialog };
    }
  }
  // ... normal page-info extraction
}

When a dialog is present, pageInfo() returns an object containing only { dialog } with properties including type, message, and defaultPrompt. This allows your automation logic to branch before attempting interactions that would be blocked.

Handling Dialogs During Screenshots

The screenshot() function in src/driver/observe.ts lines 115-125 checks for pending dialogs to avoid "page is blocked by a dialog" errors:

if (!pendingDialog()) {
  // ... normal screenshot logic
} else {
  return screenshot({ ...options, path, raw: true });
}

When a dialog is detected, the function falls back to a raw screenshot that captures the entire screen buffer rather than attempting to evaluate the page layout, which would fail while a modal dialog blocks execution.

Responding to Dialogs via CDP Commands

Once detected, you must explicitly resolve the dialog by sending the Page.handleJavaScriptDialog CDP command through the cdp() helper exported from src/cdp-eval.ts.

Accepting and Dismissing Dialogs

Use the following patterns to resolve dialogs based on their type:

import { cdp, pendingDialog } from "ego-browser";

/** Accept the current dialog (default for alerts and confirm). */
async function acceptDialog() {
  const dlg = pendingDialog();
  if (!dlg) throw new Error("No dialog to accept");
  await cdp("Page.handleJavaScriptDialog", { accept: true });
}

/** Dismiss the current dialog (useful for confirm/prompt). */
async function dismissDialog() {
  const dlg = pendingDialog();
  if (!dlg) throw new Error("No dialog to dismiss");
  await cdp("Page.handleJavaScriptDialog", {
    accept: false,
  });
}

Handling Prompt Inputs

For prompt dialogs, supply the user response via the promptText parameter:

async function answerPrompt(responseText: string) {
  const dlg = pendingDialog();
  if (dlg?.type === "prompt") {
    await cdp("Page.handleJavaScriptDialog", {
      accept: true,
      promptText: responseText,
    });
  }
}

Note that promptText is only honored when the dialog type is prompt; for alert or confirm dialogs, this parameter is ignored by the browser.

Complete Workflow Implementation

Follow this sequence to robustly handle JavaScript dialogs in ego-lite automation scripts:

  1. Detect the dialog using const info = await pageInfo(); and check if (info.dialog).
  2. Inspect the dialog properties: info.dialog.type, info.dialog.message, and info.dialog.defaultPrompt.
  3. Decide whether to accept or dismiss, and prepare prompt text if needed.
  4. Execute the appropriate cdp("Page.handleJavaScriptDialog", ...) call.
  5. Resume normal operations such as click() or screenshot() once the dialog is resolved.

Practical Example: Safe Navigation with Dialog Handling

import { pageInfo, pendingDialog, cdp, goto } from "ego-browser";

async function safeNavigate(url: string) {
  await goto(url);
  const info = await pageInfo();
  
  if (info.dialog) {
    console.log(`Dialog shown: ${info.dialog.message}`);
    await cdp("Page.handleJavaScriptDialog", { accept: true });
    
    // Re-query the page after dismissing the dialog
    const refreshed = await pageInfo();
    console.log(`Now at ${refreshed.url}`);
  }
}

Practical Example: Conditional Prompt Response

async function answerPrompt(expected: string) {
  const dlg = pendingDialog();
  if (dlg?.type === "prompt") {
    await cdp("Page.handleJavaScriptDialog", {
      accept: true,
      promptText: expected,
    });
  }
}

Summary

  • Ego-lite tracks JavaScript dialogs through CDP javascriptDialogOpening events stored in a per-session pendingDialogs map in src/browser-runtime.ts.
  • The pendingDialog() function exposes active dialog state, while pageInfo() in src/driver/nav.ts returns a { dialog } envelope when dialogs block the page.
  • Screenshots automatically fall back to raw mode when dialogs are detected, preventing "page blocked" errors in src/driver/observe.ts.
  • Resolve dialogs explicitly using cdp("Page.handleJavaScriptDialog", { accept: boolean, promptText?: string }) from src/cdp-eval.ts.
  • Dialog detection degrades gracefully if the browser runtime does not support Page.enable.

Frequently Asked Questions

What types of JavaScript dialogs can ego-lite detect?

Ego-lite handles all standard browser dialogs: alert, confirm, prompt, and beforeunload. These are tracked through CDP Page events and exposed via the same pendingDialog() interface regardless of dialog type.

How do I check if a dialog is currently open without triggering an error?

Call pageInfo() and inspect the return value. If a dialog is active, the function returns { dialog } containing type, message, and defaultPrompt properties instead of the usual page metrics. Alternatively, call pendingDialog() directly to get the dialog object or null if no dialog is present.

Why does screenshot() behave differently when a dialog is open?

When screenshot() in src/driver/observe.ts detects a pending dialog via pendingDialog(), it switches to raw screenshot mode to avoid layout evaluation that would fail while the page is blocked. This ensures you can still capture the screen state even when modal dialogs prevent normal DOM access.

Can ego-lite handle dialogs in browsers that don't support CDP Page events?

Yes. According to src/browser-runtime.ts lines 13-15, if Page.enable is not supported by the target runtime, dialog tracking is silently skipped. Your scripts will continue to work, though you won't be able to detect or handle dialogs programmatically in those environments.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →