Validate observed Action target identity before act - #2439
Conversation
|
|
This PR is from an external contributor and must be approved by a stagehand team member with write access before CI can run. |
There was a problem hiding this comment.
All reported issues were addressed across 20 files
Architecture diagram
sequenceDiagram
participant Client as Client SDK
participant Act as actService
participant Observe as observeService
participant Cache as cacheService
participant ActHandler as actHandlerUtils
participant Locator as Locator
participant CDP as CDP (Browser)
participant Snapshot as Snapshot Engine
Note over Client,Snapshot: NEW: Target Identity Validation Flow
Client->>Observe: observe(pageId, options)
Observe->>Snapshot: captureSnapshot()
Snapshot-->>Observe: combinedXpathMap
Observe->>Observe: build actions with target from xpathMap
Observe-->>Client: actions[] with optional target
Client->>Act: act(params with action including target)
Act->>Cache: check cache
alt Cache HIT
Cache-->>Act: cached actions
Act->>Act: rebind targets to current snapshot
Act->>Snapshot: captureSnapshot()
Snapshot-->>Act: current xpathMap
Act->>Act: update: action.target = new backendNodeId
else Cache MISS
Act->>Act: proceed with original action
end
Act->>ActHandler: performUnderstudyMethod(page, frame, method, selector, args, logger, domSettleTimeoutMs, targetConstraints)
ActHandler->>ActHandler: resolveLocatorWithHops()
ActHandler->>Locator: new Locator(selector)
alt Action has target
ActHandler->>Locator: locator.withTargetGuard(expected, frameOrdinal)
Note over Locator: binds expected frameOrdinal + backendNodeId
end
alt Action type: click, fill, etc.
ActHandler->>Locator: click(), fill(), etc.
Locator->>Locator: resolveElement()
Locator->>CDP: DOM.querySelector / DOM.querySelectorAll
CDP-->>Locator: objectId
alt Target guard exists
Locator->>CDP: DOM.describeNode(objectId)
CDP-->>Locator: node.backendNodeId
Locator->>Locator: compare actual vs expected
alt Expected matches actual
Locator->>CDP: Input.dispatchMouseEvent / DOM.setInputValue
else Mismatch
Locator-->>ActHandler: throw ActionTargetMismatchError
ActHandler-->>Act: error propagates
alt Self-heal enabled
Act->>Act: selfHealAction()
Act->>Locator: resolve with new selector
Act->>ActHandler: performUnderstudyMethod(updated selector)
else Self-heal disabled
Act-->>Client: { success: false, error: "target changed" }
end
end
else No target guard
Locator->>CDP: Input.dispatchMouseEvent (legacy)
end
else Action type: dragAndDrop
ActHandler->>ActHandler: resolveLocatorWithHops(targetXpath)
alt Destination has target
ActHandler->>Locator: locator.withTargetGuard(destinationTarget)
end
ActHandler->>Locator: dragAndDrop(locator, targetLocator)
Locator->>Locator: validate both source and destination targets
Locator->>CDP: Input.dispatchDragEvent
end
Note over Locator,CDP: Validation happens before DOM mutation
alt Action succeeds
Locator-->>ActHandler: success
ActHandler-->>Act: { success: true, actions[] with target metadata }
Act-->>Client: { result: { success: true, actions } }
else Action fails validation
ActHandler-->>Act: error
alt Self-heal
Act->>Act: selfHealAction()
Act->>CDP: capture new snapshot + LLM inference
CDP-->>Act: new selector + target
Act->>ActHandler: performUnderstudyMethod(new selector, new targetConstraints)
ActHandler-->>Act: success
Act-->>Client: { result: { success: true, actions } }
end
end
Note over Cache,Act: Cache Replay with Target Rebind
Client->>Observe: observe(cached=true)
Observe->>Cache: get cached actions
Cache-->>Observe: actions with old target metadata
Observe->>Snapshot: captureSnapshot()
Snapshot-->>Observe: current xpathMap
Observe->>Observe: for each action, find matching xpath in current map
Observe->>Observe: update action.target = new backendNodeId
alt Drag-and-drop
Observe->>Observe: also rebind argumentTargets["0"]
end
Observe-->>Client: rebound actions[] with current targets
Note over Cache: Malformed Target Handling
Cache->>Cache: normalizeCachedActions()
alt target has valid schema
Cache->>Cache: preserve target
else target invalid (negative frameOrdinal, zero backendNodeId, etc.)
Cache->>Cache: drop target metadata
end
alt argumentTargets has non-numeric keys
Cache->>Cache: drop invalid entries
end
Cache-->>Client: actions with valid targets only
Note over Client,SDK: Client-Side Type Validation
Client->>Client: parse action with ActionTargetSchema
alt target passes validation
Client-->>Act: send full action with target
else target fails (negative frameOrdinal, zero backendNodeId, non-numeric keys)
Client-->>Client: throw validation error
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 9 files (changes from recent commits).
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 8 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…prove error handling for mouse wheel events
8233173 to
44766ae
Compare
Summary
target/argumentTargets(frameOrdinal+backendNodeId). Deterministic act validates identity before mutation and falls back to existing self-healing on mismatch.Test plan
observe→act(Action)after a list reorder / remount: mismatch fails or self-heals instead of clicking the wrong rowtargetstill execute as beforeargumentTargetsact,observe,action-target-validation,locator-target-guard,cache-actionsSummary by cubic
Validate observed action target identity before executing actions and rebind targets on cache hits to avoid acting on the wrong element after rerenders. Adds guard rails across clicks, drag-and-drop, key press, and mouse wheel with hit-tests; targetless press/wheel keep legacy behavior and guarded mismatches fail closed with clear errors.
New Features
ActionTarget;Actionsupports optionaltargetandargumentTargets.press/mouse.wheelresolve/focus only when guarded;dragAndDroprechecks before coordinate drag; mismatches throwActionTargetMismatchErrorwith optional self-heal./to/html[1]and text-node XPaths to actionable elements, and rebind targets on observe cache hits and act replays; malformed target metadata is ignored; rebind failures throwCachedActionRebindError.ActionTarget; tests updated across protocol, server, and SDKs.Migration
targetkeep prior behavior.Written for commit 892a139. Summary will update on new commits.