# How to Wait for Specific Browser Events in ego-lite: A Complete Guide

> Learn how to wait for specific browser events in ego-lite using waitForBrowserEvent(). Pause execution until your desired Chrome DevTools Protocol event occurs. A complete guide.

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

---

**ego-lite exposes a unified event-waiting API through `waitForBrowserEvent()` that pauses agent execution until a Chrome DevTools Protocol (CDP) event matches your predicate.**

Waiting for specific browser events is essential when building reliable browser agents. In the citrolabs/ego-lite repository, the event-waiting system leverages the Chrome DevTools Protocol to synchronize agent actions with browser state. This guide explains how to wait for specific browser events in ego-lite using both low-level primitives and high-level helpers.

## How the Event-Waiting Architecture Works

The core mechanism resides in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts), which manages low-level CDP communication and maintains an internal buffer of incoming events.

### The Core Components

Three files orchestrate the waiting behavior:

- **[`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts)**: Handles CDP messaging and exposes `waitForBrowserEvent()`. This function creates a promise that resolves when an incoming CDP message satisfies your predicate.
- **[`driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/waits.ts)**: Provides ergonomic wrappers like `waitForLoadState()` and `waitForSelector()` built atop the core API.
- **[`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts)**: Stores global configuration including `state.defaultTimeout` and session identifiers.

### The Event Resolution Workflow

When you invoke `waitForBrowserEvent()`, the system executes the following steps (implemented in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts), lines 69–85):

1. **Create a waiter**: Instantiate a promise containing your predicate function and timeout duration.
2. **Register the waiter**: Push the waiter into the global `eventWaiters` list.
3. **Process incoming messages**: As CDP messages arrive via `handleMessage()`, each message is tested against every registered predicate.
4. **Resolve on match**: When a predicate returns `true`, the waiter resolves and is immediately removed from the list.

Events that do not match any predicate are buffered in the `events` array for potential future matching.

## Using the Low-Level waitForBrowserEvent API

For maximum flexibility, import `waitForBrowserEvent` directly from `ego-browser` and provide a custom predicate.

### Waiting for Generic CDP Events

The following example waits for a specific network request using the `Network.requestWillBeSent` event:

```typescript
import { waitForBrowserEvent } from 'ego-browser';

// Resolve when the first request to "example.com/api" is sent
await waitForBrowserEvent(
  (event) =>
    event.method === 'Network.requestWillBeSent' &&
    event.params?.request?.url?.includes('example.com/api'),
  5000   // optional timeout in milliseconds
);

```

This code inspects the `event.method` string and `event.params` object to identify the target request. The predicate receives every CDP event circulating through the browser runtime until it returns `true` or the timeout expires.

## High-Level Wait Helpers

While `waitForBrowserEvent()` provides complete control, the [`driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/waits.ts) module offers specialized functions for common scenarios. These helpers handle domain enablement, predicate construction, and timeout management automatically.

### Page Load State Monitoring

Use `waitForLoadState()` to pause execution until the document reaches a specific lifecycle state. According to the source code in [`driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/waits.ts) (lines 54–69), this function delegates to `waitForDocumentLoad()` and ultimately to `waitForBrowserEvent()`:

```typescript
import { waitForLoadState } from 'ego-browser';

// Wait for the 'load' event using the default timeout from state.defaultTimeout
await waitForLoadState('load');

```

### Network Idle Detection

To wait until network activity ceases, use the `networkidle` option with `waitForLoadState()`. The underlying `waitForNetworkIdle()` function (lines 46–81 in [`driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/waits.ts)) enables the Network domain, drains existing events, and resolves only after the specified idle window passes:

```typescript
import { waitForLoadState } from 'ego-browser';

// Wait until no network requests occur for 500ms, with a 15-second cap
await waitForLoadState('networkidle', { idleMs: 500, timeout: 15000 });

```

### Monitoring Specific Requests and Responses

The `waitForRequest()` and `waitForResponse()` functions allow precise interception of network traffic. Both rely on `waitForNetworkMatch()` (lines 50–65 in [`driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/waits.ts)), which constructs predicates for `waitForBrowserEvent()`:

```typescript
import { waitForRequest, waitForResponse } from 'ego-browser';

// Wait for any request whose URL ends with ".png"
const request = await waitForRequest(/\.png$/);

// Wait for the corresponding response
const response = await waitForResponse((resp) => resp.url().endsWith('.png'));

```

### Element Visibility Waits

For DOM synchronization, `waitForSelector()` (lines 85–131 in [`driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/waits.ts)) repeatedly resolves element handles and checks visibility via CDP. It utilizes [`cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/cdp-eval.ts) to execute JavaScript evaluation in the page context:

```typescript
import { waitForSelector } from 'ego-browser';

const found = await waitForSelector('#submit-button', {
  state: 'visible',
  timeout: 8000,
});
if (!found) throw new Error('Button never became visible');

```

This function integrates with [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) to handle CSS selectors, XPath expressions, and role-based locators.

## Key Implementation Files

Understanding the source structure helps when debugging complex wait scenarios:

| File | Purpose |
|------|---------|
| [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) | CDP messaging, event buffering, and `waitForBrowserEvent()` implementation (lines 69–85). |
| [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts) | High-level helpers: `waitForLoadState()`, `waitForNetworkIdle()`, `waitForRequest()`, `waitForResponse()`, and `waitForSelector()`. |
| [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) | Global runtime state including `state.defaultTimeout` and session management. |
| [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts) | JavaScript execution context for selector-based waits. |
| [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) | Locator resolution logic for `waitForSelector()`. |

## Summary

- **ego-lite** provides a unified waiting mechanism through `waitForBrowserEvent()` in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts).
- The system maintains an `eventWaiters` registry that tests every incoming CDP message against registered predicates.
- High-level helpers in [`driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/waits.ts) abstract common patterns like network idle, page load, and element visibility.
- All wait operations support configurable timeouts via `state.defaultTimeout` or explicit parameters.
- Core logic resides in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) (lines 69–85); high-level APIs live in [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts).

## Frequently Asked Questions

### What is the default timeout for wait operations in ego-lite?

When you omit the timeout parameter, functions delegate to `state.defaultTimeout` defined in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts). You can modify this global value to change the default behavior across all wait operations in your agent session.

### How does ego-lite handle multiple concurrent event waiters?

The `handleMessage()` function in [`browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/browser-runtime.ts) iterates through the `eventWaiters` array and evaluates each predicate against incoming CDP events. Multiple waiters can coexist; each resolves independently when its predicate returns `true`, and the system removes resolved waiters immediately to prevent memory leaks.

### Can I wait for custom JavaScript events using waitForBrowserEvent?

Yes. Since `waitForBrowserEvent()` accepts arbitrary predicates inspecting the `event.method` and `event.params` fields, you can construct predicates that listen for `Runtime.consoleAPICalled`, DOM mutations, or custom CDP events emitted by injected scripts. Combine this with [`cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/cdp-eval.ts) to emit custom events from the page context.

### What is the difference between waitForRequest and waitForResponse?

`waitForRequest()` resolves when the browser initiates an HTTP request (CDP event `Network.requestWillBeSent`), providing access to request headers and payload. `waitForResponse()` resolves upon receiving the corresponding `Network.responseReceived` event, exposing status codes and response headers. Both use `waitForNetworkMatch()` in [`driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/waits.ts) but filter on different CDP event types.