Skip to content

Commit b7ca418

Browse files
authored
feat: support custom touch gestures
1 parent 8819d08 commit b7ca418

File tree

9 files changed

+239
-35
lines changed

9 files changed

+239
-35
lines changed

commands/input.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package commands
22

33
import (
4+
"encoding/json"
45
"fmt"
6+
7+
"github.com/mobile-next/mobilecli/devices/wda"
58
)
69

710
// TapRequest represents the parameters for a tap command
@@ -23,6 +26,12 @@ type ButtonRequest struct {
2326
Button string `json:"button"`
2427
}
2528

29+
// GestureRequest represents the parameters for a gesture command
30+
type GestureRequest struct {
31+
DeviceID string `json:"deviceId"`
32+
Actions []interface{} `json:"actions"`
33+
}
34+
2635
// TapCommand performs a tap operation on the specified device
2736
func TapCommand(req TapRequest) *CommandResponse {
2837
if req.X < 0 || req.Y < 0 {
@@ -100,3 +109,44 @@ func ButtonCommand(req ButtonRequest) *CommandResponse {
100109
"message": fmt.Sprintf("Pressed button '%s' on device %s", req.Button, targetDevice.ID()),
101110
})
102111
}
112+
113+
// GestureCommand performs a gesture operation on the specified device
114+
func GestureCommand(req GestureRequest) *CommandResponse {
115+
if len(req.Actions) == 0 {
116+
return NewErrorResponse(fmt.Errorf("actions array is required and cannot be empty"))
117+
}
118+
119+
targetDevice, err := FindDeviceOrAutoSelect(req.DeviceID)
120+
if err != nil {
121+
return NewErrorResponse(fmt.Errorf("error finding device: %v", err))
122+
}
123+
124+
err = targetDevice.StartAgent()
125+
if err != nil {
126+
return NewErrorResponse(fmt.Errorf("failed to start agent on device %s: %v", targetDevice.ID(), err))
127+
}
128+
129+
// Convert []interface{} to []wda.TapAction
130+
tapActions := make([]wda.TapAction, len(req.Actions))
131+
for i, action := range req.Actions {
132+
actionBytes, err := json.Marshal(action)
133+
if err != nil {
134+
return NewErrorResponse(fmt.Errorf("failed to marshal action at index %d: %v", i, err))
135+
}
136+
137+
var tapAction wda.TapAction
138+
if err := json.Unmarshal(actionBytes, &tapAction); err != nil {
139+
return NewErrorResponse(fmt.Errorf("failed to unmarshal action at index %d: %v", i, err))
140+
}
141+
tapActions[i] = tapAction
142+
}
143+
144+
err = targetDevice.Gesture(tapActions)
145+
if err != nil {
146+
return NewErrorResponse(fmt.Errorf("failed to perform gesture on device %s: %v", targetDevice.ID(), err))
147+
}
148+
149+
return NewSuccessResponse(map[string]interface{}{
150+
"message": fmt.Sprintf("Performed gesture on device %s with %d actions", targetDevice.ID(), len(req.Actions)),
151+
})
152+
}

devices/android.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"path/filepath"
88
"strconv"
99
"strings"
10+
"time"
1011

12+
"github.com/mobile-next/mobilecli/devices/wda"
1113
"github.com/mobile-next/mobilecli/utils"
1214
)
1315

@@ -100,6 +102,42 @@ func (d AndroidDevice) Tap(x, y int) error {
100102
return nil
101103
}
102104

