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)fromsrc/driver/element-ops.tsuntil 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 CSSvisibility,opacity, anddisplayproperties (lines 112-124) - Releases CDP object handles via
releaseHandleafter 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.requestWillBeSentandNetwork.responseReceivedevents throughwaitForBrowserEventfromsrc/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.timeouttostate.defaultTimeout, enforced via deadline calculations usingstate.now() - Load states support
"load","domcontentloaded", and"networkidle"through dedicated CDP domain management insrc/driver/load.ts - Selector waits implement 300ms polling intervals with automatic handle cleanup via
releaseHandleand optional visibility verification - Network waits use temporary CDP Network domain subscription through
waitForNetworkMatchwith 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →