# How to Debug Element Resolution Failures with the @N Reference System in Ego Lite

> Debug @N reference system failures in Ego Lite. Inspect RefMap, handle transient or permanent errors, and verify fallback lookups for element resolution.

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

---

**To debug element resolution failures with the @N reference system, inspect the RefMap entry for the numeric ID, check whether the ElementResolutionError is transient (retry with fresh snapshot) or permanent (fix locator specificity), and verify the fallback role/name lookup in the Accessibility tree when backendNodeId is stale.**

The Ego Lite browser harness uses a numeric reference system (`@N`) to track UI elements across browser sessions via the RefMap data structure. When automation scripts fail to resolve these references, understanding the distinction between transient DOM changes and permanent locator issues is essential for stable test execution. This guide walks through the resolution pipeline implemented in `citrolabs/ego-lite` to help you diagnose and fix these failures quickly.

## Understanding the @N Reference Resolution Pipeline

The resolution flow in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) follows a strict five-step pipeline when locating elements via numeric references:

1. **Parse the reference string** – The `parseRef` function in [`ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/ref-map.ts) strips the leading `@` or `ref=` prefix and validates that the remaining string consists only of digits.
2. **Lookup in RefMap** – The system calls `RefMap.get(refId)` to retrieve the stored entry containing `backendNodeId`, ARIA `role`, accessible `name`, and optional frame information.
3. **Attempt cached backendNodeId resolution** – If a `backendNodeId` exists, the resolver invokes CDP methods like `DOM.getBoxModel` or `DOM.resolveNode`. If this throws an `ElementResolutionError` due to a missing box model (element not rendered), the error is **re-thrown** for retry logic. If the node is stale (DOM changed), execution silently proceeds to step 4.
4. **Execute role/name fallback** – The `findBackendNodeIdByRoleName` function traverses the Accessibility tree to locate the element by its stored ARIA role and name. Failure here generates a **transient** `ElementResolutionError`.
5. **Return geometry or objectId** – Upon successful resolution, the system calculates the element center via `boxModelCenter` or returns an `objectId` for subsequent CDP commands.

## Distinguishing Transient from Permanent Errors

The resolver categorizes failures into two distinct kinds that dictate your debugging strategy:

**Transient Errors** occur when:
- The reference ID is unknown (missing from current snapshot)
- The cached `backendNodeId` is stale due to DOM mutations
- The element lacks a box model because it is not yet rendered or is zero-sized

**Action**: Retry the operation. The harness automatically triggers a fresh snapshot and re-attempts resolution.

**Permanent Errors** occur when:
- A selector matches multiple elements when uniqueness is required
- The selector syntax is invalid
- The AX node lacks a `backendDOMNodeId` (non-renderable elements)

**Action**: Fix the locator to be more specific or adjust your reference usage to target renderable elements only.

## Step-by-Step Debugging Workflow

When an `@N` reference fails, follow this systematic approach to identify the root cause:

### Inspect the RefMap Entry

Use `RefMap.get(id)` to examine the stored data for the failing reference. If `backendNodeId` is `undefined`, the system will rely entirely on the role/name fallback.

```typescript
console.log('RefMap entry for @5:', refMap.get('5'));
// Output: { backendNodeId: 11234, role: 'textbox', name: 'Search', ... }

```

### Check the Error Kind

Catch `ElementResolutionError` and examine the `kind` property. Transient errors warrant an automatic retry, while permanent errors require locator modification.

```typescript
if (e instanceof ElementResolutionError) {
  if (e.kind === 'transient') {
    await snapshot(); // Force fresh snapshot
  } else {
    console.error('Permanent error:', e.message);
  }
}

```

### Validate the Snapshot

Force a fresh snapshot using `await snapshot()` (exposed via the helper context) to capture the current DOM state. This is particularly useful when elements load asynchronously.

### Confirm Role-Name Pairs

When the fallback logic activates, verify that the element's ARIA role and accessible name match the RefMap entry. The lookup is performed by `findBackendNodeIdsByRoleName`, which filters the AX tree for matching nodes.

### Review Resolver Logs

The resolver provides specific error messages through the `ElementResolutionError` constructor:
- `Could not locate element with role=${role} name=${name}`
- `Element has no box model (not rendered or zero-sized)`

## Practical Code Examples

### Basic Reference Resolution

```typescript
// Resolve center coordinates for element @12
const { x, y, sessionId } = await resolveElementCenter(
  cdp,
  currentSessionId,
  refMap,
  '@12'
);

```

If the reference is unknown or stale, this call throws an `ElementResolutionError` with kind `'transient'`, triggering automatic retry logic in the harness.

### Manual Error Handling

```typescript
try {
  const center = await resolveElementCenter(cdp, sid, refMap, '@7');
  console.log('Clicked at', center.x, center.y);
} catch (e) {
  if (e instanceof ElementResolutionError) {
    if (e.kind === 'transient') {
      console.warn('Transient failure – retrying after snapshot');
      await snapshot();
      // Retry logic here
    } else {
      console.error('Permanent resolution error:', e.message);
    }
  } else {
    throw e;
  }
}

```

### Understanding the Role-Based Fallback

When `backendNodeId` is missing or stale, the resolver automatically falls back to the stored role/name pair:

```typescript
// RefMap contains: { role: 'button', name: 'Submit' }
await resolveElementCenter(cdp, sid, refMap, '@3');
// Internally invokes:
//   findBackendNodeIdByRoleName(cdp, sid, 'button', 'Submit')
//   followed by DOM.getBoxModel on the resolved node

```

## Key Source Files for Debugging

| File | Purpose |
|------|---------|
| [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts) | Core implementation of resolution logic, error handling, and fallback mechanisms |
| [`ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/ref-map.ts) | Stores snapshot-to-reference mappings and implements `parseRef` |
| `element-resolver.test.mjs` | Test suite demonstrating success and failure scenarios |
| [`driver/observe.ts`](https://github.com/citrolabs/ego-lite/blob/main/driver/observe.ts) | High-level helper usage showing integration patterns |

## Summary

- **The @N system** relies on RefMap entries containing `backendNodeId`, role, and name to locate elements across browser sessions.
- **Transient errors** indicate timing or staleness issues; retry with a fresh snapshot via `snapshot()`.
- **Permanent errors** indicate locator ambiguity or invalid selectors; refine your reference criteria.
- **Fallback logic** traverses the Accessibility tree by role/name when cached node IDs fail.
- **Debug via RefMap inspection** and `ElementResolutionError` kind classification to determine appropriate corrective action.

## Frequently Asked Questions

### What causes a transient ElementResolutionError?

A transient error occurs when the RefMap lacks the requested ID, the cached `backendNodeId` is stale due to DOM changes, or the element has no box model because it is not yet rendered. According to the source code in [`element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/element-resolver.ts), these conditions prompt an automatic retry after a fresh snapshot.

### How does the fallback mechanism work when backendNodeId is stale?

When CDP methods like `DOM.getBoxModel` fail due to a stale node ID, the resolver silently invokes `findBackendNodeIdByRoleName` to search the Accessibility tree using the stored ARIA role and name. If this lookup succeeds, the resolver proceeds with the fresh node; otherwise, it throws a transient error.

### What is the difference between @N references and CSS selectors?

The `@N` system uses numeric references that map to cached `backendNodeId` values and accessibility metadata in the RefMap, enabling fast re-resolution across page changes. CSS selectors are parsed as locator strings and resolved via `querySelector` without the caching and fallback layers provided by the reference system.

### How can I force a fresh snapshot to resolve a stale reference?

Expose the `snapshot()` function from your helper context and invoke it manually when catching a transient error. This updates the RefMap with current `backendNodeId` values and accessibility data, allowing subsequent `resolveElementCenter` calls to succeed.