# How the Ego-Browser Wait System Handles Timeouts, Load States, and Selector Waits

> Learn how Ego-Browser's wait system manages timeouts, load states, and selector waits with a unified API and deadline-based polling. Optimize your tests now.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: internals
- Published: 2026-08-07

---

**Ego-Browser provides a unified wait API in [`package/ego-browser/src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/waits.ts) that coordinates timeouts, document load milestones, and element visibility checks through a shared deadline-based polling mechanism.**

The **ego-browser wait system** in the citrolabs/ego-lite repository provides deterministic synchronization primitives for browser automation agents. Located primarily in [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts) and supporting files like [`src/driver/load.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/load.ts) and [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts), this subsystem unifies timeout management, page lifecycle monitoring, and DOM element detection into a cohesive API that agents use to pause execution until specific conditions are met.

## Timeout Configuration and Deadline Enforcement

Every wait helper respects a hierarchical timeout configuration. The system first checks `options.timeout` passed by the caller, then falls back to `state.defaultTimeout` as the global default.

In `waitForTimeout`, `waitForFunction`, `waitForURL`, and `waitForSelector`, the implementation follows this pattern as seen in the source code:

```typescript
const timeout = options.timeout ?? state.defaultTimeout;

```

All wait loops calculate a deadline using `state.now() + timeout` and exit when `state.now()` exceeds this threshold. If the timeout expires before the condition is satisfied, helpers return `false` or throw descriptive errors for network-related waits. This pattern appears consistently throughout [`waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/waits.ts) (for example, in `waitForFunction` at lines 66-81 and `waitForSelector` at lines 95-107).

## Load State Monitoring

The `waitForLoadState` function supports three distinct page lifecycle milestones that agents use to determine when a page is ready for interaction:

**`"load"`**: Waits until the document's `readyState` equals `"complete"` (the default state).

**`"domcontentloaded"`**: Waits until the document becomes interactive.

**`"networkidle"`**: Waits until no network activity occurs for a configurable idle window.

According to the source code in [`waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/waits.ts) at lines 61-68, the function delegates to specialized handlers based on the `stateName` parameter. For `"load"` and `"domcontentloaded"`, it calls `waitForDocumentLoad` (implemented in [`src/driver/load.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/load.ts)), while `"networkidle"` routes to `waitForNetworkIdle`. The network idle implementation temporarily enables the Chrome DevTools Protocol (CDP) Network domain for the duration of the wait (lines 345-383), then disables it upon completion to conserve resources.

## Selector Waiting and Element Visibility

The `waitForSelector` function implements robust polling logic for DOM element detection. Located at lines 98-132 in [`waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/waits.ts), the implementation:

- Repeatedly calls `resolveHandle(selector)` from [`src/driver/element-ops.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/element-ops.ts) until the element is found or the deadline passes
- Treats transient resolution errors as retryable, triggering a 300ms sleep before the next poll (lines 104-108)
- Supports visibility requirements via `options.state: "visible"`
- Executes a remote JavaScript function (`visibilityFn`) to verify CSS `visibility`, `opacity`, and `display` properties (lines 112-124)
- Releases CDP object handles via `releaseHandle` after each poll to prevent memory leaks (line 127)

This cleanup pattern ensures that even failed or timed-out waits properly release browser resources.

## Network Request Interception Waits

For waiting on specific HTTP traffic, the system provides `waitForRequest` and `waitForResponse`, both built on the shared `waitForNetworkMatch` helper (lines 50-77). This implementation:

- Enables the CDP Network domain on-demand via `acquireNetworkEvents` (lines 49-82)
- Listens for `Network.requestWillBeSent` and `Network.responseReceived` events through `waitForBrowserEvent` from [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts)
- Matches events against caller-provided matchers (strings, RegExp patterns, or predicate functions)
- Automatically cleans up the Network domain when no active waiters remain

## Practical Usage Examples

```typescript
// Wait for a selector to become visible with default timeout
await waitForSelector('button.submit', { state: 'visible' });

// Wait for DOM ready with explicit 5-second timeout
await waitForLoadState('domcontentloaded', { timeout: 5000 });

// Wait for network idle (500ms quiet period, 10s max)
await waitForLoadState('networkidle', { idleMs: 500, timeout: 10000 });

// Wait for URL pattern match
await waitForURL('**/dashboard/**', { waitUntil: 'load' });

// Intercept specific API request
const req = await waitForRequest(/\/api\/orders\/\d+/, { timeout: 8000 });
console.log('Captured:', req.url());

// Simple delay
await waitForTimeout(2000);

```

## Summary

- The **ego-browser wait system** centralizes all synchronization logic in [`package/ego-browser/src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/waits.ts)
- Timeouts cascade from `options.timeout` to `state.defaultTimeout`, enforced via deadline calculations using `state.now()`
- Load states support `"load"`, `"domcontentloaded"`, and `"networkidle"` through dedicated CDP domain management in [`src/driver/load.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/load.ts)
- Selector waits implement 300ms polling intervals with automatic handle cleanup via `releaseHandle` and optional visibility verification
- Network waits use temporary CDP Network domain subscription through `waitForNetworkMatch` with automatic resource cleanup

## Frequently Asked Questions

### How does ego-browser handle transient errors during selector waits?

Transient resolution errors (such as when an element doesn't yet exist in the DOM) are caught and treated as retryable conditions. According to the implementation in [`waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/waits.ts) at lines 104-108, the system sleeps for 300ms before polling again, continuing until the element is found or the timeout deadline expires. This prevents flakiness during page transitions.

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

Each wait function uses `state.defaultTimeout` as the global default when the caller doesn't specify `options.timeout`. This value is read from the runtime state object defined in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) and can be overridden on a per-operation basis.

### How does the networkidle load state determine when a page is stable?

The `waitForNetworkIdle` implementation monitors CDP Network events and considers the page idle when no network requests occur for a configurable duration (controlled via the `idleMs` option, typically 500ms). It temporarily enables the CDP Network domain during the wait and disables it afterward to minimize overhead, as implemented at lines 345-383 in [`waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/waits.ts).

### Does waitForSelector verify CSS visibility or just DOM presence?

By default, `waitForSelector` checks for DOM presence only, but you can require visibility by passing `{ state: 'visible' }`. When enabled, it executes a JavaScript function on the remote element to verify CSS properties including `visibility`, `opacity`, and `display`, ensuring the element is actually visible to users rather than merely attached to the DOM.