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

> Learn to handle JavaScript dialogs in ego-lite using CDP Page events. Discover detection patterns and resolution strategies to accept, dismiss, or respond to prompts effectively.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: how-to-guide
- Published: 2026-07-27

---

**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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) lines 198-200. The `pendingDialog()` function returns a copy of the stored dialog object for the current session:

```typescript
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`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/nav.ts) lines 84-90, the `pageInfo()` function short-circuits its normal execution when it detects a dialog:

```typescript
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`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts) lines 115-125 checks for pending dialogs to avoid "page is blocked by a dialog" errors:

```typescript
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`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts).

### Accepting and Dismissing Dialogs

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

```typescript
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:

```typescript
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

```typescript
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

```typescript
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`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts).
- The `pendingDialog()` function exposes active dialog state, while `pageInfo()` in [`src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts).
- Resolve dialogs explicitly using `cdp("Page.handleJavaScriptDialog", { accept: boolean, promptText?: string })` from [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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.