Skip to content

Conversation

@seanmcguire12
Copy link
Member

@seanmcguire12 seanmcguire12 commented Dec 20, 2025

why

  • add more unit tests for the functions that captureHybridSnapshot() depends on

what changed

added snapshot-a11y-resolvers.test.ts which tests the following functions:

  • a11yForFrame()
    • checks that the unscoped path/no selector provided path returns the full AX outline and url map
    • checks that xpath scoping trims the tree to the resolved node, including the branch where frame scoping fails, and it retries without a frameId
    • checks that when selector resolution throws (e.g., CSS resolver rejects), the function falls back to the full tree and leaves scopeApplied false
  • resolveObjectIdForXPath()
    • checks that it waits for the frame’s main world, evaluates the helper script, and returns the resulting object id
    • checks that that runtime exceptions (or missing contexts) cause it to return null
  • resolveObjectIdForCss()
    • checks that on primaryExpr evaluation success, the object id is returned
    • checks that fallbackExpr is used when primaryExpr fails
    • checks that null is returned if both primaryExpr and fallbackExpr fail
  • tryScopedSnapshot()
    • checks that the CSS and xpath focus selectors both produce prefixed combined maps when scoping succeeds
    • checks that when a11yForFrame() returns scopeApplied: false the helper logs and returns null
    • checks that when focus resolution throws, the catch block logs and returns null

test plan

  • this is it

Summary by cubic

Adds unit tests covering AX tree scoping, CSS/XPath object ID resolvers, DOM session builders, and CBOR fallback paths to harden captureHybridSnapshot and related helpers.

  • Refactors
    • Exported tryScopedSnapshot for direct testing.
    • Replaced ad-hoc test session with shared MockCDPSession.

Written for commit 1117b16. Summary will update automatically on new commits.

@changeset-bot
Copy link

changeset-bot bot commented Dec 20, 2025

⚠️ No Changeset found

Latest commit: 1d219bf

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Dec 20, 2025

Greptile Summary

This PR adds comprehensive unit tests for the accessibility tree fetching and object ID resolution logic that captureHybridSnapshot() depends on. The tests cover four main functions:

  • a11yForFrame() - validates full tree retrieval, XPath scoping with frame retry fallback, and CSS resolver error handling
  • resolveObjectIdForXPath() - confirms main world context evaluation and null return on runtime exceptions
  • resolveObjectIdForCss() - tests primary evaluation, fallback to pierce selector, and null return on dual failure
  • tryScopedSnapshot() - verifies CSS/XPath focus resolution, prefix computation, fallback logging, and error handling

The only production code change is exporting tryScopedSnapshot from capture.ts to make it directly testable. All tests use the shared MockCDPSession helper and properly mock dependencies like executionContexts, domTree, and a11yTree modules.

Key strengths:

  • Thorough edge case coverage (frame scope errors, resolver failures, missing contexts)
  • Proper use of vitest mocking with beforeEach cleanup
  • Tests validate both success and failure paths
  • Clear test descriptions matching implementation behavior

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk - it adds test coverage without changing production behavior
  • Score reflects thorough test implementation with proper mocking, comprehensive edge case coverage, and only a minimal export change to production code for testability. All tests validate existing behavior without introducing new functionality or breaking changes
  • No files require special attention

Important Files Changed

Filename Overview
packages/core/lib/v3/understudy/a11y/snapshot/capture.ts Exports tryScopedSnapshot function to enable direct unit testing
packages/core/tests/snapshot-a11y-resolvers.test.ts Comprehensive test coverage for AX tree fetching, object ID resolvers, and scoped snapshot logic with proper mocking and edge case handling

Sequence Diagram

sequenceDiagram
    participant Test as Test Suite
    participant a11y as a11yForFrame()
    participant resolve as resolveObjectId*()
    participant scoped as tryScopedSnapshot()
    participant session as MockCDPSession
    participant ctx as executionContexts
    
    Note over Test,ctx: Test Flow for AX Tree & Object ID Resolvers
    
    rect rgb(240, 248, 255)
        Note right of Test: a11yForFrame() Tests
        Test->>a11y: call with focusSelector
        a11y->>session: Accessibility.getFullAXTree
        session-->>a11y: return nodes
        a11y->>resolve: resolveObjectIdForXPath/Css
        resolve->>ctx: waitForMainWorld(frameId)
        ctx-->>resolve: return contextId
        resolve->>session: Runtime.evaluate
        session-->>resolve: return objectId
        resolve-->>a11y: objectId or null
        a11y->>session: DOM.describeNode
        session-->>a11y: backendNodeId
        a11y-->>Test: {outline, urlMap, scopeApplied}
    end
    
    rect rgb(255, 248, 240)
        Note right of Test: resolveObjectId Tests
        Test->>resolve: resolveObjectIdForXPath/Css
        resolve->>ctx: waitForMainWorld(frameId)
        alt context available
            ctx-->>resolve: contextId
            resolve->>session: Runtime.evaluate
            alt success
                session-->>resolve: {result: {objectId}}
                resolve-->>Test: objectId
            else exception
                session-->>resolve: {exceptionDetails}
                resolve-->>Test: null
            end
        else context missing
            ctx-->>resolve: throw error
            resolve-->>Test: null
        end
    end
    
    rect rgb(248, 255, 240)
        Note right of Test: tryScopedSnapshot() Tests
        Test->>scoped: call with focusSelector
        alt CSS selector
            scoped->>resolve: resolveCssFocusFrameAndTail
            resolve-->>scoped: {targetFrameId, tailSelector, absPrefix}
        else XPath selector
            scoped->>resolve: resolveFocusFrameAndTail
            resolve-->>scoped: {targetFrameId, tailXPath, absPrefix}
        end
        scoped->>a11y: a11yForFrame(tailSelector)
        a11y-->>scoped: {outline, urlMap, scopeApplied}
        alt scopeApplied true
            scoped-->>Test: HybridSnapshot
        else scopeApplied false
            scoped->>scoped: logScopeFallback()
            scoped-->>Test: null
        end
    end
Loading

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.

No issues found across 5 files

Architecture diagram
sequenceDiagram
    participant Caller as Snapshot Capture
    participant Scope as tryScopedSnapshot
    participant Resolve as Object ID Resolver
    participant CDP as CDP / MockSession
    participant Context as Execution Contexts

    Note over Caller,Context: Diagram 1: Scoped Accessibility & Focus Resolution (New Test Coverage)

    Caller->>Scope: tryScopedSnapshot(page, options)
    
    alt Selectors Provided (CSS or XPath)
        Scope->>Resolve: resolveObjectId(selector)
        
        alt Strategy: XPath
            Resolve->>Context: waitForMainWorld()
            Context-->>Resolve: contextId
            Resolve->>CDP: Runtime.evaluate(xpath, contextId)
        else Strategy: CSS
            Resolve->>CDP: Runtime.evaluate(primarySelector)
            opt Primary Evaluation Fails
                Resolve->>CDP: Runtime.evaluate(fallbackPierceSelector)
            end
        end

        alt Resolution Success
            CDP-->>Resolve: objectId
            Resolve-->>Scope: objectId
            Scope->>CDP: Accessibility.getFullAXTree(objectId)
            CDP-->>Scope: Trimmed AX Tree
            Scope-->>Caller: Scoped Snapshot (scopeApplied: true)
        else Resolution Fails / Error
            Resolve-->>Scope: null
            Scope-->>Caller: null (Fallback to full snapshot)
        end
    else No Selector
        Scope-->>Caller: null
    end

    Caller->>CDP: Accessibility.getFullAXTree(depth: -1)
    CDP-->>Caller: Full AX Tree

    
sequenceDiagram
    participant Service as Snapshot Service
    participant Builder as DOM Builder
    participant CDP as CDP / MockSession

    Note over Service,CDP: Diagram 2: Robust DOM Fetching with CBOR Fallback (New Test Coverage)

    Service->>Builder: getDomTreeWithFallback()
    
    Builder->>CDP: DOM.getDocument(depth: -1)
    
    alt Standard Success
        CDP-->>Builder: Full DOM Tree
    else CBOR Error (Stack Limit Exceeded)
        Note right of Builder: NEW: Retry logic for large DOMs
        Builder->>CDP: DOM.getDocument(depth: 256)
        CDP-->>Builder: Partial DOM Tree
        
        loop For every truncated node
            Builder->>Builder: hydrateDomTree(node)
            Builder->>CDP: DOM.describeNode(backendNodeId, depth: 2)
            
            alt Describe Success
                CDP-->>Builder: Node Details
            else CBOR Error on Describe
                Note right of Builder: Retry describe once
                Builder->>CDP: DOM.describeNode(backendNodeId)
                CDP-->>Builder: Node Details
            end
        end
    else Network/Other Error
        CDP-->>Builder: Throw Error
        Builder-->>Service: Throw StagehandDomProcessError
    end

    Builder-->>Service: Fully Hydrated DOM Root
Loading

@seanmcguire12 seanmcguire12 force-pushed the seanmcguire/stg-1098-add-unit-tests-for-ax-tree-fetching branch from 1117b16 to 1d219bf Compare December 20, 2025 00:16
@seanmcguire12
Copy link
Member Author

@greptileai

@seanmcguire12 seanmcguire12 merged commit 36a7940 into main Dec 20, 2025
17 checks passed
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.

3 participants