Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mobilecli
screenshot.png
.DS_Store
.idea
**/node_modules
**/coverage*.out
**/coverage*.html
Expand Down
21 changes: 21 additions & 0 deletions cli/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,25 @@ var ioTextCmd = &cobra.Command{
},
}

var ioShakeCmd = &cobra.Command{
Use: "shake",
Short: "Simulate a shake gesture on a device",
Long: `Sends a shake gesture event to the specified device. Supported on iOS simulators and Android emulators only.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
req := commands.ShakeRequest{
DeviceID: deviceId,
}

response := commands.ShakeCommand(req)
printJson(response)
if response.Status == "error" {
return fmt.Errorf("%s", response.Error)
}
return nil
},
}

var ioSwipeCmd = &cobra.Command{
Use: "swipe [x1,y1,x2,y2]",
Short: "Swipe on a device screen from one point to another",
Expand Down Expand Up @@ -184,6 +203,7 @@ func init() {
ioCmd.AddCommand(ioLongPressCmd)
ioCmd.AddCommand(ioButtonCmd)
ioCmd.AddCommand(ioTextCmd)
ioCmd.AddCommand(ioShakeCmd)
ioCmd.AddCommand(ioSwipeCmd)

// io command flags
Expand All @@ -192,5 +212,6 @@ func init() {
ioLongPressCmd.Flags().IntVar(&longPressDuration, "duration", 500, "duration of the long press in milliseconds")
ioButtonCmd.Flags().StringVar(&deviceId, "device", "", "ID of the device to press button on")
ioTextCmd.Flags().StringVar(&deviceId, "device", "", "ID of the device to send keys to")
ioShakeCmd.Flags().StringVar(&deviceId, "device", "", "ID of the device to shake")
ioSwipeCmd.Flags().StringVar(&deviceId, "device", "", "ID of the device to swipe on")
}
29 changes: 29 additions & 0 deletions commands/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ type GestureRequest struct {
Actions []any `json:"actions"`
}

// ShakeRequest represents the parameters for a shake gesture command
type ShakeRequest struct {
DeviceID string `json:"deviceId"`
}

// SwipeRequest represents the parameters for a swipe command
type SwipeRequest struct {
DeviceID string `json:"deviceId"`
Expand Down Expand Up @@ -205,6 +210,30 @@ func GestureCommand(req GestureRequest) *CommandResponse {
})
}

// ShakeCommand performs a shake gesture on the specified device
func ShakeCommand(req ShakeRequest) *CommandResponse {
targetDevice, err := FindDeviceOrAutoSelect(req.DeviceID)
if err != nil {
return NewErrorResponse(fmt.Errorf("error finding device: %v", err))
}

err = targetDevice.StartAgent(devices.StartAgentConfig{
Hook: GetShutdownHook(),
})
if err != nil {
return NewErrorResponse(fmt.Errorf("failed to start agent on device %s: %v", targetDevice.ID(), err))
}

err = targetDevice.Shake()
if err != nil {
return NewErrorResponse(fmt.Errorf("failed to shake device %s: %v", targetDevice.ID(), err))
}

return NewSuccessResponse(map[string]any{
"message": fmt.Sprintf("Performed shake gesture on device %s", targetDevice.ID()),
})
}

// SwipeCommand performs a swipe operation on the specified device
func SwipeCommand(req SwipeRequest) *CommandResponse {
targetDevice, err := FindDeviceOrAutoSelect(req.DeviceID)
Expand Down
22 changes: 22 additions & 0 deletions devices/android.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,28 @@ func (d *AndroidDevice) Swipe(x1, y1, x2, y2 int) error {
return nil
}

// Shake simulates a shake gesture on Android emulators by injecting accelerometer data.
// Only supported on emulators; real devices will return an error.
func (d *AndroidDevice) Shake() error {
if d.DeviceType() != "emulator" {
return fmt.Errorf("shake gesture is only supported on Android emulators, not real devices")
}

_, err := d.runAdbCommand("emu", "sensor", "set", "acceleration", "100:100:100")
if err != nil {
return fmt.Errorf("failed to set accelerometer data: %w", err)
}

time.Sleep(500 * time.Millisecond)

_, err = d.runAdbCommand("emu", "sensor", "set", "acceleration", "0:0:0")
if err != nil {
return fmt.Errorf("failed to reset accelerometer data: %w", err)
}

return nil
}

// Gesture performs a sequence of touch actions on the Android device
func (d *AndroidDevice) Gesture(actions []wda.TapAction) error {

Expand Down
1 change: 1 addition & 0 deletions devices/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type ControllableDevice interface {
Tap(x, y int) error
LongPress(x, y, duration int) error
Swipe(x1, y1, x2, y2 int) error
Shake() error
Gesture(actions []wda.TapAction) error
StartAgent(config StartAgentConfig) error
SendKeys(text string) error
Expand Down
4 changes: 4 additions & 0 deletions devices/ios.go
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,10 @@ func (d *IOSDevice) PressButton(key string) error {
return d.wdaClient.PressButton(key)
}

func (d *IOSDevice) Shake() error {
return fmt.Errorf("shake gesture is not supported on real iOS devices")
}

func deviceWithRsdProvider(device goios.DeviceEntry, udid string, address string, rsdPort int) (goios.DeviceEntry, error) {
rsdService, err := goios.NewWithAddrPortDevice(address, rsdPort, device)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions devices/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ func (r *RemoteDevice) Swipe(x1, y1, x2, y2 int) error {
return r.fireRPC("device.io.swipe", params{"x1": x1, "y1": y1, "x2": x2, "y2": y2})
}

func (r *RemoteDevice) Shake() error {
return r.fireRPC("device.io.shake", params{})
}

func (r *RemoteDevice) Gesture(actions []wda.TapAction) error {
return r.fireRPC("device.io.gesture", params{"actions": actions})
}
Expand Down
5 changes: 5 additions & 0 deletions devices/simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,11 @@ func (s SimulatorDevice) Swipe(x1, y1, x2, y2 int) error {
return s.wdaClient.Swipe(x1, y1, x2, y2)
}

func (s SimulatorDevice) Shake() error {
_, err := runSimctl("notify_post", s.ID(), "com.apple.UIKit.SimulatorShake")
return err
}

func (s SimulatorDevice) Gesture(actions []wda.TapAction) error {
return s.wdaClient.Gesture(actions)
}
Expand Down
1 change: 1 addition & 0 deletions server/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func GetMethodRegistry() map[string]HandlerFunc {
"device.io.text": handleIoText,
"device.io.button": handleIoButton,
"device.io.swipe": handleIoSwipe,
"device.io.shake": handleIoShake,
"device.io.gesture": handleIoGesture,
"device.url": handleURL,
"device.info": handleDeviceInfo,
Expand Down
22 changes: 22 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,28 @@ func handleIoButton(params json.RawMessage) (any, error) {
return okResponse, nil
}

func handleIoShake(params json.RawMessage) (any, error) {
var shakeParams struct {
DeviceID string `json:"deviceId"`
}
if len(params) > 0 {
if err := json.Unmarshal(params, &shakeParams); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
}

req := commands.ShakeRequest{
DeviceID: shakeParams.DeviceID,
}

response := commands.ShakeCommand(req)
if response.Status == "error" {
return nil, fmt.Errorf("%s", response.Error)
}

return okResponse, nil
}

func handleIoGesture(params json.RawMessage) (any, error) {
if len(params) == 0 {
return nil, fmt.Errorf("'params' is required with fields: deviceId, actions")
Expand Down