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

The ego-browser provides eight specialized wait helpers in 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 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

Seven of eight waits bypass element resolution entirely. Only waitForSelector triggers the resolution retry logic defined in src/element-resolver.ts.

How waitForSelector Interacts with Element Resolution

The waitForSelector implementation (lines 491–511 in 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
  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, enables resilient automation against dynamic content.

// 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 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:

// 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:

// 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:

// 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 (lines 384–398), this ego-specific wait pauses until the agent runtime produces task-space results:

// 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:

// 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

// 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: waitForTimeout, waitForFunction, waitForURL, waitForRequest, waitForResponse, waitForLoadState, waitForSelector, plus waitForAgentControl in src/helpers.ts

  • Only waitForSelector integrates with element resolution, using resolveHandle and transient-error retry logic from 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →