mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
fix(runner): an ambiguous name loses to the coordinates it was built from
Attribute values match by substring, so a selector that named one element where the candidate was built can name several in the tree it resolves against, and the lookup sent every one of them to the first match. The host gates blank an ambiguous tag at enumeration time; this closes the gap between that moment and the action. A bare-string target carries no coordinates, so the first match stays the answer there rather than dropping an authored action. Claude-Session: https://claude.ai/code/session_01A5KmftdEJ49A9z5mF5ESrX
This commit is contained in:
1 parent
fd7bf17256
commit
bf2da973de
2 files changed
+56
-2
No files matched your search
@@ -903,8 +903,15 @@ func resolveCoordinates(action verifier.Action, tree *hierarchy.Tree) (int, int,
|
|||||||
return 0, 0, false
|
return 0, 0, false
|
||||||
}
|
}
|
||||||
if tree != nil {
|
if tree != nil {
|
||||||
if element := tree.Find(action.On); element != nil {
|
// An ambiguous selector names several elements while the action's own
|
||||||
x, y := element.Bounds.Center()
|
// coordinates name one, so the coordinates win. Attribute values match
|
||||||
|
// by substring, so a selector unique where the candidate was built can
|
||||||
|
// be ambiguous in the tree it resolves against. A bare-string target
|
||||||
|
// carries no coordinates, and there the name is all there is.
|
||||||
|
matches := tree.FindAll(action.On)
|
||||||
|
hasCoordinates := action.X > 0 && action.Y > 0
|
||||||
|
if len(matches) > 0 && (len(matches) == 1 || !hasCoordinates) {
|
||||||
|
x, y := matches[0].Bounds.Center()
|
||||||
if x > 0 && y > 0 {
|
if x > 0 && y > 0 {
|
||||||
return x, y, true
|
return x, y, true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1037,6 +1037,53 @@ func TestApplyAction_V8InputTextAtOriginStillTaps(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attribute values match by substring, so "data-testid:card" answers to
|
||||||
|
// "card-1" and "card-10" alike. A candidate built where the selector named one
|
||||||
|
// element can execute where it names several, and the tree lookup would send
|
||||||
|
// every one of them to the first match.
|
||||||
|
func TestApplyAction_AmbiguousSelectorTapsTheActionsOwnCoordinates(t *testing.T) {
|
||||||
|
tree, err := hierarchy.Parse(`{"attributes":{"resource-id":"root","bounds":"[0,0,1080,2340]"},"children":[
|
||||||
|
{"attributes":{"data-testid":"card-1","bounds":"[0,100,200,200]"},"children":[]},
|
||||||
|
{"attributes":{"data-testid":"card-10","bounds":"[0,300,200,400]"},"children":[]}
|
||||||
|
]}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse: %v", err)
|
||||||
|
}
|
||||||
|
driverMock := mockdriver.New()
|
||||||
|
action := verifier.Action{Kind: verifier.ActionKindTap, On: "data-testid:card-1", X: 100, Y: 350}
|
||||||
|
|
||||||
|
mustDispatch(t, driverMock, action, tree)
|
||||||
|
for _, dispatched := range driverMock.Actions() {
|
||||||
|
if dispatched.Kind == mockdriver.ActionTap && dispatched.X == 100 && dispatched.Y == 350 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Errorf("tap reached the driver at %v, want the action's own (100,350)", driverMock.Actions())
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bare-string target carries no coordinates of its own, so an ambiguous name
|
||||||
|
// is all there is to act on and the first match stays the answer. Refusing it
|
||||||
|
// would drop an authored action.
|
||||||
|
func TestApplyAction_AmbiguousSelectorWithoutCoordinatesTapsTheFirstMatch(t *testing.T) {
|
||||||
|
tree, err := hierarchy.Parse(`{"attributes":{"resource-id":"root","bounds":"[0,0,1080,2340]"},"children":[
|
||||||
|
{"attributes":{"data-testid":"card-1","bounds":"[0,100,200,200]"},"children":[]},
|
||||||
|
{"attributes":{"data-testid":"card-10","bounds":"[0,300,200,400]"},"children":[]}
|
||||||
|
]}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse: %v", err)
|
||||||
|
}
|
||||||
|
driverMock := mockdriver.New()
|
||||||
|
action := verifier.Action{Kind: verifier.ActionKindTap, On: "data-testid:card-1"}
|
||||||
|
|
||||||
|
mustDispatch(t, driverMock, action, tree)
|
||||||
|
for _, dispatched := range driverMock.Actions() {
|
||||||
|
if dispatched.Kind == mockdriver.ActionTap && dispatched.X == 100 && dispatched.Y == 150 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Errorf("tap reached the driver at %v, want the first match's centre (100,150)", driverMock.Actions())
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplyAction_DoubleTapDispatchesDoubleTapAtCoordinates(t *testing.T) {
|
func TestApplyAction_DoubleTapDispatchesDoubleTapAtCoordinates(t *testing.T) {
|
||||||
driverMock := mockdriver.New()
|
driverMock := mockdriver.New()
|
||||||
action := verifier.Action{Kind: verifier.ActionKindDoubleTap, X: 100, Y: 200}
|
action := verifier.Action{Kind: verifier.ActionKindDoubleTap, X: 100, Y: 200}
|
||||||
|
|||||||
Reference in new issue
Block a user