Navigation and Waiting Strategies in ego-browser: Complete CDP Automation Guide

Ego-browser exposes a deterministic automation API that separates tab management in src/driver/nav.ts from synchronization primitives in src/driver/waits.ts, enabling agents to wait for precise page states rather than using arbitrary delays.

The ego-browser package within the citrolabs/ego-lite repository provides a lightweight abstraction over the Chrome DevTools Protocol (CDP). Mastering the navigation and waiting strategies in ego-browser allows you to build robust scraping and testing workflows that synchronize actions with actual browser events—such as network idle states, DOM readiness, and specific HTTP responses—rather than relying on brittle sleep timers.

The src/driver/nav.ts module implements all page movement and tab lifecycle operations. These functions wrap low-level CDP commands like Page.navigate and Target.createTarget while handling session invalidation and target caching automatically.

goto(url, options)

The goto() function initiates navigation on the current tab and optionally blocks until a load state is reached. In nav.ts lines 60‑76, the implementation issues a Page.navigate CDP command and then waits based on the waitUntil option:

  • "load" (default): Wait for the full page load event.
  • "domcontentloaded": Wait only until the DOM is parsed.
  • "commit": Return immediately after the navigation request is sent.

Additional options include timeout (default 20 000 ms) and settle (default 0 ms), which adds a configurable pause after the load event completes to allow animations or lazy-loaded resources to stabilize.

import { goto } from 'ego-browser/driver/nav';

// Navigate and wait for full load with 5s settle time
await goto('https://example.com', {
  waitUntil: 'load',
  timeout: 30000,
  settle: 5000
});

Tab Lifecycle Management

Beyond simple navigation, ego-browser provides granular control over browser tabs:

  • currentTab(): Resolves the active tab or throws if none exist.
  • newTab(url?): Creates a fresh tab via Target.createTarget and returns the new targetId.
  • switchTab(target): Activates a tab by targetId, invalidating the cached session and marking it as the preferred target.
  • closeTab(target?): Closes the specified tab (or active tab) and clears the preferred target if matched.
  • listTabs(options): Returns all known targets with an optional includeChrome filter to exclude internal chrome:// URLs.
  • ensureRealTab(): Guarantees the session is attached to a non-internal page, returning null if only Chrome internals are open.

Advanced Tab Operations

For workflows requiring tab reuse, openOrReuseTab() (lines 82‑100 in nav.ts) searches existing tabs by URL pattern before creating new ones:

await openOrReuseTab('https://example.com/dashboard', {
  match: 'origin',      // "exact"|"origin"|"origin+path"|"includes"
  wait: true,           // Wait for load state after switching
  timeout: 15000,
  settle: 1000
});

The iframeTarget(urlSubstring) helper locates embedded frames by URL substring, returning the targetId of matching iframes for context switching into nested documents.

Waiting Primitives: Synchronization in waits.ts

The src/driver/waits.ts module exports a comprehensive toolkit for blocking execution until specific browser conditions are met. All wait helpers share a unified timeout strategy: they compute a deadline using state.now() + timeout and throw descriptive errors if the condition is not satisfied.

Time-Based and Function Polling

  • waitForTimeout(ms): Simple sleep wrapper defaulting to 1000 ms.
  • waitForFunction(pageFunction, argOrOptions?, options?): Repeatedly evaluates a browser-side expression until it returns truthy. The default polling interval is 100 ms, and it respects state.defaultTimeout.
import { waitForFunction } from 'ego-browser/driver/waits';

// Poll every 200ms until custom condition is met
await waitForFunction(
  () => window.myAppReady === true,
  { timeout: 10000, polling: 200 }
);

waitForURL(url, options) (lines 91‑115) polls location.href until it matches a string, glob pattern, RegExp, or predicate function. If the URL matches and waitUntil is not set to "commit", the function delegates to waitForLoadState to ensure the page has reached the desired milestone.

waitForLoadState(stateOrOptions, options) accepts three states:

  • "load": Standard window load event.
  • "domcontentloaded": DOM ready without waiting for resources.
  • "networkidle": No network requests for a specified idleMs period (default 500 ms).

Network-Level Synchronization

For API-driven workflows, ego-browser can observe individual network events:

  • waitForRequest(urlOrPredicate, options): Resolves when a matching request is initiated.
  • waitForResponse(urlOrPredicate, options): Resolves when a matching response is received.

Both functions temporarily enable the CDP Network domain if not already active, ensuring events are captured even in minimal runtime configurations.

// Wait for API call before proceeding
await waitForResponse('**/api/data.json', { timeout: 5000 });
await waitForLoadState('networkidle', { idleMs: 300 });

Element-Ready Detection

waitForSelector(selector, options) polls for CSS selectors (or special prefixes like @ref, loc=, xpath=) until the element is "attached" (in DOM) or "visible" (intersecting viewport). The implementation (lines 92‑131) handles transient ElementResolutionError by retrying with short pauses, making it resilient to DOM mutations during page load.

await waitForSelector('#submit-button', {
  state: 'visible',
  timeout: 8000
});

Integrating Navigation with Waits

The waitUntil Option and Settle Time

Navigation and waiting intersect through the waitUntil parameter found in goto, waitForURL, and openOrReuseTab. This option determines whether the function returns immediately after the Page.navigate CDP command ("commit") or blocks until the document reaches "domcontentloaded" or "load".

The settle option adds deterministic stability. After the load state is achieved, goto inserts an additional sleep (default 0 ms) to account for JavaScript hydration, lazy image loading, or CSS transitions that occur post-load.

Shared Timeout Handling and State Management

All wait strategies rely on the global state singleton defined in src/state.ts, which provides:

  • state.defaultTimeout: Shared fallback for wait operations.
  • state.now() and state.sleep: Utilities for deadline calculation and async pausing.

When timeouts occur, functions throw explicit errors such as "page.waitForRequest timed out after X ms", enabling precise error handling in agent logic.

The underlying cdp function and session utilities from src/browser-runtime.ts power both navigation and waiting layers, while src/driver/load.js supplies the low-level waitForDocumentLoad detector used by waitForLoadState and goto.

Summary

  • Navigation is handled by src/driver/nav.ts, providing goto, newTab, switchTab, closeTab, and openOrReuseTab with waitUntil options ("load", "domcontentloaded", "commit") and configurable settle delays.
  • Waiting is implemented in src/driver/waits.ts, offering waitForFunction, waitForURL, waitForSelector, waitForRequest/waitForResponse, and waitForLoadState (including "networkidle" detection).
  • Shared infrastructure in src/state.ts and src/browser-runtime.ts provides unified timeout deadlines, CDP session management, and the waitForDocumentLoad primitive from src/driver/load.js.
  • Error handling is consistent across all waiters, computing deadlines via state.now() + timeout and throwing descriptive messages on expiry.

Frequently Asked Questions

What is the difference between "load" and "networkidle" in ego-browser?

"load" waits for the browser's standard window.load event, indicating that all static resources (HTML, images, CSS) have finished loading. "networkidle" (implemented in waitForNetworkIdle with a default 500 ms idleMs) waits until no network requests have been initiated for a specified duration, which is stricter and useful for single-page applications that continue fetching data after the initial load.

How do I switch between tabs without losing session context?

Use switchTab(targetId) from src/driver/nav.ts. This function invalidates the cached CDP session and marks the new tab as the preferred target, ensuring subsequent commands execute in the correct context. If you need to return to a previous tab later, store the targetId returned by currentTab() or listTabs() before switching.

Can ego-browser wait for specific API responses before proceeding?

Yes. waitForResponse(urlOrPredicate, options) enables the CDP Network domain temporarily and resolves when a matching response is observed. You can pass a glob pattern like '**/api/data.json' or a RegExp. This is particularly useful for intercepting GraphQL or REST API calls that populate dynamic content.

What happens if waitForSelector times out in ego-browser?

When waitForSelector exceeds its timeout (defaulting to state.defaultTimeout), it throws a timeout error specifying the selector and duration. The polling loop (lines 92‑131 in waits.ts) automatically retries on transient element resolution errors, but if the element never appears or remains hidden (when state: 'visible' is required), the function rejects with a clear error message indicating the failure.

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 →