105+
// Gesture performs a sequence of touch actions on the Android device
106+
func (d AndroidDevice) Gesture(actions []wda.TapAction) error {
107+
108+
x := 0
109+
y := 0
110+
111+
for _, action := range actions {
112+
var cmd []string
113+
114+
if action.Type == "pause" {
115+
time.Sleep(time.Duration(action.Duration) * time.Millisecond)
116+
continue
117+
}
118+
119+
switch action.Type {
120+
case "pointerDown":
121+
cmd = []string{"shell", "input", "touchscreen", "motionevent", "down", fmt.Sprintf("%d", x), fmt.Sprintf("%d", y)}
122+
case "pointerMove":
123+
x = action.X
124+
y = action.Y
125+
cmd = []string{"shell", "input", "touchscreen", "motionevent", "move", fmt.Sprintf("%d", action.X), fmt.Sprintf("%d", action.Y)}
126+
case "pointerUp":
127+
cmd = []string{"shell", "input", "touchscreen", "motionevent", "up", fmt.Sprintf("%d", x), fmt.Sprintf("%d", y)}
128+
default:
129+
return fmt.Errorf("unsupported gesture action type: %s", action.Type)
130+
}
131+
132+
_, err := d.runAdbCommand(cmd...)
133+
if err != nil {
134+
return fmt.Errorf("failed to execute gesture action %s: %v", action.Type, err)
135+
}
136+
}
137+
138+
return nil
139+
}
140+
103141
func parseAdbDevicesOutput(output string) []ControllableDevice {
104142
var devices []ControllableDevice
105143

devices/common.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package devices
33
import (
44
"fmt"
55

6+
"github.com/mobile-next/mobilecli/devices/wda"
67
"github.com/mobile-next/mobilecli/utils"
78
)
89

@@ -15,6 +16,7 @@ type ControllableDevice interface {
1516
TakeScreenshot() ([]byte, error)
1617
Reboot() error
1718
Tap(x, y int) error
19+
Gesture(actions []wda.TapAction) error
1820
StartAgent() error
1921
SendKeys(text string) error
2022
PressButton(key string) error

devices/ios.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ func (d IOSDevice) Tap(x, y int) error {
9595
return wda.Tap(x, y)
9696
}
9797

98+
func (d IOSDevice) Gesture(actions []wda.TapAction) error {
99+
return wda.Gesture(actions)
100+
}
101+
98102
func (d IOSDevice) StartAgent() error {
99103
_, err := wda.GetWebDriverAgentStatus()
100104
return err

devices/simulator.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,10 @@ func (s SimulatorDevice) Tap(x, y int) error {
418418
return wda.Tap(x, y)
419419
}
420420

421+
func (s SimulatorDevice) Gesture(actions []wda.TapAction) error {
422+
return wda.Gesture(actions)
423+
}
424+
421425
func (s *SimulatorDevice) OpenURL(url string) error {
422426
return exec.Command("xcrun", "simctl", "openurl", s.ID(), url).Run()
423427
}

devices/wda/gesture.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package wda
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
func Gesture(actions []TapAction) error {
9+
sessionId, err := CreateSession()
10+
if err != nil {
11+
return err
12+
}
13+
14+
defer DeleteSession(sessionId)
15+
16+
data := ActionsRequest{
17+
Actions: []Pointer{
18+
{
19+
Type: "pointer",
20+
ID: "finger1",
21+
Parameters: PointerParameters{
22+
PointerType: "touch",
23+
},
24+
Actions: actions,
25+
},
26+
},
27+
}
28+
29+
_, err = PostWebDriverAgentEndpoint(fmt.Sprintf("session/%s/actions", sessionId), data)
30+
if err != nil {
31+
return err
32+
}
33+
return nil
34+
}
35+
36+
func GestureFromJSON(jsonData []byte) error {
37+
var actions []TapAction
38+
if err := json.Unmarshal(jsonData, &actions); err != nil {
39+
return fmt.Errorf("failed to parse gesture actions: %v", err)
40+
}
41+
42+
return Gesture(actions)
43+
}

devices/wda/tap.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,6 @@ import (
44
"fmt"
55
)
66

7-
type TapAction struct {
8-
Type string `json:"type"`
9-
Duration int `json:"duration,omitempty"`
10-
X int `json:"x,omitempty"`
11-
Y int `json:"y,omitempty"`
12-
Button int `json:"button,omitempty"`
13-
}
14-
15-
type PointerParameters struct {
16-
PointerType string `json:"pointerType"`
17-
}
18-
19-
type Pointer struct {
20-
Type string `json:"type"`
21-
ID string `json:"id"`
22-
Parameters PointerParameters `json:"parameters"`
23-
Actions []TapAction `json:"actions"`
24-
}
25-
26-
type ActionsRequest struct {
27-
Actions []Pointer `json:"actions"`
28-
}
29-
307
func Tap(x, y int) error {
318

329
sessionId, err := CreateSession()

devices/wda/types.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package wda
2+
3+
type TapAction struct {
4+
Type string `json:"type"`
5+
Duration int `json:"duration,omitempty"`
6+
X int `json:"x,omitempty"`
7+
Y int `json:"y,omitempty"`
8+
Button int `json:"button,omitempty"`
9+
}
10+
11+
type PointerParameters struct {
12+
PointerType string `json:"pointerType"`
13+
}
14+
15+
type Pointer struct {
16+
Type string `json:"type"`
17+
ID string `json:"id"`
18+
Parameters PointerParameters `json:"parameters"`
19+
Actions []TapAction `json:"actions"`
20+
}
21+
22+
type ActionsRequest struct {
23+
Actions []Pointer `json:"actions"`
24+
}

0 commit comments

Comments
 (0)