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

> Master ego-browser navigation and waiting strategies for precise CDP automation. Learn to manage tabs and sync page states without arbitrary delays.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: deep-dive
- Published: 2026-07-30

---

**Ego-browser exposes a deterministic automation API that separates tab management in [`src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/nav.ts) from synchronization primitives in [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/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.

## Navigation API: Tab Management in nav.ts

The [`src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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.

```typescript
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`](https://github.com/citrolabs/ego-lite/blob/main/nav.ts)) searches existing tabs by URL pattern before creating new ones:

```typescript
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`](https://github.com/citrolabs/ego-lite/blob/main/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`.

```typescript
import { waitForFunction } from 'ego-browser/driver/waits';

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

```

### Navigation-Aware Waits

**`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.

```typescript
// 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.

```typescript
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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) power both navigation and waiting layers, while [`src/driver/load.js`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/load.js) supplies the low-level `waitForDocumentLoad` detector used by `waitForLoadState` and `goto`.

## Summary

- **Navigation** is handled by [`src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts), offering `waitForFunction`, `waitForURL`, `waitForSelector`, `waitForRequest`/`waitForResponse`, and `waitForLoadState` (including `"networkidle"` detection).
- **Shared infrastructure** in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) and [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) provides unified timeout deadlines, CDP session management, and the `waitForDocumentLoad` primitive from [`src/driver/load.js`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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.