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

Ego-Browser provides a unified wait API in 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 and supporting files like src/driver/load.ts and 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:

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 (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 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), 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, the implementation:

  • Repeatedly calls resolveHandle(selector) from 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
  • 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

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

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.

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 →