mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-03 03:27:10 +00:00
fix(verifier): populate Action.On when tap chooser picks an element
Coordinate-targeted Taps/DoubleTaps left On empty, so action-gated properties reading lastAction.on couldn't tell which target was hit and were vacuously skipped. Resolve the picked element to a stable key:value selector (resource-id, testTag, text, desc) and validate it resolves back to the same element so we don't accidentally redirect the tap to a sibling that shares the identifier.
This commit is contained in:
1 parent
ff1a8928dc
commit
ee1fad1d30
2 files changed
+60
-1
No files matched your search
@@ -421,6 +421,24 @@ func TestDoubleTapsBuiltin_TargetsClickable(t *testing.T) {
|
||||
if action.X != 50 || action.Y != 60 {
|
||||
t.Errorf("coords = (%d,%d), want (50,60) at SubmitButton center", action.X, action.Y)
|
||||
}
|
||||
// Action-gated properties read lastAction.on to tell which target the
|
||||
// chooser hit. An empty On reduces those properties to vacuously-true and
|
||||
// they never fire on the real tap event.
|
||||
if action.On == "" {
|
||||
t.Fatal("On must be populated so action-gated properties can identify the target")
|
||||
}
|
||||
if !strings.Contains(action.On, "SubmitButton") {
|
||||
t.Errorf("On = %q, want a selector containing SubmitButton", action.On)
|
||||
}
|
||||
resolved := tree.Find(action.On)
|
||||
if resolved == nil {
|
||||
t.Fatalf("On = %q does not resolve in the same tree", action.On)
|
||||
}
|
||||
rx, ry := resolved.Bounds.Center()
|
||||
if rx != action.X || ry != action.Y {
|
||||
t.Errorf("On %q resolves to (%d,%d), want the picked element's center (%d,%d)",
|
||||
action.On, rx, ry, action.X, action.Y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoubleTap_RoundTrip(t *testing.T) {
|
||||
|
||||
@@ -612,7 +612,48 @@ func (v *Verifier) generateRandomTapKind(kind ActionKind) (Action, error) {
|
||||
}
|
||||
picked := candidates[v.rng.IntN(len(candidates))]
|
||||
x, y := picked.Bounds.Center()
|
||||
return Action{Kind: kind, X: x, Y: y}, nil
|
||||
return Action{Kind: kind, On: selectorForElement(v.lastTree, picked), X: x, Y: y}, nil
|
||||
}
|
||||
|
||||
// selectorForElement builds a canonical "key:value" selector that resolves
|
||||
// back to the given element via hierarchy.Tree.Find. Prefers resource-id (the
|
||||
// testTag carrier on Android / accessibilityIdentifier on iOS), falling back
|
||||
// to text and content-description so action-gated properties can still tell
|
||||
// what was tapped even on legacy nodes without a testTag. Returns "" when no
|
||||
// candidate selector uniquely resolves to the picked element so the runner
|
||||
// keeps using the action's coordinates without re-routing to a sibling that
|
||||
// shares the same id/text.
|
||||
func selectorForElement(tree *hierarchy.Tree, element *hierarchy.Element) string {
|
||||
if element == nil || tree == nil {
|
||||
return ""
|
||||
}
|
||||
candidates := make([]string, 0, 4)
|
||||
if element.ResourceID != "" {
|
||||
candidates = append(candidates, "id:"+element.ResourceID)
|
||||
}
|
||||
// Some platforms surface the Compose testTag only in the attributes map
|
||||
// (the sidecar doesn't always promote it to resource-id). Try the raw
|
||||
// attribute keys before falling back to text-based selectors so an
|
||||
// element with a unique testTag still gets identified.
|
||||
for _, key := range []string{"testTag", "identifier", "accessibilityIdentifier"} {
|
||||
if value := element.Attributes[key]; value != "" {
|
||||
candidates = append(candidates, key+":"+value)
|
||||
}
|
||||
}
|
||||
if element.Text != "" {
|
||||
candidates = append(candidates, "text:"+element.Text)
|
||||
}
|
||||
if element.Description != "" {
|
||||
candidates = append(candidates, "desc:"+element.Description)
|
||||
}
|
||||
for _, selector := range candidates {
|
||||
resolved := tree.Find(selector)
|
||||
if resolved == nil || resolved != element {
|
||||
continue
|
||||
}
|
||||
return selector
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// inputCorpus is the edge-case string pool the typing builtin draws from to
|
||||
|
||||
Reference in new issue
Block a user