# Ego-Browser Wait Functions: Complete Guide to Handling Different Waiting Conditions

> Discover ego browser wait functions. This guide details eight helpers for timeouts, DOM elements, page functions, URLs, network activity, load states, and agent ownership.

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

---

**Ego‑browser provides eight distinct wait helpers in [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts) and [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) that handle timeouts, DOM elements, page functions, URLs, network requests/responses, load states, and agent task‑space ownership.**

All wait functions are exposed through the `helpers` object, making them directly callable from agent scripts. This article examines each wait function's implementation, parameters, and use cases based on the citrolabs/ego-lite source code.

## waitForTimeout: Fixed Duration Pauses

The simplest wait function pauses execution for a specified duration.

```typescript
// src/driver/waits.ts#L44
await waitForTimeout(2000);  // 2 second pause

```

**Implementation details:**
- Calls `state.sleep(ms)` internally
- Defaults to **1000 ms** when no argument provided
- No polling overhead—pure sleep-based delay

Use sparingly; prefer explicit condition waits over arbitrary timeouts.

## waitForFunction: Custom JavaScript Expressions

Polls until an arbitrary JavaScript expression returns a truthy value.

```typescript
// Poll a custom page function with 250ms intervals
const title = await waitForFunction(
  () => document.title === 'Dashboard',
  { timeout: 5000, polling: 250 }
);

```

**How it works:**
- Converts the function to a string via `buildWaitForFunctionExpression`
- Repeatedly evaluates via Chrome DevTools Protocol's `Runtime.evaluate`
- Loop continues until truthy return or timeout expires

## waitForSelector: DOM Element Presence and Visibility

The most common element wait, supporting complex locator syntax beyond CSS selectors.

```typescript
// Wait for element to exist in DOM
await waitForSelector('#login');

// Wait for visible, interactive element
await waitForSelector('#submit', { state: 'visible' });

```

**Condition evaluation:**
- Repeatedly calls `resolveHandle(selector)` to locate the element
- On successful resolution, optionally runs a visibility check via `Runtime.callFunctionOn`
- Transient resolution errors trigger retry with short sleep
- Permanent errors abort immediately

Available states: `'attached'` (default), `'visible'`, `'hidden'`.

## waitForURL: Navigation Completion

Waits for the page URL to match a pattern, with optional load state verification.

```typescript
// Glob pattern matching with network idle confirmation
await waitForURL('**/dashboard/**', { waitUntil: 'networkidle' });

// RegExp matcher
await waitForURL(/\/user\/\d+\/profile/, { timeout: 10000 });

```

**Matching logic:**
- Polls `location.href` via `Runtime.evaluate`
- Applies `urlMatches` against string, glob (`*`), RegExp, or predicate function
- Optional `waitUntil` parameter accepts: `'load'`, `'domcontentloaded'`, `'networkidle'`, `'commit'`

## Network Observation Waits

These functions automatically enable the CDP *Network* domain during operation and clean up afterward.

### waitForRequest

Captures outgoing network requests matching criteria.

```typescript
// String matcher
await waitForRequest('**/api/users', { timeout: 10000 });

// Predicate function for complex matching
await waitForRequest(req => req.method === 'POST' && req.url.includes('/submit'));

```

**Implementation:** (`src/driver/waits.ts#L26`)
- Uses internal `waitForNetworkMatch("request", …)`
- Listens for `Network.requestWillBeSent` events
- Builds request façade with URL, method, headers, postData accessors

### waitForResponse

Captures incoming network responses with full body access.

```typescript
// RegExp matcher with JSON extraction
const resp = await waitForResponse(/\.json$/, { timeout: 8000 });
const data = await resp.json();
console.log(resp.status());  // 200

```

**Response façade methods:** `status()`, `text()`, `json()`, `headers()`, `url()`

**Implementation:** (`src/driver/waits.ts#L34`)
- Mirrors `waitForRequest` architecture
- Listens for `Network.responseReceived` plus redirect chains
- Provides lazy body retrieval methods

## waitForLoadState: Page Lifecycle Milestones

Waits for specific document or network idle conditions.

