diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 23158be..e6d875d 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -562,27 +562,23 @@ func settleForForeground(ctx context.Context, options Options) { func applyAction(ctx context.Context, drv driver.DeviceDriver, action verifier.Action, tree *hierarchy.Tree) error { switch action.Kind { case verifier.ActionKindTap: - // A selector resolves live on-device at tap time, so it lands correctly - // even when the layout shifted between observe and apply (e.g. the soft - // keyboard finished animating out, moving every control down). Tapping - // the observe-time coordinates would miss the moved control. Coordinates - // are the fallback for selector-less actions (the web/V8 path only emits - // coordinates). - if action.On != "" { + x, y, ok := resolveCoordinates(action, tree) + if !ok { + if action.On == "" { + return nil + } return drv.TapSelector(ctx, action.On) } - if x, y, ok := resolveCoordinates(action, tree); ok { - return drv.Tap(ctx, x, y) - } - return nil + return drv.Tap(ctx, x, y) case verifier.ActionKindDoubleTap: - if action.On != "" { + x, y, ok := resolveCoordinates(action, tree) + if !ok { + if action.On == "" { + return nil + } return drv.DoubleTapSelector(ctx, action.On) } - if x, y, ok := resolveCoordinates(action, tree); ok { - return drv.DoubleTap(ctx, x, y) - } - return nil + return drv.DoubleTap(ctx, x, y) case verifier.ActionKindLongPress: x, y, ok := resolveCoordinates(action, tree) if !ok { @@ -600,18 +596,14 @@ func applyAction(ctx context.Context, drv driver.DeviceDriver, action verifier.A } return drv.Swipe(ctx, fromX, fromY, toX, toY, duration) case verifier.ActionKindInputText: - // Focus the field by selector when one is present, for the same - // layout-shift reason as Tap: InputText is the action that raises the - // keyboard, so its follow-on focus tap is the most likely to land on a - // stale position. Coordinates focus the selector-less web/V8 path. tapped := false - if action.On != "" { - if err := drv.TapSelector(ctx, action.On); err != nil { + if x, y, ok := resolveCoordinates(action, tree); ok { + if err := drv.Tap(ctx, x, y); err != nil { return err } tapped = true - } else if x, y, ok := resolveCoordinates(action, tree); ok { - if err := drv.Tap(ctx, x, y); err != nil { + } else if action.On != "" { + if err := drv.TapSelector(ctx, action.On); err != nil { return err } tapped = true diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go index 15f7618..db7c7fa 100644 --- a/internal/runner/runner_test.go +++ b/internal/runner/runner_test.go @@ -673,14 +673,13 @@ func TestApplyAction_InputTextErasesExistingTextBeforeTyping(t *testing.T) { t.Fatalf("applyAction: %v", err) } // The post-tap settle is now a brief internal sleep, not a WaitForIdle RPC, - // so the recorded driver actions are focus-tap, erase, input. The focus tap - // goes by selector (live on-device resolution) since the action carries one. + // so the recorded driver actions are tap, erase, input. actions := driverMock.Actions() if len(actions) != 3 { t.Fatalf("want tap, erase, input; got %v", actions) } - if actions[0].Kind != mockdriver.ActionTapSelector || actions[0].Selector != "id:username" { - t.Errorf("first action = %+v, want tap_selector id:username", actions[0]) + if actions[0].Kind != mockdriver.ActionTap { + t.Errorf("first action = %v, want tap", actions[0].Kind) } if actions[1].Kind != mockdriver.ActionEraseText || actions[1].CharacterCount != len("stale-value") { t.Errorf("second action = %+v, want erase_text of %d characters", actions[1], len("stale-value"))