mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-13 17:29:56 +08:00
Merge branch 'master' into dependabot/go_modules/golang.org/x/text-0.3.8
This commit is contained in:
@@ -1,5 +1,20 @@
|
||||
# Release History
|
||||
|
||||
## v4.3.3 (2023-04-14)
|
||||
|
||||
**go version**
|
||||
|
||||
- feat: add `sleep_random` to sleep random seconds, with weight for multiple time ranges
|
||||
- feat: input text with adb
|
||||
- feat: add adb `screencap` sub command
|
||||
- feat: add `IsAppInForeground` to check if the given package is in foreground
|
||||
- feat: check if app is in foreground when step failed
|
||||
- fix: adb driver for TapFloat
|
||||
- fix: stop logcat only when enabled
|
||||
- fix: do not fail case when kill logcat error
|
||||
- fix: take screenshot after each step
|
||||
- fix: screencap compatibility for shell v1 and v2
|
||||
|
||||
## v4.3.2 (2022-12-26)
|
||||
|
||||
**go version**
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "滑动 Feed 35 次,随机间隔 0-20s",
|
||||
"name": "滑动 Feed 3 次,随机间隔 0-5s",
|
||||
"android": {
|
||||
"actions": [
|
||||
{
|
||||
@@ -54,15 +54,15 @@
|
||||
"method": "sleep_random",
|
||||
"params": [
|
||||
0,
|
||||
20
|
||||
5
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"loops": 35
|
||||
"loops": 3
|
||||
},
|
||||
{
|
||||
"name": "滑动 Feed 15 次,随机间隔 15-50s",
|
||||
"name": "滑动 Feed 1 次,随机间隔 5-10s",
|
||||
"android": {
|
||||
"actions": [
|
||||
{
|
||||
@@ -72,13 +72,36 @@
|
||||
{
|
||||
"method": "sleep_random",
|
||||
"params": [
|
||||
15,
|
||||
50
|
||||
5,
|
||||
10
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"loops": 15
|
||||
"loops": 1
|
||||
},
|
||||
{
|
||||
"name": "滑动 Feed 10 次,70% 随机间隔 0-5s,30% 随机间隔 5-10s",
|
||||
"android": {
|
||||
"actions": [
|
||||
{
|
||||
"method": "swipe",
|
||||
"params": "up"
|
||||
},
|
||||
{
|
||||
"method": "sleep_random",
|
||||
"params": [
|
||||
0,
|
||||
5,
|
||||
0.7,
|
||||
5,
|
||||
10,
|
||||
0.3
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"loops": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -25,16 +25,21 @@ func TestAndroidDouyinFeedTest(t *testing.T) {
|
||||
hrp.NewStep("处理青少年弹窗").
|
||||
Android().
|
||||
TapByOCR("我知道了", uixt.WithIgnoreNotFoundError(true)),
|
||||
hrp.NewStep("滑动 Feed 35 次,随机间隔 0-20s").
|
||||
Loop(35).
|
||||
hrp.NewStep("滑动 Feed 3 次,随机间隔 0-5s").
|
||||
Loop(3).
|
||||
Android().
|
||||
SwipeUp().
|
||||
SleepRandom(0, 20),
|
||||
hrp.NewStep("滑动 Feed 15 次,随机间隔 15-50s").
|
||||
Loop(15).
|
||||
SleepRandom(0, 5),
|
||||
hrp.NewStep("滑动 Feed 1 次,随机间隔 5-10s").
|
||||
Loop(1).
|
||||
Android().
|
||||
SwipeUp().
|
||||
SleepRandom(15, 50),
|
||||
SleepRandom(5, 10),
|
||||
hrp.NewStep("滑动 Feed 10 次,70% 随机间隔 0-5s,30% 随机间隔 5-10s").
|
||||
Loop(10).
|
||||
Android().
|
||||
SwipeUp().
|
||||
SleepRandom(0, 5, 0.7, 5, 10, 0.3),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
6
go.mod
6
go.mod
@@ -28,7 +28,7 @@ require (
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
gocv.io/x/gocv v0.31.0
|
||||
golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
||||
google.golang.org/grpc v1.49.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
@@ -75,8 +75,8 @@ require (
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51 // indirect
|
||||
|
||||
12
go.sum
12
go.sum
@@ -513,8 +513,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1 h1:TWZxd/th7FbRSMret2MVQdlI8uT49QEtwZdvJrxjEHU=
|
||||
golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -626,8 +626,8 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -639,8 +639,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -5,10 +5,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||
)
|
||||
|
||||
@@ -21,22 +19,10 @@ var listAndroidDevicesCmd = &cobra.Command{
|
||||
Use: "devices",
|
||||
Short: "List all Android devices",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
devices, err := uixt.DeviceList()
|
||||
deviceList, err := uixt.GetAndroidDevices(serial)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "list android devices failed")
|
||||
}
|
||||
|
||||
var deviceList []gadb.Device
|
||||
// filter by serial
|
||||
for _, d := range devices {
|
||||
if serial != "" && serial != d.Serial() {
|
||||
continue
|
||||
}
|
||||
deviceList = append(deviceList, d)
|
||||
}
|
||||
if serial != "" && len(deviceList) == 0 {
|
||||
fmt.Printf("no android device found for serial: %s\n", serial)
|
||||
os.Exit(1)
|
||||
fmt.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
for _, d := range deviceList {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
package adb
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||
)
|
||||
|
||||
var androidRootCmd = &cobra.Command{
|
||||
Use: "adb",
|
||||
@@ -8,6 +15,17 @@ var androidRootCmd = &cobra.Command{
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
|
||||
func getDevice(serial string) (*gadb.Device, error) {
|
||||
devices, err := uixt.GetAndroidDevices(serial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(devices) > 1 {
|
||||
return nil, fmt.Errorf("found multiple attached devices, please specify android serial")
|
||||
}
|
||||
return devices[0], nil
|
||||
}
|
||||
|
||||
func Init(rootCmd *cobra.Command) {
|
||||
rootCmd.AddCommand(androidRootCmd)
|
||||
}
|
||||
|
||||
37
hrp/cmd/adb/screencap.go
Normal file
37
hrp/cmd/adb/screencap.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package adb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var screencapAndroidDevicesCmd = &cobra.Command{
|
||||
Use: "screencap",
|
||||
Short: "Start android screen capture",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
device, err := getDevice(serial)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := device.ScreenCap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filepath := fmt.Sprintf("screencap_%d.png", time.Now().Unix())
|
||||
if err = ioutil.WriteFile(filepath, res, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("screencap saved to", filepath)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
screencapAndroidDevicesCmd.Flags().StringVarP(&serial, "serial", "s", "", "filter by device's serial")
|
||||
androidRootCmd.AddCommand(screencapAndroidDevicesCmd)
|
||||
}
|
||||
@@ -70,18 +70,10 @@ var listDevicesCmd = &cobra.Command{
|
||||
Short: "List all iOS devices",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
devices, err := uixt.IOSDevices(udid)
|
||||
devices, err := uixt.GetIOSDevices(udid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
if udid != "" {
|
||||
fmt.Printf("no ios device found for udid: %s\n", udid)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
fmt.Println("no ios device found")
|
||||
os.Exit(0)
|
||||
}
|
||||
fmt.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
for _, d := range devices {
|
||||
|
||||
@@ -2,7 +2,6 @@ package ios
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -16,16 +15,12 @@ var iosRootCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func getDevice(udid string) (gidevice.Device, error) {
|
||||
devices, err := uixt.IOSDevices(udid)
|
||||
devices, err := uixt.GetIOSDevices(udid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
fmt.Println("no ios device found")
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(devices) > 1 {
|
||||
return nil, fmt.Errorf("multiple devices found, please specify udid")
|
||||
return nil, fmt.Errorf("found multiple attached devices, please specify ios udid")
|
||||
}
|
||||
return devices[0], nil
|
||||
}
|
||||
|
||||
@@ -67,8 +67,9 @@ var (
|
||||
|
||||
// UI automation related: [70, 80)
|
||||
var (
|
||||
MobileUIDriverError = errors.New("mobile UI driver error") // 70
|
||||
MobileUIValidationError = errors.New("mobile UI validation error") // 75
|
||||
MobileUIDriverError = errors.New("mobile UI driver error") // 70
|
||||
MobileUIValidationError = errors.New("mobile UI validation error") // 75
|
||||
MobileUIAppNotInForegroundError = errors.New("mobile UI app not in foreground error") // 76
|
||||
)
|
||||
|
||||
// OCR related: [80, 90)
|
||||
|
||||
@@ -1 +1 @@
|
||||
v4.3.2
|
||||
v4.3.3.2304142356
|
||||
@@ -1,5 +1,11 @@
|
||||
# gadb
|
||||
|
||||
This module is initially forked from [electricbubble/gadb@v0.0.7].
|
||||
This module is initially forked from [electricbubble/gadb@v0.0.7] and optimized by [@appl3s].
|
||||
|
||||
- feat: add reverse forward command
|
||||
- feat: add `RunShellCommandV2` which supports running nohup
|
||||
- feat: add `InstallAPK` with feature judgment
|
||||
- feat: add `Uninstall` for specified package name
|
||||
|
||||
[electricbubble/gadb@v0.0.7]: https://github.com/electricbubble/gadb/tree/v0.0.7
|
||||
[@appl3s]: https://github.com/appl3s
|
||||
|
||||
@@ -38,6 +38,16 @@ func NewClientWith(host string, port ...int) (adbClient Client, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func NewClientWithoutTransport(host string, port ...int) (adbClient Client, err error) {
|
||||
if len(port) == 0 {
|
||||
port = []int{AdbServerPort}
|
||||
}
|
||||
adbClient.host = host
|
||||
adbClient.port = port[0]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) ServerVersion() (version int, err error) {
|
||||
var resp string
|
||||
if resp, err = c.executeCommand("host:version"); err != nil {
|
||||
@@ -73,14 +83,14 @@ func (c Client) DeviceSerialList() (serials []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) DeviceList() (devices []Device, err error) {
|
||||
func (c Client) DeviceList() (devices []*Device, err error) {
|
||||
var resp string
|
||||
if resp, err = c.executeCommand("host:devices-l"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lines := strings.Split(resp, "\n")
|
||||
devices = make([]Device, 0, len(lines))
|
||||
devices = make([]*Device, 0, len(lines))
|
||||
|
||||
for i := range lines {
|
||||
line := strings.TrimSpace(lines[i])
|
||||
@@ -101,7 +111,7 @@ func (c Client) DeviceList() (devices []Device, err error) {
|
||||
key, val := split[0], split[1]
|
||||
mapAttrs[key] = val
|
||||
}
|
||||
devices = append(devices, Device{adbClient: c, serial: fields[0], attrs: mapAttrs})
|
||||
devices = append(devices, &Device{adbClient: c, serial: fields[0], attrs: mapAttrs})
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -127,3 +128,22 @@ func TestClient_KillServer(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScreenCap(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dl, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
d := dl[0]
|
||||
res, err := d.ScreenCap()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(len(res))
|
||||
ioutil.WriteFile("/tmp/1.png", res, 0o644)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -48,9 +50,10 @@ func deviceStateConv(k string) (deviceState DeviceState) {
|
||||
}
|
||||
|
||||
type DeviceForward struct {
|
||||
Serial string
|
||||
Local string
|
||||
Remote string
|
||||
Serial string
|
||||
Local string
|
||||
Remote string
|
||||
Reverse bool
|
||||
// LocalProtocol string
|
||||
// RemoteProtocol string
|
||||
}
|
||||
@@ -59,51 +62,92 @@ type Device struct {
|
||||
adbClient Client
|
||||
serial string
|
||||
attrs map[string]string
|
||||
feat Features
|
||||
}
|
||||
|
||||
func (d Device) Product() string {
|
||||
func (d *Device) HasFeature(name Feature) bool {
|
||||
feats, err := d.GetFeatures()
|
||||
if err != nil || len(feats) == 0 {
|
||||
return false
|
||||
}
|
||||
return feats.HasFeature(name)
|
||||
}
|
||||
|
||||
func (d *Device) GetFeatures() (features Features, err error) {
|
||||
if len(d.feat) > 0 {
|
||||
return d.feat, nil
|
||||
}
|
||||
return d.features()
|
||||
}
|
||||
|
||||
func (d *Device) features() (features Features, err error) {
|
||||
res, err := d.executeCommand("host:features")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(res) > 4 {
|
||||
// stip hash
|
||||
res = res[4:]
|
||||
}
|
||||
fs := strings.Split(string(res), ",")
|
||||
features = make(Features, len(fs))
|
||||
for _, f := range fs {
|
||||
features[Feature(f)] = struct{}{}
|
||||
}
|
||||
d.feat = features
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func (d *Device) Product() string {
|
||||
return d.attrs["product"]
|
||||
}
|
||||
|
||||
func (d Device) Model() string {
|
||||
func (d *Device) Model() string {
|
||||
return d.attrs["model"]
|
||||
}
|
||||
|
||||
func (d Device) Usb() string {
|
||||
func (d *Device) Usb() string {
|
||||
return d.attrs["usb"]
|
||||
}
|
||||
|
||||
func (d Device) transportId() string {
|
||||
func (d *Device) transportId() string {
|
||||
return d.attrs["transport_id"]
|
||||
}
|
||||
|
||||
func (d Device) DeviceInfo() map[string]string {
|
||||
func (d *Device) DeviceInfo() map[string]string {
|
||||
return d.attrs
|
||||
}
|
||||
|
||||
func (d Device) Serial() string {
|
||||
func (d *Device) Serial() string {
|
||||
// resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-serialno", d.serial))
|
||||
return d.serial
|
||||
}
|
||||
|
||||
func (d Device) IsUsb() bool {
|
||||
func (d *Device) IsUsb() bool {
|
||||
return d.Usb() != ""
|
||||
}
|
||||
|
||||
func (d Device) State() (DeviceState, error) {
|
||||
func (d *Device) State() (DeviceState, error) {
|
||||
resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-state", d.serial))
|
||||
return deviceStateConv(resp), err
|
||||
}
|
||||
|
||||
func (d Device) DevicePath() (string, error) {
|
||||
func (d *Device) DevicePath() (string, error) {
|
||||
resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-devpath", d.serial))
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (d Device) Forward(localPort, remotePort int, noRebind ...bool) (err error) {
|
||||
func (d *Device) Forward(localPort int, remoteInterface interface{}, noRebind ...bool) (err error) {
|
||||
command := ""
|
||||
var remote string
|
||||
local := fmt.Sprintf("tcp:%d", localPort)
|
||||
remote := fmt.Sprintf("tcp:%d", remotePort)
|
||||
switch r := remoteInterface.(type) {
|
||||
// for unix sockets
|
||||
case string:
|
||||
remote = r
|
||||
case int:
|
||||
remote = fmt.Sprintf("tcp:%d", r)
|
||||
}
|
||||
|
||||
if len(noRebind) != 0 && noRebind[0] {
|
||||
command = fmt.Sprintf("host-serial:%s:forward:norebind:%s;%s", d.serial, local, remote)
|
||||
@@ -115,7 +159,7 @@ func (d Device) Forward(localPort, remotePort int, noRebind ...bool) (err error)
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) ForwardList() (deviceForwardList []DeviceForward, err error) {
|
||||
func (d *Device) ForwardList() (deviceForwardList []DeviceForward, err error) {
|
||||
var forwardList []DeviceForward
|
||||
if forwardList, err = d.adbClient.ForwardList(); err != nil {
|
||||
return nil, err
|
||||
@@ -131,18 +175,80 @@ func (d Device) ForwardList() (deviceForwardList []DeviceForward, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) ForwardKill(localPort int) (err error) {
|
||||
func (d *Device) ForwardKill(localPort int) (err error) {
|
||||
local := fmt.Sprintf("tcp:%d", localPort)
|
||||
_, err = d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:killforward:%s", d.serial, local), true)
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) RunShellCommand(cmd string, args ...string) (string, error) {
|
||||
func (d *Device) ReverseForward(localPort int, remoteInterface interface{}, noRebind ...bool) (err error) {
|
||||
var command string
|
||||
var remote string
|
||||
local := fmt.Sprintf("tcp:%d", localPort)
|
||||
switch r := remoteInterface.(type) {
|
||||
// for unix sockets
|
||||
case string:
|
||||
remote = r
|
||||
case int:
|
||||
remote = fmt.Sprintf("tcp:%d", r)
|
||||
}
|
||||
|
||||
if len(noRebind) != 0 && noRebind[0] {
|
||||
command = fmt.Sprintf("reverse:forward:norebind:%s;%s", remote, local)
|
||||
} else {
|
||||
command = fmt.Sprintf("reverse:forward:%s;%s", remote, local)
|
||||
}
|
||||
_, err = d.executeCommand(command, true)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) ReverseForwardList() (deviceForwardList []DeviceForward, err error) {
|
||||
res, err := d.executeCommand("reverse:list-forward")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resStr := string(res)
|
||||
lines := strings.Split(resStr, "\n")
|
||||
for _, line := range lines {
|
||||
groups := strings.Split(line, " ")
|
||||
if len(groups) == 3 {
|
||||
deviceForwardList = append(deviceForwardList, DeviceForward{
|
||||
Reverse: true,
|
||||
Serial: d.serial,
|
||||
Remote: groups[1],
|
||||
Local: groups[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) ReverseForwardKill(remoteInterface interface{}) error {
|
||||
remote := ""
|
||||
switch r := remoteInterface.(type) {
|
||||
case string:
|
||||
remote = r
|
||||
case int:
|
||||
remote = fmt.Sprintf("tcp:%d", r)
|
||||
}
|
||||
_, err := d.executeCommand(fmt.Sprintf("reverse:killforward:%s", remote), true)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Device) ReverseForwardKillAll() error {
|
||||
_, err := d.executeCommand("reverse:killforward-all")
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Device) RunShellCommand(cmd string, args ...string) (string, error) {
|
||||
raw, err := d.RunShellCommandWithBytes(cmd, args...)
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
func (d Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, error) {
|
||||
func (d *Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, error) {
|
||||
if d.HasFeature(FeatShellV2) {
|
||||
return d.RunShellCommandV2WithBytes(cmd, args...)
|
||||
}
|
||||
if len(args) > 0 {
|
||||
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
|
||||
}
|
||||
@@ -156,7 +262,86 @@ func (d Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, er
|
||||
return raw, err
|
||||
}
|
||||
|
||||
func (d Device) EnableAdbOverTCP(port ...int) (err error) {
|
||||
func (d *Device) RunShellCommandV2(cmd string, args ...string) (string, error) {
|
||||
raw, err := d.RunShellCommandV2WithBytes(cmd, args...)
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
// RunShellCommandV2WithBytes shell v2, 支持后台运行而不会阻断
|
||||
func (d *Device) RunShellCommandV2WithBytes(cmd string, args ...string) ([]byte, error) {
|
||||
if len(args) > 0 {
|
||||
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
|
||||
}
|
||||
if strings.TrimSpace(cmd) == "" {
|
||||
return nil, errors.New("adb shell: command cannot be empty")
|
||||
}
|
||||
log.Debug().Str("cmd",
|
||||
fmt.Sprintf("adb -s %s shell %s", d.serial, cmd)).
|
||||
Msg("run adb command in v2")
|
||||
raw, err := d.executeCommand(fmt.Sprintf("shell,v2,raw:%s", cmd))
|
||||
if err != nil {
|
||||
return raw, err
|
||||
}
|
||||
return d.parseV2CommandWithBytes(raw)
|
||||
}
|
||||
|
||||
func (d *Device) parseV2CommandWithBytes(input []byte) (res []byte, err error) {
|
||||
if len(input) == 0 {
|
||||
return input, nil
|
||||
}
|
||||
reader := bytes.NewReader(input)
|
||||
sizeBuf := make([]byte, 4)
|
||||
var (
|
||||
resBuf []byte
|
||||
exitCode int
|
||||
)
|
||||
loop:
|
||||
for {
|
||||
msgCode, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
switch msgCode {
|
||||
case 0x01, 0x02: // STDOUT, STDERR
|
||||
_, err = io.ReadFull(reader, sizeBuf)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
size := binary.LittleEndian.Uint32(sizeBuf)
|
||||
if cap(resBuf) < int(size) {
|
||||
resBuf = make([]byte, int(size))
|
||||
}
|
||||
_, err = io.ReadFull(reader, resBuf[:size])
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
res = append(res, resBuf[:size]...)
|
||||
case 0x03: // EXIT
|
||||
_, err = io.ReadFull(reader, sizeBuf)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
size := binary.LittleEndian.Uint32(sizeBuf)
|
||||
if cap(resBuf) < int(size) {
|
||||
resBuf = make([]byte, int(size))
|
||||
}
|
||||
ec, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
exitCode = int(ec)
|
||||
break loop
|
||||
default:
|
||||
return input, nil
|
||||
}
|
||||
}
|
||||
if exitCode != 0 {
|
||||
return nil, errors.New(string(res))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Device) EnableAdbOverTCP(port ...int) (err error) {
|
||||
if len(port) == 0 {
|
||||
port = []int{AdbDaemonPort}
|
||||
}
|
||||
@@ -168,7 +353,7 @@ func (d Device) EnableAdbOverTCP(port ...int) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) createDeviceTransport() (tp transport, err error) {
|
||||
func (d *Device) createDeviceTransport() (tp transport, err error) {
|
||||
if tp, err = newTransport(fmt.Sprintf("%s:%d", d.adbClient.host, d.adbClient.port)); err != nil {
|
||||
return transport{}, err
|
||||
}
|
||||
@@ -180,7 +365,7 @@ func (d Device) createDeviceTransport() (tp transport, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw []byte, err error) {
|
||||
func (d *Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw []byte, err error) {
|
||||
if len(onlyVerifyResponse) == 0 {
|
||||
onlyVerifyResponse = []bool{false}
|
||||
}
|
||||
@@ -207,7 +392,7 @@ func (d Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err error) {
|
||||
func (d *Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return nil, err
|
||||
@@ -237,7 +422,7 @@ func (d Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err erro
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) PushFile(local *os.File, remotePath string, modification ...time.Time) (err error) {
|
||||
func (d *Device) PushFile(local *os.File, remotePath string, modification ...time.Time) (err error) {
|
||||
if len(modification) == 0 {
|
||||
var stat os.FileInfo
|
||||
if stat, err = local.Stat(); err != nil {
|
||||
@@ -249,7 +434,7 @@ func (d Device) PushFile(local *os.File, remotePath string, modification ...time
|
||||
return d.Push(local, remotePath, modification[0], DefaultFileMode)
|
||||
}
|
||||
|
||||
func (d Device) Push(source io.Reader, remotePath string, modification time.Time, mode ...os.FileMode) (err error) {
|
||||
func (d *Device) Push(source io.Reader, remotePath string, modification time.Time, mode ...os.FileMode) (err error) {
|
||||
if len(mode) == 0 {
|
||||
mode = []os.FileMode{DefaultFileMode}
|
||||
}
|
||||
@@ -285,7 +470,7 @@ func (d Device) Push(source io.Reader, remotePath string, modification time.Time
|
||||
return
|
||||
}
|
||||
|
||||
func (d Device) Pull(remotePath string, dest io.Writer) (err error) {
|
||||
func (d *Device) Pull(remotePath string, dest io.Writer) (err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return err
|
||||
@@ -305,3 +490,102 @@ func (d Device) Pull(remotePath string, dest io.Writer) (err error) {
|
||||
err = sync.WriteStream(dest)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) {
|
||||
var (
|
||||
tp transport
|
||||
filesize int64
|
||||
)
|
||||
filesize, err = apk.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
if err = tp.Send(fmt.Sprintf("abb_exec:package\x00install\x00-t\x00-S\x00%d", filesize)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = tp.VerifyResponse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = apk.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.Copy(tp.Conn(), apk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw, err = tp.ReadBytesAll()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) InstallAPK(apk io.ReadSeeker) (string, error) {
|
||||
haserr := func(ret string) bool {
|
||||
return strings.Contains(ret, "Failure")
|
||||
}
|
||||
if d.HasFeature(FeatAbbExec) {
|
||||
raw, err := d.installViaABBExec(apk)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error installing: %v", err)
|
||||
}
|
||||
if haserr(string(raw)) {
|
||||
return "", errors.New(string(raw))
|
||||
}
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
remote := fmt.Sprintf("/data/local/tmp/gadb_remote_%d.apk", time.Now().Unix())
|
||||
err := d.Push(apk, remote, time.Now())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error pushing: %v", err)
|
||||
}
|
||||
|
||||
res, err := d.RunShellCommand("pm", "install", "-f", remote)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error installing: %v", err)
|
||||
}
|
||||
if haserr(res) {
|
||||
return "", errors.New(res)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Device) Uninstall(packageName string, keepData ...bool) (string, error) {
|
||||
if len(keepData) == 0 {
|
||||
keepData = []bool{false}
|
||||
}
|
||||
packageName = strings.ReplaceAll(packageName, " ", "")
|
||||
if len(packageName) == 0 {
|
||||
return "", fmt.Errorf("invalid package name")
|
||||
}
|
||||
args := []string{"uninstall"}
|
||||
if keepData[0] {
|
||||
args = append(args, "-k")
|
||||
}
|
||||
args = append(args, packageName)
|
||||
return d.RunShellCommandV2("pm", args...)
|
||||
}
|
||||
|
||||
func (d *Device) ScreenCap() ([]byte, error) {
|
||||
if d.HasFeature(FeatShellV2) {
|
||||
return d.RunShellCommandV2WithBytes("screencap", "-p")
|
||||
}
|
||||
|
||||
// for shell v1, screenshot buffer maybe truncated
|
||||
// thus we firstly save it to local file and then pull it
|
||||
tempPath := fmt.Sprintf("/data/local/tmp/screenshot_%d.png",
|
||||
time.Now().Unix())
|
||||
_, err := d.RunShellCommandWithBytes("screencap", "-p", tempPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
err = d.Pull(tempPath, buffer)
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -145,6 +146,42 @@ func TestDevice_Forward(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_ReverseForward(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
localPort := 5005
|
||||
err = devices[0].ReverseForward(localPort, "localabstract:scrcpy")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = devices[0].ReverseForward(localPort, "localabstract:scrcpy1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = devices[0].ReverseForwardList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = devices[0].ReverseForwardKill("localabstract:scrcpy1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = devices[0].ReverseForwardKillAll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_ForwardList(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
@@ -314,3 +351,94 @@ func TestDevice_Pull(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_RunShellCommandBackgroundWithBytes(t *testing.T) {
|
||||
type fields struct {
|
||||
adbClient Client
|
||||
serial string
|
||||
attrs map[string]string
|
||||
}
|
||||
type args struct {
|
||||
cmd string
|
||||
args []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "runShellCommandBackground",
|
||||
fields: fields{
|
||||
adbClient: func() Client {
|
||||
c, _ := NewClient()
|
||||
return c
|
||||
}(),
|
||||
serial: "63c1ee94",
|
||||
},
|
||||
args: args{
|
||||
cmd: "nohup sleep 10 2>/dev/null 1>/dev/null &",
|
||||
// cmd: "sleep 10",
|
||||
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := Device{
|
||||
adbClient: tt.fields.adbClient,
|
||||
serial: tt.fields.serial,
|
||||
attrs: tt.fields.attrs,
|
||||
}
|
||||
got, err := d.RunShellCommandV2WithBytes(tt.args.cmd, tt.args.args...)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Device.RunShellCommandBackgroundWithBytes() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Device.RunShellCommandBackgroundWithBytes() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevice_InstallAPK(t *testing.T) {
|
||||
apk, _ := os.Open("test.apk")
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dev := devices[len(devices)-1]
|
||||
dev = devices[0]
|
||||
|
||||
res, err := dev.InstallAPK(apk)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(res)
|
||||
}
|
||||
|
||||
func TestDevice_HasFeature(t *testing.T) {
|
||||
adbClient, err := NewClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
devices, err := adbClient.DeviceList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dev := devices[len(devices)-1]
|
||||
dev = devices[0]
|
||||
|
||||
t.Log(dev.GetFeatures())
|
||||
}
|
||||
|
||||
26
hrp/pkg/gadb/features.go
Normal file
26
hrp/pkg/gadb/features.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package gadb
|
||||
|
||||
type (
|
||||
Feature string
|
||||
Features map[Feature]struct{}
|
||||
)
|
||||
|
||||
var (
|
||||
FeatSendrecvV2Brotli = Feature("sendrecv_v2_brotli")
|
||||
FeatRemountShell = Feature("remount_shell")
|
||||
FeatSendrecvV2 = Feature("sendrecv_v2")
|
||||
FeatAbbExec = Feature("abb_exec")
|
||||
FeatFixedPushMkdir = Feature("fixed_push_mkdir")
|
||||
FeatFixedPushSymlinkTimestamp = Feature("fixed_push_symlink_timestamp")
|
||||
FeatAbb = Feature("abb")
|
||||
FeatShellV2 = Feature("shell_v2")
|
||||
FeatCmd = Feature("cmd")
|
||||
FeatLsV2 = Feature("ls_v2")
|
||||
FeatApex = Feature("apex")
|
||||
FeatStatV2 = Feature("stat_v2")
|
||||
)
|
||||
|
||||
func (fs Features) HasFeature(name Feature) bool {
|
||||
_, has := fs[name]
|
||||
return has
|
||||
}
|
||||
@@ -250,5 +250,6 @@ func (sync syncTransport) Close() (err error) {
|
||||
if sync.sock == nil {
|
||||
return nil
|
||||
}
|
||||
_ = DisableTimeWait(sync.sock.(*net.TCPConn))
|
||||
return sync.sock.Close()
|
||||
}
|
||||
|
||||
@@ -37,6 +37,14 @@ func (t transport) Send(command string) (err error) {
|
||||
return _send(t.sock, []byte(msg))
|
||||
}
|
||||
|
||||
func (t transport) SendBytes(b []byte) (err error) {
|
||||
return _send(t.sock, b)
|
||||
}
|
||||
|
||||
func (t transport) Conn() net.Conn {
|
||||
return t.sock
|
||||
}
|
||||
|
||||
func (t transport) VerifyResponse() (err error) {
|
||||
var status string
|
||||
if status, err = t.ReadStringN(4); err != nil {
|
||||
@@ -103,6 +111,7 @@ func (t transport) Close() (err error) {
|
||||
if t.sock == nil {
|
||||
return nil
|
||||
}
|
||||
_ = DisableTimeWait(t.sock.(*net.TCPConn))
|
||||
return t.sock.Close()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ func Test_transport_VerifyResponse(t *testing.T) {
|
||||
}
|
||||
defer transport.Close()
|
||||
|
||||
// err = transport.Send("host:123version")
|
||||
err = transport.Send("host:version")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
9
hrp/pkg/gadb/utils.go
Normal file
9
hrp/pkg/gadb/utils.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func DisableTimeWait(conn *net.TCPConn) error {
|
||||
return conn.SetLinger(0)
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
type adbDriver struct {
|
||||
Driver
|
||||
|
||||
adbClient gadb.Device
|
||||
adbClient *gadb.Device
|
||||
logcat *AdbLogcat
|
||||
}
|
||||
|
||||
@@ -153,11 +153,11 @@ func (ad *adbDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta) (err error
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) AppLaunch(bundleId string) (err error) {
|
||||
func (ad *adbDriver) AppLaunch(packageName string) (err error) {
|
||||
// 不指定 Activity 名称启动(启动主 Activity)
|
||||
// adb shell monkey -p <packagename> -c android.intent.category.LAUNCHER 1
|
||||
sOutput, err := ad.adbClient.RunShellCommand(
|
||||
"monkey", "-p", bundleId, "-c", "android.intent.category.LAUNCHER", "1",
|
||||
"monkey", "-p", packageName, "-c", "android.intent.category.LAUNCHER", "1",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -165,14 +165,22 @@ func (ad *adbDriver) AppLaunch(bundleId string) (err error) {
|
||||
if strings.Contains(sOutput, "monkey aborted") {
|
||||
return fmt.Errorf("app launch: %s", strings.TrimSpace(sOutput))
|
||||
}
|
||||
ad.lastLaunchedPackageName = packageName
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) AppTerminate(bundleId string) (successful bool, err error) {
|
||||
func (ad *adbDriver) AppTerminate(packageName string) (successful bool, err error) {
|
||||
// 强制停止应用,停止 <packagename> 相关的进程
|
||||
// adb shell am force-stop <packagename>
|
||||
_, err = ad.adbClient.RunShellCommand("am", "force-stop", bundleId)
|
||||
return err == nil, err
|
||||
_, err = ad.adbClient.RunShellCommand("am", "force-stop", packageName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if ad.lastLaunchedPackageName == packageName {
|
||||
ad.lastLaunchedPackageName = "" // reset last launched package name
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Tap(x, y int, options ...DataOption) error {
|
||||
@@ -280,9 +288,7 @@ func (ad *adbDriver) SetRotation(rotation Rotation) (err error) {
|
||||
|
||||
func (ad *adbDriver) Screenshot() (raw *bytes.Buffer, err error) {
|
||||
// adb shell screencap -p
|
||||
resp, err := ad.adbClient.RunShellCommandWithBytes(
|
||||
"screencap", "-p",
|
||||
)
|
||||
resp, err := ad.adbClient.ScreenCap()
|
||||
if err == nil {
|
||||
return bytes.NewBuffer(resp), nil
|
||||
}
|
||||
@@ -323,7 +329,7 @@ func (ad *adbDriver) StartCaptureLog(identifier ...string) (err error) {
|
||||
log.Info().Msg("start adb log recording")
|
||||
|
||||
// clear logcat
|
||||
if _, err = ad.adbClient.RunShellCommand("logcat", "--clear"); err != nil {
|
||||
if _, err = ad.adbClient.RunShellCommand("logcat", "-c"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -349,3 +355,34 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) {
|
||||
content := ad.logcat.logBuffer.String()
|
||||
return ConvertPoints(content), nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) GetLastLaunchedApp() (packageName string) {
|
||||
return ad.lastLaunchedPackageName
|
||||
}
|
||||
|
||||
func (ad *adbDriver) IsAppInForeground(packageName string) (bool, error) {
|
||||
if packageName == "" {
|
||||
return false, errors.New("package name is not given")
|
||||
}
|
||||
|
||||
// adb shell dumpsys activity activities | grep mResumedActivity
|
||||
output, err := ad.adbClient.RunShellCommand("dumpsys", "activity", "activities")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(output), "\n")
|
||||
isInForeground := false
|
||||
|
||||
for _, line := range lines {
|
||||
trimmedLine := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmedLine, "mResumedActivity:") {
|
||||
if strings.Contains(trimmedLine, packageName) {
|
||||
isInForeground = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return isInForeground, nil
|
||||
}
|
||||
|
||||
@@ -81,15 +81,6 @@ func GetAndroidDeviceOptions(dev *AndroidDevice) (deviceOptions []AndroidDeviceO
|
||||
// uiautomator2 server must be started before
|
||||
// adb shell am instrument -w io.appium.uiautomator2.server.test/androidx.test.runner.AndroidJUnitRunner
|
||||
func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, err error) {
|
||||
deviceList, err := DeviceList()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
|
||||
fmt.Sprintf("get attached devices failed: %v", err))
|
||||
} else if len(deviceList) == 0 {
|
||||
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
|
||||
"not attached device found")
|
||||
}
|
||||
|
||||
device = &AndroidDevice{
|
||||
UIA2IP: UIA2ServerHost,
|
||||
UIA2Port: UIA2ServerPort,
|
||||
@@ -98,34 +89,56 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er
|
||||
option(device)
|
||||
}
|
||||
|
||||
serialNumber := device.SerialNumber
|
||||
for _, dev := range deviceList {
|
||||
// find device by serial number if specified
|
||||
if serialNumber != "" && dev.Serial() != serialNumber {
|
||||
continue
|
||||
}
|
||||
|
||||
device.SerialNumber = dev.Serial()
|
||||
device.d = dev
|
||||
device.logcat = NewAdbLogcat(device.SerialNumber)
|
||||
return device, nil
|
||||
deviceList, err := GetAndroidDevices(device.SerialNumber)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(code.AndroidDeviceConnectionError, err.Error())
|
||||
}
|
||||
|
||||
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
|
||||
fmt.Sprintf("device %s not found", device.SerialNumber))
|
||||
dev := deviceList[0]
|
||||
device.SerialNumber = dev.Serial()
|
||||
device.d = dev
|
||||
device.logcat = NewAdbLogcat(device.SerialNumber)
|
||||
|
||||
log.Info().Str("serial", device.SerialNumber).Msg("select android device")
|
||||
return device, nil
|
||||
}
|
||||
|
||||
func DeviceList() (devices []gadb.Device, err error) {
|
||||
func GetAndroidDevices(serial ...string) (devices []*gadb.Device, err error) {
|
||||
var adbClient gadb.Client
|
||||
if adbClient, err = gadb.NewClientWith(AdbServerHost, AdbServerPort); err != nil {
|
||||
return nil, errors.Wrap(code.AndroidDeviceConnectionError, err.Error())
|
||||
}
|
||||
|
||||
return adbClient.DeviceList()
|
||||
if devices, err = adbClient.DeviceList(); err != nil {
|
||||
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
|
||||
fmt.Sprintf("list android devices failed: %v", err))
|
||||
}
|
||||
|
||||
var deviceList []*gadb.Device
|
||||
// filter by serial
|
||||
for _, d := range devices {
|
||||
for _, s := range serial {
|
||||
if s != "" && s != d.Serial() {
|
||||
continue
|
||||
}
|
||||
deviceList = append(deviceList, d)
|
||||
}
|
||||
}
|
||||
|
||||
if len(deviceList) == 0 {
|
||||
var err error
|
||||
if serial == nil || (len(serial) == 1 && serial[0] == "") {
|
||||
err = fmt.Errorf("no android device found")
|
||||
} else {
|
||||
err = fmt.Errorf("no android device found for serial %v", serial)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return deviceList, nil
|
||||
}
|
||||
|
||||
type AndroidDevice struct {
|
||||
d gadb.Device
|
||||
d *gadb.Device
|
||||
logcat *AdbLogcat
|
||||
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
|
||||
UIA2 bool `json:"uia2,omitempty" yaml:"uia2,omitempty"` // use uiautomator2
|
||||
@@ -315,7 +328,7 @@ func (l *AdbLogcat) CatchLogcat() (err error) {
|
||||
}
|
||||
|
||||
// clear logcat
|
||||
if err = myexec.RunCommand("adb", "-s", l.serial, "logcat", "--clear"); err != nil {
|
||||
if err = myexec.RunCommand("adb", "-s", l.serial, "shell", "logcat", "-c"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ func Test_getFreePort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeviceList(t *testing.T) {
|
||||
devices, err := DeviceList()
|
||||
devices, err := GetAndroidDevices()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -353,6 +353,34 @@ func TestDriver_AppLaunch(t *testing.T) {
|
||||
t.Log(ioutil.WriteFile("s1.png", raw.Bytes(), 0o600))
|
||||
}
|
||||
|
||||
func TestDriver_IsAppInForeground(t *testing.T) {
|
||||
device, _ := NewAndroidDevice()
|
||||
driver, err := device.NewDriver(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = driver.Driver.AppLaunch("com.android.settings")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
yes, err := driver.Driver.IsAppInForeground(driver.Driver.GetLastLaunchedApp())
|
||||
if err != nil || !yes {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = driver.Driver.AppTerminate("com.android.settings")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
yes, err = driver.Driver.IsAppInForeground("com.android.settings")
|
||||
if err != nil || yes {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriver_KeepAlive(t *testing.T) {
|
||||
device, _ := NewAndroidDevice()
|
||||
driver, err := device.NewDriver(nil)
|
||||
|
||||
@@ -20,6 +20,8 @@ type Driver struct {
|
||||
urlPrefix *url.URL
|
||||
sessionId string
|
||||
client *http.Client
|
||||
// cache the last launched package name
|
||||
lastLaunchedPackageName string
|
||||
}
|
||||
|
||||
func (wd *Driver) concatURL(u *url.URL, elem ...string) string {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"math/rand"
|
||||
@@ -284,6 +285,8 @@ func saveScreenShot(raw *bytes.Buffer, fileName string) (string, error) {
|
||||
err = png.Encode(file, img)
|
||||
case "jpeg":
|
||||
err = jpeg.Encode(file, img, nil)
|
||||
case "gif":
|
||||
err = gif.Encode(file, img, nil)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported image format: %s", format)
|
||||
}
|
||||
@@ -348,6 +351,19 @@ func (dExt *DriverExt) IsImageExist(text string) bool {
|
||||
|
||||
var errActionNotImplemented = errors.New("UI action not implemented")
|
||||
|
||||
func convertToFloat64(val interface{}) (float64, error) {
|
||||
switch v := val.(type) {
|
||||
case float64:
|
||||
return v, nil
|
||||
case int:
|
||||
return float64(v), nil
|
||||
case int64:
|
||||
return float64(v), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid type for conversion to float64: %T, value: %+v", val, val)
|
||||
}
|
||||
}
|
||||
|
||||
func (dExt *DriverExt) DoAction(action MobileAction) error {
|
||||
log.Info().Str("method", string(action.Method)).Interface("params", action.Params).Msg("start UI action")
|
||||
|
||||
@@ -609,24 +625,54 @@ func (dExt *DriverExt) DoAction(action MobileAction) error {
|
||||
}
|
||||
return fmt.Errorf("invalid sleep params: %v(%T)", action.Params, action.Params)
|
||||
case CtlSleepRandom:
|
||||
if params, ok := action.Params.([]interface{}); ok && len(params) == 2 {
|
||||
var a, b float64
|
||||
if v, ok := params[0].(float64); ok {
|
||||
a = v
|
||||
} else if v, ok := params[0].(int64); ok {
|
||||
a = float64(v)
|
||||
params, ok := action.Params.([]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid sleep random params: %v(%T)", action.Params, action.Params)
|
||||
}
|
||||
// append default weight 1
|
||||
if len(params) == 2 {
|
||||
params = append(params, 1.0)
|
||||
}
|
||||
|
||||
var sections []struct {
|
||||
min, max, weight float64
|
||||
}
|
||||
totalProb := 0.0
|
||||
for i := 0; i+3 <= len(params); i += 3 {
|
||||
min, err := convertToFloat64(params[i])
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "invalid minimum time: %v", params[i])
|
||||
}
|
||||
if v, ok := params[1].(float64); ok {
|
||||
b = v
|
||||
} else if v, ok := params[1].(int64); ok {
|
||||
b = float64(v)
|
||||
max, err := convertToFloat64(params[i+1])
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "invalid maximum time: %v", params[i+1])
|
||||
}
|
||||
n := a + rand.Float64()*(b-a)
|
||||
log.Info().Float64("duration", n).Msg("sleep random seconds")
|
||||
time.Sleep(time.Duration(n*1000) * time.Millisecond)
|
||||
weight, err := convertToFloat64(params[i+2])
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "invalid weight value: %v", params[i+2])
|
||||
}
|
||||
totalProb += weight
|
||||
sections = append(sections,
|
||||
struct{ min, max, weight float64 }{min, max, weight},
|
||||
)
|
||||
}
|
||||
|
||||
if totalProb == 0 {
|
||||
log.Warn().Msg("total weight is 0, skip sleep")
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid sleep random params: %v(%T)", action.Params, action.Params)
|
||||
|
||||
r := rand.Float64()
|
||||
accProb := 0.0
|
||||
for _, s := range sections {
|
||||
accProb += s.weight / totalProb
|
||||
if r < accProb {
|
||||
n := s.min + rand.Float64()*(s.max-s.min)
|
||||
log.Info().Float64("duration", n).Msg("sleep random seconds")
|
||||
time.Sleep(time.Duration(n*1000) * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case CtlScreenShot:
|
||||
// take snapshot
|
||||
log.Info().Msg("take snapshot for current screen")
|
||||
|
||||
@@ -627,10 +627,14 @@ type WebDriver interface {
|
||||
|
||||
// AppLaunch Launch an application with given bundle identifier in scope of current session.
|
||||
// !This method is only available since Xcode9 SDK
|
||||
AppLaunch(bundleId string) error
|
||||
// AppTerminate Terminate an application with the given bundle id.
|
||||
AppLaunch(packageName string) error
|
||||
// AppTerminate Terminate an application with the given pacakge name.
|
||||
// Either `true` if the app has been successfully terminated or `false` if it was not running
|
||||
AppTerminate(bundleId string) (bool, error)
|
||||
AppTerminate(packageName string) (bool, error)
|
||||
// GetLastLaunchedApp returns the package name of the last launched app
|
||||
GetLastLaunchedApp() string
|
||||
// IsAppInForeground returns true if the given package is in foreground
|
||||
IsAppInForeground(packageName string) (bool, error)
|
||||
|
||||
// StartCamera Starts a new camera for recording
|
||||
StartCamera() error
|
||||
|
||||
@@ -141,7 +141,7 @@ func WithIOSPcapOptions(options ...gidevice.PcapOption) IOSDeviceOption {
|
||||
}
|
||||
}
|
||||
|
||||
func IOSDevices(udid ...string) (devices []gidevice.Device, err error) {
|
||||
func GetIOSDevices(udid ...string) (devices []gidevice.Device, err error) {
|
||||
var usbmux gidevice.Usbmux
|
||||
if usbmux, err = gidevice.NewUsbmux(); err != nil {
|
||||
return nil, errors.Wrap(code.IOSDeviceConnectionError,
|
||||
@@ -168,6 +168,15 @@ func IOSDevices(udid ...string) (devices []gidevice.Device, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(deviceList) == 0 {
|
||||
var err error
|
||||
if udid == nil || (len(udid) == 1 && udid[0] == "") {
|
||||
err = fmt.Errorf("no ios device found")
|
||||
} else {
|
||||
err = fmt.Errorf("no ios device found for udid %v", udid)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return deviceList, nil
|
||||
}
|
||||
|
||||
@@ -223,31 +232,27 @@ func NewIOSDevice(options ...IOSDeviceOption) (device *IOSDevice, err error) {
|
||||
option(device)
|
||||
}
|
||||
|
||||
deviceList, err := IOSDevices(device.UDID)
|
||||
deviceList, err := GetIOSDevices(device.UDID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(code.IOSDeviceConnectionError, err.Error())
|
||||
}
|
||||
|
||||
for _, dev := range deviceList {
|
||||
udid := dev.Properties().SerialNumber
|
||||
device.UDID = udid
|
||||
device.d = dev
|
||||
dev := deviceList[0]
|
||||
udid := dev.Properties().SerialNumber
|
||||
device.UDID = udid
|
||||
device.d = dev
|
||||
|
||||
// run xctest if XCTestBundleID is set
|
||||
if device.XCTestBundleID != "" {
|
||||
_, err = device.RunXCTest(device.XCTestBundleID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("udid", udid).Msg("failed to init XCTest")
|
||||
continue
|
||||
}
|
||||
// run xctest if XCTestBundleID is set
|
||||
if device.XCTestBundleID != "" {
|
||||
_, err = device.RunXCTest(device.XCTestBundleID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("udid", udid).Msg("failed to init XCTest")
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Str("udid", device.UDID).Msg("select device")
|
||||
return device, nil
|
||||
}
|
||||
|
||||
return nil, errors.Wrap(code.IOSDeviceConnectionError,
|
||||
fmt.Sprintf("device %s not found", device.UDID))
|
||||
log.Info().Str("udid", device.UDID).Msg("select ios device")
|
||||
return device, nil
|
||||
}
|
||||
|
||||
type IOSDevice struct {
|
||||
|
||||
@@ -308,6 +308,9 @@ func (wd *wdaDriver) AppLaunch(bundleId string) (err error) {
|
||||
data := make(map[string]interface{})
|
||||
data["bundleId"] = bundleId
|
||||
_, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/apps/launch")
|
||||
if err == nil {
|
||||
wd.lastLaunchedPackageName = bundleId
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -328,6 +331,9 @@ func (wd *wdaDriver) AppTerminate(bundleId string) (successful bool, err error)
|
||||
if successful, err = rawResp.valueConvertToBool(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if wd.lastLaunchedPackageName == bundleId {
|
||||
wd.lastLaunchedPackageName = "" // reset last launched package name
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -348,6 +354,14 @@ func (wd *wdaDriver) AppDeactivate(second float64) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) GetLastLaunchedApp() (packageName string) {
|
||||
return wd.lastLaunchedPackageName
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) IsAppInForeground(packageName string) (bool, error) {
|
||||
return false, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) Tap(x, y int, options ...DataOption) error {
|
||||
return wd.TapFloat(float64(x), float64(y), options...)
|
||||
}
|
||||
|
||||
@@ -93,16 +93,19 @@ func (s *veDEMOCRService) getOCRResult(imageBuf *bytes.Buffer) ([]OCRResult, err
|
||||
// retry 3 times
|
||||
for i := 1; i <= 3; i++ {
|
||||
resp, err = client.Do(req)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
var logID string
|
||||
if resp != nil {
|
||||
logID = getLogID(resp.Header)
|
||||
}
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
log.Debug().
|
||||
Str("X-TT-LOGID", logID).
|
||||
Int("imageBufSize", size).
|
||||
Msg("request OCR service success")
|
||||
break
|
||||
}
|
||||
log.Error().Err(err).
|
||||
Str("logID", logID).
|
||||
Str("X-TT-LOGID", logID).
|
||||
Int("imageBufSize", size).
|
||||
Msgf("request OCR service failed, retry %d", i)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
@@ -281,10 +281,14 @@ func (s *StepMobile) Sleep(n float64) *StepMobile {
|
||||
return &StepMobile{step: s.step}
|
||||
}
|
||||
|
||||
func (s *StepMobile) SleepRandom(a, b float64) *StepMobile {
|
||||
// SleepRandom specify random sleeping seconds after last action
|
||||
// params have two different kinds:
|
||||
// 1. [min, max] : min and max are float64 time range boudaries
|
||||
// 2. [min1, max1, weight1, min2, max2, weight2, ...] : weight is the probability of the time range
|
||||
func (s *StepMobile) SleepRandom(params ...float64) *StepMobile {
|
||||
s.mobileStep().Actions = append(s.mobileStep().Actions, uixt.MobileAction{
|
||||
Method: uixt.CtlSleepRandom,
|
||||
Params: []float64{a, b},
|
||||
Params: params,
|
||||
})
|
||||
return &StepMobile{step: s.step}
|
||||
}
|
||||
@@ -557,6 +561,24 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
||||
attachments := make(map[string]interface{})
|
||||
if err != nil {
|
||||
attachments["error"] = err.Error()
|
||||
|
||||
// check if app is in foreground
|
||||
packageName := uiDriver.Driver.GetLastLaunchedApp()
|
||||
yes, err2 := uiDriver.Driver.IsAppInForeground(packageName)
|
||||
if packageName != "" && (!yes || err2 != nil) {
|
||||
log.Error().Err(err2).Str("packageName", packageName).Msg("app is not in foreground")
|
||||
err = errors.Wrap(code.MobileUIAppNotInForegroundError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// take screenshot after each step
|
||||
screenshotPath, err := uiDriver.ScreenShot(
|
||||
fmt.Sprintf("step_%d", time.Now().Unix()))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("step", step.Name).Msg("take screenshot failed")
|
||||
} else {
|
||||
log.Info().Str("path", screenshotPath).Msg("take screenshot on step finished")
|
||||
screenshots = append(screenshots, screenshotPath)
|
||||
}
|
||||
|
||||
// save attachments
|
||||
@@ -595,16 +617,6 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
||||
}
|
||||
}
|
||||
|
||||
// take snapshot
|
||||
screenshotPath, err := uiDriver.ScreenShot(
|
||||
fmt.Sprintf("validate_%d", time.Now().Unix()))
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("step", step.Name).Msg("take screenshot failed")
|
||||
} else {
|
||||
log.Info().Str("path", screenshotPath).Msg("take screenshot before validation")
|
||||
screenshots = append(screenshots, screenshotPath)
|
||||
}
|
||||
|
||||
// validate
|
||||
validateResults, err := validateUI(uiDriver, step.Validators)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user