```typescript
// Standard load states
await waitForLoadState('domcontentloaded');
await waitForLoadState('load');

// Network idle with custom idle duration
await waitForLoadState('networkidle', { idleMs: 500, timeout: 15000 });

```

**Two implementation paths:**

| Target | Internal function | Mechanism |
|--------|-------------------|-----------|
| `networkidle` | `waitForNetworkIdle` (private) | Polls `drainEvents` from [`src/driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts) until no network activity for `idleMs` |
| `load` / `domcontentloaded` | `waitForDocumentLoad` from [`src/driver/load.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/load.ts) | Polls `document.readyState` until target state reached |

## waitForAgentControl: Task-Space Ownership

A specialized helper defined in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) for multi-agent coordination scenarios.

```typescript
// Ensure exclusive access to a task space
await waitForAgentControl('my-task-space');

```

**Implementation:** (`src/helpers.ts#L384`)
- Captures initial snapshot via `ego.snapshot`
- Polls snapshot until task‑space ownership transfers to calling agent
- Uses `waitForBrowserEvent` to listen for `TaskSpaceChanged` events
- Prevents race conditions when human users and agents share browser instances

## Shared Timeout Behavior

All wait functions honor consistent timeout semantics:

- **Omitted timeout:** Uses `state.defaultTimeout`
- **`timeout: 0`:** Infinite wait (internally capped at `2147483647 ms` / ~24.8 days)
- **Network waits:** Automatic CDP *Network* domain lifecycle management

## Complete Usage Examples

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

// Sequential wait chain for complex login flow
async function performSecureLogin() {
  await helpers.waitForSelector('#username', { state: 'visible' });
  await helpers.waitForTimeout(500); // anti-automation debounce
  
  // Submit and wait for navigation + network quiet
  await helpers.waitForURL('**/dashboard', { 
    waitUntil: 'networkidle',
    timeout: 15000 
  });
  
  // Verify API initialization
  const initResponse = await helpers.waitForResponse('**/api/session/init');
  const session = await initResponse.json();
  
  // Confirm page-specific condition
  await helpers.waitForFunction(
    () => window.app?.initialized === true,
    { polling: 100, timeout: 5000 }
  );
}

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts) | Core wait API implementations (`waitForTimeout`, `waitForFunction`, `waitForURL`, `waitForRequest`, `waitForResponse`, `waitForLoadState`, `waitForSelector`) |
| [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) | Re-exports waits and defines `waitForAgentControl` |
| [`src/driver/load.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/load.ts) | Document load state polling (`waitForDocumentLoad`) |
| [`src/driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts) | Event draining for network idle detection |
| [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) | Low-level `waitForBrowserEvent` infrastructure |

## Summary

- **Eight wait functions** cover timeouts, DOM elements, JavaScript predicates, URLs, network requests/responses, load states, and agent control
- **Automatic CDP domain management** ensures network waits work reliably across bridge configurations
- **Consistent timeout API** with global default, explicit override, and infinite wait support
- **Locator flexibility** extends beyond CSS selectors to include @refs and custom predicates
- **Source files** in [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts) (core waits) and [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) (agent control)

## Frequently Asked Questions

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

When `timeout` is omitted, all wait functions fall back to `state.defaultTimeout`. The repository does not hard-code a single value—this is configurable at the browser context level. Explicit `timeout: 0` creates an effectively infinite wait.

### How does waitForSelector handle elements that appear then disappear?

`waitForSelector` resolves on first successful match according to the `state` option. For dynamic elements, combine with `waitForFunction` to verify stable presence, or chain multiple waits to observe state transitions.

### Can waitForRequest and waitForResponse match request bodies?

The request façade includes `postData()` for body access, but matching predicates execute against the façade object before body retrieval. For body-based matching, use `waitForResponse` and inspect the response in subsequent code rather than in the matcher itself.

### What happens if networkidle is requested but the page has persistent polling?

`waitForLoadState('networkidle')` waits until no network requests occur for `idleMs` milliseconds (default 500). Persistent polling will prevent networkidle unless the polling interval exceeds the idle threshold. Consider using `domcontentloaded` or explicit `waitForFunction` checks for such pages.