Skip to content

Conversation

@shrey150
Copy link
Contributor

@shrey150 shrey150 commented Dec 22, 2025

why

what changed

  • Add focusableMap to track focusable/interactive nodes, preserving them in a11y tree

test plan

  • Updated tests & ensured they past
  • Ran through local test suite against user report and found it passed as well

Summary by cubic

Add a focusable map to the DOM/a11y snapshot pipeline to mark focusable nodes and keep them in the accessibility tree. This preserves semantic elements (like links, buttons, and tabindex>=0) that were previously pruned.

  • New Features
    • Detect focusable nodes (native focusables + tabindex>=0) and build a focusableMap.
    • Pass focusableMap through domMapsForSession, frame collection, and a11y options.
    • Keep focusable structural nodes during pruning; if role is generic/none, use tagName as role.
    • Update types and tests to include focusableMap.

Written for commit 353fcb5. Summary will update automatically on new commits.

@changeset-bot
Copy link

changeset-bot bot commented Dec 22, 2025

🦋 Changeset detected

Latest commit: 353fcb5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@browserbasehq/stagehand Patch
@browserbasehq/stagehand-evals Patch
@browserbasehq/stagehand-server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@shrey150 shrey150 marked this pull request as ready for review December 22, 2025 22:52
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 7 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/core/lib/v3/understudy/a11y/snapshot/domTree.ts">

<violation number="1" location="packages/core/lib/v3/understudy/a11y/snapshot/domTree.ts:24">
P2: Native form elements with `disabled` attribute are not focusable. The function should check for the `disabled` attribute before returning `true` for native focusable tags, otherwise `&lt;button disabled&gt;` and `&lt;input disabled&gt;` will incorrectly be marked as focusable.</violation>

<violation number="2" location="packages/core/lib/v3/understudy/a11y/snapshot/domTree.ts:24">
P2: Anchors are treated as focusable even when they lack an href, so `&lt;a&gt;` elements that are not actually focusable will be marked as focusable in focusableMap.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Dec 22, 2025

Greptile Summary

Adds focusability tracking to preserve semantic interactive elements in accessibility tree snapshots. The implementation introduces a focusableMap that identifies DOM nodes with native focusable tags (button, input, select, etc.) or tabindex >= 0, then uses this information during a11y tree pruning to prevent removal of focusable elements.

Key Changes:

  • Added isNodeFocusable() helper that checks for native focusable tags and tabindex attributes
  • Extended type definitions to include focusableMap and focusableByBe across the snapshot pipeline
  • Modified a11y tree pruning logic to preserve focusable nodes even if they're structural or have no children
  • Updated all test fixtures to include the new focusableMap field

Impact:
This ensures that interactive elements like buttons and links remain visible in the accessibility tree for browser automation, even when they have generic/none roles or are otherwise structural elements that would normally be pruned.

Confidence Score: 4/5

  • Safe to merge with minor consideration for focusability edge cases
  • The core logic is sound and well-integrated throughout the codebase. The implementation correctly tracks focusable elements and preserves them during a11y tree pruning. However, the isNodeFocusable() function has a simplification that may not perfectly match browser behavior - it doesn't account for the disabled attribute or the requirement that <a> tags need an href to be focusable. This is a style/accuracy concern rather than a critical bug, as including extra elements in the tree is safer than excluding them.
  • Pay attention to packages/core/lib/v3/understudy/a11y/snapshot/domTree.ts - review the focusability detection logic to ensure it matches your requirements

Important Files Changed

Filename Overview
packages/core/lib/v3/understudy/a11y/snapshot/domTree.ts Added isNodeFocusable() helper and integrated focusable tracking throughout DOM traversal - logic is sound but doesn't account for disabled attribute or href requirement on <a> tags
packages/core/lib/v3/understudy/a11y/snapshot/a11yTree.ts Preserved focusable elements in a11y tree by checking focusableMap in pruning logic - prevents removal of semantically important interactive elements
packages/core/lib/v3/understudy/a11y/snapshot/capture.ts Threaded focusableMap through snapshot capture pipeline - straightforward plumbing changes with no logic issues

Sequence Diagram

sequenceDiagram
    participant Capture as capture.ts
    participant DomTree as domTree.ts
    participant A11yTree as a11yTree.ts
    
    Note over Capture: tryScopedSnapshot() called
    Capture->>DomTree: domMapsForSession()
    DomTree->>DomTree: getDomTreeWithFallback()
    DomTree->>DomTree: Traverse DOM nodes
    loop For each DOM node
        DomTree->>DomTree: isNodeFocusable(node)
        Note over DomTree: Check tag in NATIVE_FOCUSABLE_TAGS<br/>or tabindex >= 0
        DomTree->>DomTree: Store in focusableMap[encodedId]
    end
    DomTree-->>Capture: Return {tagNameMap, xpathMap, scrollableMap, focusableMap}
    
    Capture->>A11yTree: a11yForFrame(session, frameId, opts)
    Note over Capture: Pass focusableMap in opts
    A11yTree->>A11yTree: Get accessibility tree
    A11yTree->>A11yTree: buildHierarchicalTree(nodes, opts)
    
    loop For each a11y node
        A11yTree->>A11yTree: Check if isFocusable via opts.focusableMap
        alt isFocusable == true
            Note over A11yTree: Keep node in tree even if<br/>structural or has no children
            A11yTree->>A11yTree: Preserve role or use tagName
        else Not focusable
            A11yTree->>A11yTree: Apply normal pruning rules
        end
    end
    
    A11yTree-->>Capture: Return outline with preserved focusable elements
    Capture-->>Capture: Merge frames into final snapshot
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@shrey150 shrey150 requested a review from a team December 23, 2025 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants