# ego-browser Wait Conditions: Complete Guide to Element Resolution and Timing Controls

> Explore ego-browser wait conditions, including timeouts and network idle states. Learn how these helpers manage element resolution and timing for efficient web automation.

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

---

**The ego-browser provides eight specialized wait helpers in [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts) that handle everything from simple timeouts to network idle states, with only `waitForSelector` directly integrating with the element-resolution subsystem.**

ego-browser, the browser automation driver in the `citrolabs/ego-lite` repository, offers a focused set of Playwright-style wait primitives designed for agent-driven automation. These waits let scripts pause execution until specific conditions—DOM state, network activity, URL changes, or task-space control—are satisfied. This guide maps every available wait condition, shows how `waitForSelector` uniquely interacts with element resolution, and provides runnable code examples from the source.

## Available Wait Conditions in ego-browser

All public wait helpers reside in **[`package/ego-browser/src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/waits.ts)** and are exposed through the helper context as `helpers.waitFor...()`. The implementation deliberately separates concerns: most waits operate at the protocol or navigation level, while only one—`waitForSelector`—touches the DOM resolution pipeline.

### Wait Condition Overview

| Helper | Waits For | Resolution Interaction |
|--------|-----------|------------------------|
| `waitForTimeout(ms)` | Fixed sleep duration | None—direct `state.sleep()` call |
| `waitForFunction(fn, arg, opts)` | Page-side JavaScript to return truthy | None—CDP `Runtime.evaluate` only |
| `waitForURL(matcher, opts)` | URL match plus optional load state | None—string/glob/regex evaluation |
| `waitForRequest(matcher, opts)` | Matching network request | None—CDP `Network.requestWillBeSent` |
| `waitForResponse(matcher, opts)` | Matching network response | None—CDP `Network.responseReceived` |
| `waitForLoadState(state, opts)` | Page lifecycle milestone | None—navigation-level checks |
| `waitForSelector(selector, opts)` | Element presence/visibility | **Full element-resolution integration** |
| `waitForAgentControl(taskSpace)` | Non-empty task-space snapshot | None—snapshot polling in [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) |

Seven of eight waits bypass element resolution entirely. Only `waitForSelector` triggers the resolution retry logic defined in **[`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts)**.

## How waitForSelector Interacts with Element Resolution

