# How ego-lite Handles Browser Session Readiness Before Executing Helpers

> Discover how ego-lite ensures browser session readiness before executing helpers. It automatically creates CDP sessions, enables domains, and polls document readyState for a stable page.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: how-to-guide
- Published: 2026-08-26

---

**ego-lite guarantees a ready browser session by automatically creating CDP sessions via `browserCdp()` in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts), enabling required domains on-demand, and polling `document.readyState` until the page reaches a stable state before executing any helper.**

When automating browser interactions with **citrolabs/ego-lite**, the framework must ensure that the Chrome DevTools Protocol (CDP) session is active and the page is in a responsive state before running high-level helpers like `page.goto()` or `locator.click()`. The library achieves this through a layered architecture that manages session lifecycle, domain enablement, and readiness polling transparently.

## The State Management Layer

At the core of ego-lite's readiness guarantee is a singleton **runtime state** defined in [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts). This module maintains the `sessionId` and `sessionTargetId` variables that track the current CDP session attachment.

When any helper invokes a CDP command, the request flows through `state.send`, which delegates to `browserCdp()` imported from [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts). If `state.sessionId` is `null`, `browserCdp()` automatically opens a fresh CDP session, stores its ID in `state.sessionId`, and records the target ID in `state.sessionTargetId`. This ensures that every subsequent command runs against an **attached session** without requiring explicit session management from the user.

## Automatic Session Attachment and CDP Evaluation

The generic CDP helper `cdp()` exported from [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts) serves as the gateway for all browser communication. This function forwards requests to `state.send`, which triggers the automatic session creation described above.

All high-level helpers in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) ultimately rely on this pipeline. Whether navigating to a URL or clicking an element, the helper calls funnel through `cdp()`, ensuring the session exists before the actual CDP command is transmitted.

## Domain Enablement for Network and Page Events

Before network-related helpers like `waitForRequest` or `waitForResponse` execute, the driver layer must enable the appropriate CDP domains. In [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts), the `acquireNetworkEvents()` function checks `state.networkDomainEnabled` and sends the `Network.enable` command if the domain is not yet active.

The same pattern applies to **Page** events when loading URLs through `waitForLoadState()`. The first call to these helpers enables the necessary domains and caches the enabled state in the runtime state object, preventing redundant enablement calls on subsequent operations.

## Readiness Checks Before Execution

Helpers that interact with page content implement explicit readiness loops to ensure the DOM is in a stable state.

**Document Load Polling**

The `waitForLoadState()` function in [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts) calls `waitForDocumentLoad()`, which repeatedly evaluates `document.readyState` via `Runtime.evaluate` until it reaches `"complete"` (or the requested state). This polling mechanism guarantees that navigation helpers do not interact with the page until the browser has finished parsing the document and executing deferred scripts.

**Selector Resolution Retries**

For element-specific operations, `waitForSelector()` attempts to resolve the selector through `resolveHandle()`. If the element is not present, the function catches transient resolution errors, invokes `state.sleep()` to pause execution, and retries the resolution. This loop continues until the element appears or the timeout expires, treating resolution failures as "not ready yet" rather than hard errors.

## Consistent Timeout Handling

Every readiness check respects the `state.defaultTimeout` value, which defaults to **10 seconds**. Individual helpers can override this default per-call, ensuring that polling loops for document readiness or selector presence never hang indefinitely. This timeout is applied consistently across the driver modules ([`src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/nav.ts), [`src/driver/pointer.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/pointer.ts), etc.), providing predictable error boundaries for all automation scripts.

## Practical Usage Examples

The following snippets demonstrate how ego-lite automatically handles session readiness without explicit setup:

```typescript
// Navigate and wait for full page load
await page.goto('https://example.com');          // → nav.goto → cdp("Page.navigate")
await page.waitForLoadState('networkidle');      // → waits.waitForLoadState → waitForNetworkIdle

```

```typescript
// Interact with an element only after it appears
await page.locator('#submit').waitFor({ timeout: 5000 }); // → waits.waitForSelector
await page.locator('#submit').click();                     // → pointer.click (uses existing session)

```

Both examples rely on the automatic session creation in `browserCdp()` and the readiness polling in `waitForDocumentLoad()` and `waitForSelector()`.

## Summary

- **Automatic Session Creation**: `state.send` delegates to `browserCdp()` in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts), which opens a new CDP session if `state.sessionId` is null.
- **On-Demand Domain Enablement**: `acquireNetworkEvents()` in [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts) enables the `Network` domain when first needed; `Page` domains are enabled similarly during load state checks.
- **Document Readiness Polling**: `waitForDocumentLoad()` polls `document.readyState` via `Runtime.evaluate` until the page reaches `"complete"`.
- **Element Selection Retries**: `waitForSelector()` uses `resolveHandle()` with exponential backoff via `state.sleep()` to wait for DOM elements.
- **Configurable Timeouts**: All helpers respect `state.defaultTimeout` (10s default) to prevent indefinite blocking.

## Frequently Asked Questions

### How does ego-lite create a browser session if one doesn't exist?

When a helper invokes `cdp()`, the call flows through `state.send` to `browserCdp()` in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts). If `state.sessionId` is null, the function automatically opens a fresh CDP session, stores the session ID in `state.sessionId`, and records the target ID in `state.sessionTargetId` before executing the requested command.

### What happens if the page hasn't finished loading when a helper is called?

Helpers that require page content, such as `waitForLoadState()`, call `waitForDocumentLoad()` which polls `document.readyState` via `Runtime.evaluate` until it returns `"complete"`. Similarly, `waitForSelector()` retries element resolution until the selector appears or the timeout expires, ensuring operations only proceed against stable DOM states.

### Which CDP domains does ego-lite enable automatically?

The framework enables the **Network** domain via `Network.enable` when `acquireNetworkEvents()` is first called from network-related wait helpers, and it enables **Page** domains during navigation and load state monitoring. The enabled state is cached in `state.networkDomainEnabled` and similar flags to prevent redundant enablement calls.

### Can I customize the timeout for readiness checks?

Yes. While all helpers default to `state.defaultTimeout` (10 seconds), you can override this on a per-call basis by passing a `timeout` option, as shown in `page.locator('#submit').waitFor({ timeout: 5000 })`. This ensures that both session creation and readiness polling respect your specified maximum wait time.