Skip to main content
BAP tracks elements across observations using a stable identity system. Instead of fragile positional refs that change every time the page updates, BAP generates refs based on element identity signals that persist across DOM changes.

Stable Refs

When you run bap observe, each interactive element gets a stable ref like @submitBtn or @e7f3a2:
@submitbtn    button    "Submit"          role:button:"Submit"
@emailinput   textbox   "Email address"   label:"Email address"
@e7f3a2       link      "Learn more"      text:"Learn more"
These refs persist across multiple observations of the same page. Clicking @submitbtn works whether you observed the page 1 second or 30 seconds ago, even if the DOM shifted.

Identity Signals

BAP uses a priority-ordered set of signals to identify elements:
1

data-testid (highest stability)

Developer-controlled, unlikely to change between deployments.
Element: <button data-testid="submit-btn">Submit</button>
Ref:     @submitbtn
2

HTML id

Fairly stable, but can be auto-generated by frameworks. Element: <input id="email-field" /> Ref: @emailfield
3

aria-label

Human-readable, good for accessibility-conscious apps. Element:{" "} <button aria-label="Close dialog">X</button> Ref: @closedialog
4

Hash-based (fallback)

Combined hash of role, name, tagName, parentRole, and siblingIndex.
Element: <a href="/about">Learn more</a>
Ref:     @e7f3a2

Ref Generation

The generateStableRef() function follows this priority:
1. data-testid → normalize → @submitbtn
2. HTML id     → normalize → @emailfield
3. aria-label  → normalize → @closedialog
4. Hash        → base36    → @e7f3a2
Normalization lowercases the string, removes special characters, and truncates to 12 characters. The @ prefix distinguishes stable refs from positional e<N> refs.

Identity Comparison

BAP compares element identities using a weighted scoring system:
SignalWeightDescription
testId3Highest weight — developer-controlled
id3High weight — usually stable
ariaLabel2Medium-high — accessibility attribute
role2Medium — ARIA role
name2Medium — accessible name
tagName1Low — HTML tag
parentRole1Low — structural context
siblingIndex1Low — position among siblings
The compareIdentities() function returns a confidence score between 0 and 1. A score of 1.0 means perfect match; a score below 0.5 suggests the elements are different.

PageElementRegistry

Each page maintains an element registry that maps stable refs to element info:
interface PageElementRegistry {
  elements: Map<string, ElementRegistryEntry>;
  lastObservation: number;
  pageUrl: string;
}

interface ElementRegistryEntry {
  ref: string; // Stable ref (e.g., "@submitbtn")
  selector: BAPSelector; // Best selector for this element
  identity: ElementIdentity; // Identity signals
  lastSeen: number; // Timestamp of last observation
  bounds?: BoundingBox; // Visual position
  cachedCssSelector?: string; // Fusion 4: cached CSS path
}

Registry Lifecycle

  1. Created when a page is first observed
  2. Updated on each bap observe — existing elements get lastSeen refreshed, new elements are added
  3. Preserved across session park/restore (dormant sessions keep registries)
  4. Cleaned up when entries go stale (not seen for 60 seconds)

Size Limits

The registry is capped at 2,000 entries per page (ELEMENT_REGISTRY_MAX_SIZE). When exceeded, the oldest entries by lastSeen are evicted first. This prevents unbounded memory growth on pages with heavy DOM churn.

Stale Entry Cleanup

The cleanupStaleEntries() function runs in two phases:
  1. Time-based: Remove entries not seen for longer than ELEMENT_STALE_THRESHOLD (60 seconds)
  2. Size-based: If still over 2,000 entries, evict the oldest by lastSeen

Self-Healing Selectors

When a primary selector fails (element moved or was removed), BAP tries fallback identity signals in order:
  1. data-testid (if available)
  2. aria-label + role combination
  3. id attribute
  4. name attribute
This resolveSelectorWithHealing() mechanism means agents can use the same ref across page navigations and dynamic content updates without manual correction.
For the most stable element targeting, encourage your web application to use data-testid attributes. BAP will generate predictable, human-readable refs like @submitbtn that survive across deployments.

Alternative Selectors

Each InteractiveElement returned by observe includes an alternativeSelectors array, ordered by reliability:
  1. testId selector (if element has data-testid)
  2. role selector (ARIA role + name)
  3. id-based CSS selector (with special character escaping)
  4. text selector
  5. css selector (computed CSS path)
Element IDs from the DOM can contain dots, colons, and brackets (valid in HTML, invalid bare in CSS). BAP escapes special characters when constructing #id CSS selectors for alternative selectors.