The **`waitForSelector`** implementation (lines 491–511 in [`waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/waits.ts)) is the sole wait condition that exercises ego-browser's element-resolution pipeline. This design choice reflects a clear architectural boundary: DOM element discovery requires robust retry semantics that other wait types simply don't need.

### The Resolution-Retry Loop

When `waitForSelector` executes, it follows this pattern:

1. **Polling loop** runs until `timeout` expires (defaults to `state.defaultTimeout`)
2. **`resolveHandle` invocation** attempts to locate the element via [`src/driver/element-ops.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/element-ops.ts)
3. **Transient error detection** catches `ElementResolutionError` when the failure is temporary
4. **Automatic retry** occurs for transient failures; permanent errors abort immediately
5. **State validation** checks `visible` or `attached` condition once resolved
6. **Element handle return** or `TimeoutError` on exhaustion

The resolution system treats certain failures as **transient**—meaning the element might appear if we wait longer. This distinction, marked in `ElementResolutionError` at lines 4–5 of [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts), enables resilient automation against dynamic content.

```javascript
// Wait for element with full resolution-retry semantics
await helpers.waitForSelector('button.submit', {
  state: 'visible',    // 'attached' | 'visible'
  timeout: 5000        // override default timeout
});

```

### Ref-Map Integration and Automatic Re-Snapshot

ego-browser supports symbolic references like `@21` for previously-interacted elements. The resolution pipeline handles these through a ref-map maintained during the session. Crucially, **`waitForSelector` triggers automatic re-snapshot behavior**:

- If the ref-map is stale or empty, resolution requests a fresh **runtime snapshot**
- This snapshot updates the element registry, enabling cross-navigation selector resolution
- The retry loop accommodates snapshot latency without script-level complexity

This mechanism lives at lines 858–860 of [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) and ensures that `waitForSelector` remains robust across page transitions that invalidate previous DOM handles.

## Network and Navigation Waits

The remaining wait conditions operate at protocol or browser-event layers, requiring no DOM access.

### waitForRequest and waitForResponse

These helpers leverage CDP's `Network` domain events. Implementation at lines 120–144:

```javascript
// Capture a specific request before it completes
const loginReq = await helpers.waitForRequest('/api/login', { timeout: 3000 });
console.log('Method:', loginReq.method());

// Wait for response and extract body
const itemsResp = await helpers.waitForResponse(/\/api\/items/, { timeout: 4000 });
const data = await itemsResp.json();

```

Both builds **request/response facades** via `createRequestFacade` rather than returning raw CDP objects. No element resolution occurs—the match happens against URL patterns or predicate functions.

### waitForURL with Load-State Coordination

This helper (lines 84–115) combines URL matching with optional navigation readiness:

```javascript
// Match glob pattern and ensure DOM is ready
await helpers.waitForURL('**/dashboard/**', {
  waitUntil: 'domcontentloaded'  // 'load' | 'domcontentloaded' | 'networkidle' | 'commit'
});

```

Internally evaluates `location.href` via CDP, then conditionally delegates to `waitForLoadState` for the requested readiness level.

### waitForLoadState and Network Idle

Page lifecycle waits (lines 146–169) handle standard navigation milestones:

```javascript
// Wait for complete page load
await helpers.waitForLoadState('load');

// Wait for network quiescence (no requests for 500ms)
await helpers.waitForLoadState('networkidle', { idleMs: 500 });

```

The `networkidle` implementation polls CDP network activity rather than using element resolution, making it suitable for SPAs where `load` events don't indicate readiness.

## Agent Control and Custom Waits

### waitForAgentControl

Located in **[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)** (lines 384–398), this ego-specific wait pauses until the agent runtime produces task-space results:

```javascript
// Ensure agent has processed the task space
await helpers.waitForAgentControl('my-task-space');

```

Internally polls `ego.snapshot({maxResultLength: 1})`—no element resolution, no CDP interaction with the page DOM.

### waitForFunction and JavaScript Evaluation

For page-state conditions not expressible as selectors:

```javascript
// Wait for custom JavaScript condition
await helpers.waitForFunction((selector) => {
  return document.querySelector(selector)?.dataset.ready === 'true';
}, '[data-async-component]', { timeout: 10000, polling: 500 });

```

Uses CDP `Runtime.evaluate` on each poll interval. This is intentionally separate from `waitForSelector`—while both can wait for element presence, `waitForFunction` executes arbitrary page code without the resolution subsystem's retry semantics.

## Code Examples: Complete Patterns

```javascript
// Pattern 1: Sequential navigation with element confirmation
await helpers.goto('/checkout');
await helpers.waitForURL('**/checkout**', { waitUntil: 'networkidle' });
await helpers.waitForSelector('.payment-form', { state: 'visible', timeout: 10000 });

// Pattern 2: API call synchronization
const [submitResp] = await Promise.all([
  helpers.waitForResponse('/api/process-payment', { timeout: 30000 }),
  helpers.click('button#pay')
]);
const result = await submitResp.json();

// Pattern 3: Dynamic content with fallback polling
await helpers.waitForFunction(() => window.appReady === true, null, {
  timeout: 15000,
  polling: 200
});
// Then confirm UI update
await helpers.waitForSelector('[data-status="ready"]');

// Pattern 4: Agent workflow coordination
await helpers.waitForAgentControl('invoice-extraction');
const results = await ego.snapshot();

```

## Summary

- **Eight total wait conditions** in [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts): `waitForTimeout`, `waitForFunction`, `waitForURL`, `waitForRequest`, `waitForResponse`, `waitForLoadState`, `waitForSelector`, plus `waitForAgentControl` in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)

- **Only `waitForSelector` integrates with element resolution**, using `resolveHandle` and transient-error retry logic from [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts)

- **Automatic re-snapshot behavior** ensures `waitForSelector` works across page navigations that invalidate element references

- **All other waits** operate via CDP protocol messages, network events, or JavaScript evaluation—no DOM resolution required

- **Timeout and polling options** are consistent across helpers, with `state.defaultTimeout` as the fallback

## Frequently Asked Questions

### How does `waitForSelector` handle elements that don't exist yet?

`waitForSelector` polls the resolution pipeline until the element appears or timeout expires. Transient `ElementResolutionError` failures trigger automatic retries; permanent errors throw immediately. The polling loop also triggers re-snapshot if the ref-map is stale, enabling recovery across navigation.

### Can I combine multiple wait conditions in a single call?

Not directly—ego-browser follows Playwright's pattern of atomic waits. Use `Promise.all()` to race or synchronize conditions, or chain waits sequentially for ordered dependencies. For example, wait for URL match, then element visibility.

### What's the difference between `waitForFunction` and `waitForSelector` for element detection?

`waitForSelector` uses the full resolution subsystem with automatic retries and ref-map integration—ideal for stable selectors. `waitForFunction` runs arbitrary JavaScript without retry semantics for resolution failures; use it for custom conditions like `dataset` attribute checks or computed style evaluation.

### Does `waitForAgentControl` interact with the browser page at all?

No—`waitForAgentControl` operates purely against the ego task-space runtime. It polls `ego.snapshot()` for non-empty results, making it suitable for coordinating agent workflows without DOM dependencies.