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:
- Polling loop runs until
timeoutexpires (defaults tostate.defaultTimeout) resolveHandleinvocation attempts to locate the element viasrc/driver/element-ops.ts- Transient error detection catches
ElementResolutionErrorwhen the failure is temporary - Automatic retry occurs for transient failures; permanent errors abort immediately
- State validation checks
visibleorattachedcondition once resolved - Element handle return or
TimeoutErroron 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, pluswaitForAgentControlinsrc/helpers.ts -
Only
waitForSelectorintegrates with element resolution, usingresolveHandleand transient-error retry logic fromsrc/element-resolver.ts -
Automatic re-snapshot behavior ensures
waitForSelectorworks 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.defaultTimeoutas 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →