Merge pull request #1629 from httprunner/ios-pcap

- feat: log step elapsed in seconds
- feat: log uixt action elapsed in seconds
- change: log elapsed seconds for shell execution
- refactor: raise adb error
This commit is contained in:
debugtalk
2023-06-28 21:02:13 +08:00
committed by GitHub
18 changed files with 329 additions and 446 deletions

View File

@@ -61,11 +61,12 @@ var (
// android device related: [60, 70)
var (
AndroidDeviceConnectionError = errors.New("android device connection error") // 60
AndroidDeviceUSBDriverError = errors.New("android device USB driver error") // 61
AndroidShellExecError = errors.New("android adb shell exec error") // 62
AndroidScreenShotError = errors.New("android screenshot error") // 65
AndroidCaptureLogError = errors.New("android capture log error") // 66
AndroidDeviceConnectionError = errors.New("android device general connection error") // 60
AndroidDeviceConnectionRefusedError = errors.New("android device connection refused") // 61
AndroidShellExecError = errors.New("android adb shell exec error") // 62
AndroidDeviceOfflineError = errors.New("android device offline") // 63
AndroidScreenShotError = errors.New("android screenshot error") // 65
AndroidCaptureLogError = errors.New("android capture log error") // 66
)
// UI automation related: [70, 80)
@@ -127,11 +128,12 @@ var errorsMap = map[error]int{
IOSCaptureLogError: 56,
// android related
AndroidDeviceConnectionError: 60,
AndroidDeviceUSBDriverError: 61,
AndroidShellExecError: 62,
AndroidScreenShotError: 65,
AndroidCaptureLogError: 66,
AndroidDeviceConnectionError: 60,
AndroidDeviceConnectionRefusedError: 61,
AndroidShellExecError: 62,
AndroidDeviceOfflineError: 63,
AndroidScreenShotError: 65,
AndroidCaptureLogError: 66,
// UI automation related
MobileUIDriverError: 70,

View File

@@ -1 +1 @@
v4.3.4-beta-2306212230
v4.3.4-beta-2306282100

View File

@@ -5,7 +5,10 @@ import (
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/code"
)
const (
@@ -84,6 +87,12 @@ func (c Client) DeviceSerialList() (serials []string, err error) {
}
func (c Client) DeviceList() (devices []*Device, err error) {
defer func() {
if err != nil && errors.Cause(err) == nil {
err = errors.Wrap(code.AndroidDeviceConnectionError, err.Error())
}
}()
var resp string
if resp, err = c.executeCommand("host:devices-l"); err != nil {
return
@@ -212,10 +221,7 @@ func (c Client) executeCommand(command string, onlyVerifyResponse ...bool) (resp
}
defer func() { _ = tp.Close() }()
if err = tp.Send(command); err != nil {
return "", err
}
if err = tp.VerifyResponse(); err != nil {
if err = tp.SendWithCheck(command); err != nil {
return "", err
}

View File

@@ -7,11 +7,18 @@ import (
"testing"
)
func TestClient_ServerVersion(t *testing.T) {
adbClient, err := NewClient()
var adbClient Client
func setupClient(t *testing.T) {
var err error
adbClient, err = NewClient()
if err != nil {
t.Fatal(err)
}
}
func TestClient_ServerVersion(t *testing.T) {
setupClient(t)
adbServerVersion, err := adbClient.ServerVersion()
if err != nil {
@@ -22,10 +29,7 @@ func TestClient_ServerVersion(t *testing.T) {
}
func TestClient_DeviceSerialList(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupClient(t)
serials, err := adbClient.DeviceSerialList()
if err != nil {
@@ -38,15 +42,7 @@ func TestClient_DeviceSerialList(t *testing.T) {
}
func TestClient_DeviceList(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
for i := range devices {
t.Log(devices[i].serial, devices[i].DeviceInfo())
@@ -54,10 +50,7 @@ func TestClient_DeviceList(t *testing.T) {
}
func TestClient_ForwardList(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupClient(t)
deviceForwardList, err := adbClient.ForwardList()
if err != nil {
@@ -70,80 +63,59 @@ func TestClient_ForwardList(t *testing.T) {
}
func TestClient_ForwardKillAll(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupClient(t)
err = adbClient.ForwardKillAll()
err := adbClient.ForwardKillAll()
if err != nil {
t.Fatal(err)
}
}
func TestClient_Connect(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupClient(t)
err = adbClient.Connect("192.168.1.28")
err := adbClient.Connect("192.168.1.28")
if err != nil {
t.Fatal(err)
}
}
func TestClient_Disconnect(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupClient(t)
err = adbClient.Disconnect("192.168.1.28")
err := adbClient.Disconnect("192.168.1.28")
if err != nil {
t.Fatal(err)
}
}
func TestClient_DisconnectAll(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupClient(t)
err = adbClient.DisconnectAll()
err := adbClient.DisconnectAll()
if err != nil {
t.Fatal(err)
}
}
func TestClient_KillServer(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupClient(t)
err = adbClient.KillServer()
err := adbClient.KillServer()
if err != nil {
t.Fatal(err)
}
}
func TestScreenCap(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
dl, err := adbClient.DeviceList()
if err != nil {
t.Error(err)
for _, d := range devices {
res, err := d.ScreenCap()
if err != nil {
t.Error(err)
}
t.Log(len(res))
ioutil.WriteFile("/tmp/1.png", res, 0o644)
}
d := dl[0]
res, err := d.ScreenCap()
if err != nil {
t.Error(err)
}
t.Log(len(res))
ioutil.WriteFile("/tmp/1.png", res, 0o644)
}

View File

@@ -36,12 +36,18 @@ const (
StateOnline DeviceState = "online"
StateOffline DeviceState = "offline"
StateDisconnected DeviceState = "disconnected"
StateBootloader DeviceState = "bootloader"
StateRecovery DeviceState = "recovery"
StateUnauthorized DeviceState = "unauthorized"
)
var deviceStateStrings = map[string]DeviceState{
"": StateDisconnected,
"offline": StateOffline,
"device": StateOnline,
"": StateDisconnected, // no devices/emulators found
"offline": StateOffline,
"bootloader": StateBootloader,
"recovery": StateRecovery,
"unauthorized": StateUnauthorized,
"device": StateOnline,
}
func deviceStateConv(k string) (deviceState DeviceState) {
@@ -245,13 +251,10 @@ func (d *Device) ReverseForwardKillAll() error {
func (d *Device) RunShellCommand(cmd string, args ...string) (string, error) {
raw, err := d.RunShellCommandWithBytes(cmd, args...)
if err != nil {
if errors.Is(err, code.AndroidDeviceConnectionError) {
return "", err
}
return "", errors.Wrap(code.AndroidShellExecError, err.Error())
if err != nil && errors.Cause(err) == nil {
err = errors.Wrap(code.AndroidShellExecError, err.Error())
}
return string(raw), nil
return string(raw), err
}
func (d *Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, error) {
@@ -264,15 +267,20 @@ func (d *Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, e
if strings.TrimSpace(cmd) == "" {
return nil, errors.New("adb shell: command cannot be empty")
}
startTime := time.Now()
defer func() {
// log elapsed seconds for shell execution
log.Debug().Str("cmd",
fmt.Sprintf("adb -s %s shell %s", d.serial, cmd)).
Float64("elapsed(s)", time.Since(startTime).Seconds()).
Msg("run adb shell")
}()
raw, err := d.executeCommand(fmt.Sprintf("shell:%s", cmd))
return raw, err
}
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 {
@@ -282,6 +290,15 @@ func (d *Device) RunShellCommandV2WithBytes(cmd string, args ...string) ([]byte,
return nil, errors.New("adb shell: command cannot be empty")
}
startTime := time.Now()
defer func() {
// log elapsed seconds for shell execution
log.Debug().Str("cmd",
fmt.Sprintf("adb -s %s shell %s", d.serial, cmd)).
Float64("elapsed(s)", time.Since(startTime).Seconds()).
Msg("run adb shell in v2")
}()
raw, err := d.executeCommand(fmt.Sprintf("shell,v2,raw:%s", cmd))
if err != nil {
return raw, err
@@ -359,39 +376,11 @@ func (d *Device) createDeviceTransport() (tp transport, err error) {
return transport{}, err
}
if err = tp.Send(fmt.Sprintf("host:transport:%s", d.serial)); err != nil {
return transport{}, err
}
err = tp.VerifyResponse()
err = tp.SendWithCheck(fmt.Sprintf("host:transport:%s", d.serial))
return
}
func (d *Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw []byte, err error) {
startTime := time.Now()
defer func() {
// log elapsed seconds for shell execution
elapsed := time.Since(startTime).Seconds()
if strings.HasPrefix(command, "shell,v2,raw:") {
cmd := strings.TrimPrefix(command, "shell,v2,raw:")
log.Debug().Str("cmd",
fmt.Sprintf("adb -s %s shell %s", d.serial, cmd)).
Float64("elapsed(s)", elapsed).
Msg("run adb shell in v2")
} else if strings.HasPrefix(command, "shell:") {
cmd := strings.TrimPrefix(command, "shell:")
log.Debug().Str("cmd",
fmt.Sprintf("adb -s %s shell %s", d.serial, cmd)).
Float64("elapsed(s)", elapsed).
Msg("run adb shell")
} else {
cmd := strings.ReplaceAll(command, ":", " ")
log.Debug().Str("command",
fmt.Sprintf("adb -s %s %s", d.serial, cmd)).
Float64("elapsed(s)", elapsed).
Msg("run adb command")
}
}()
if len(onlyVerifyResponse) == 0 {
onlyVerifyResponse = []bool{false}
}
@@ -402,11 +391,7 @@ func (d *Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw
}
defer func() { _ = tp.Close() }()
if err = tp.Send(command); err != nil {
return nil, err
}
if err = tp.VerifyResponse(); err != nil {
if err = tp.SendWithCheck(command); err != nil {
return nil, err
}
@@ -530,13 +515,12 @@ func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) {
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 {
cmd := fmt.Sprintf("abb_exec:package\x00install\x00-t\x00-S\x00%d", filesize)
if err = tp.SendWithCheck(cmd); 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
@@ -594,7 +578,7 @@ func (d *Device) Uninstall(packageName string, keepData ...bool) (string, error)
args = append(args, "-k")
}
args = append(args, packageName)
return d.RunShellCommandV2("pm", args...)
return d.RunShellCommand("pm", args...)
}
func (d *Device) ScreenCap() ([]byte, error) {

View File

@@ -12,16 +12,19 @@ import (
"time"
)
func TestDevice_State(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
var devices []*Device
devices, err := adbClient.DeviceList()
func setupDevices(t *testing.T) {
var err error
setupClient(t)
devices, err = adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
}
func TestDevice_State(t *testing.T) {
setupDevices(t)
for i := range devices {
dev := devices[i]
@@ -30,19 +33,17 @@ func TestDevice_State(t *testing.T) {
t.Fatal(err)
}
t.Log(dev.Serial(), state)
resp, err := dev.RunShellCommand("ls")
if err != nil {
t.Fatal(err)
}
t.Log(string(resp))
}
}
func TestDevice_DevicePath(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
for i := range devices {
dev := devices[i]
@@ -55,15 +56,7 @@ func TestDevice_DevicePath(t *testing.T) {
}
func TestDevice_Product(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
for i := range devices {
dev := devices[i]
@@ -73,15 +66,7 @@ func TestDevice_Product(t *testing.T) {
}
func TestDevice_Model(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
for i := range devices {
dev := devices[i]
@@ -90,15 +75,7 @@ func TestDevice_Model(t *testing.T) {
}
func TestDevice_Usb(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
for i := range devices {
dev := devices[i]
@@ -107,15 +84,7 @@ func TestDevice_Usb(t *testing.T) {
}
func TestDevice_DeviceInfo(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
for i := range devices {
dev := devices[i]
@@ -124,74 +93,54 @@ func TestDevice_DeviceInfo(t *testing.T) {
}
func TestDevice_Forward(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
for _, device := range devices {
localPort := 61000
err := device.Forward(localPort, 6790)
if err != nil {
t.Fatal(err)
}
localPort := 61000
err = devices[0].Forward(localPort, 6790)
if err != nil {
t.Fatal(err)
}
err = devices[0].ForwardKill(localPort)
if err != nil {
t.Fatal(err)
err = device.ForwardKill(localPort)
if err != nil {
t.Fatal(err)
}
}
}
func TestDevice_ReverseForward(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
for _, device := range devices {
localPort := 5005
err := device.ReverseForward(localPort, "localabstract:scrcpy")
if err != nil {
t.Fatal(err)
}
err = device.ReverseForward(localPort, "localabstract:scrcpy1")
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 = device.ReverseForwardList()
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)
err = device.ReverseForwardKill("localabstract:scrcpy1")
if err != nil {
t.Fatal(err)
}
err = device.ReverseForwardKillAll()
if err != nil {
t.Fatal(err)
}
}
}
func TestDevice_ForwardList(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
for i := range devices {
dev := devices[i]
@@ -204,32 +153,18 @@ func TestDevice_ForwardList(t *testing.T) {
}
func TestDevice_ForwardKill(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
err = devices[0].ForwardKill(6790)
if err != nil {
t.Fatal(err)
for _, device := range devices {
err := device.ForwardKill(6790)
if err != nil {
t.Fatal(err)
}
}
}
func TestDevice_RunShellCommand(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
// for i := range devices {
// dev := devices[i]
@@ -243,112 +178,75 @@ func TestDevice_RunShellCommand(t *testing.T) {
// t.Log("\n"+dev.serial, cmdOutput)
// }
dev := devices[len(devices)-1]
dev = devices[0]
// cmdOutput, err := dev.RunShellCommand("monkey", "-p", "tv.danmaku.bili", "-c", "android.intent.category.LAUNCHER", "1")
cmdOutput, err := dev.RunShellCommand("ls /sdcard")
// cmdOutput, err := dev.RunShellCommandWithBytes("screencap -p")
if err != nil {
t.Fatal(dev.serial, err)
for _, dev := range devices {
// cmdOutput, err := dev.RunShellCommand("monkey", "-p", "tv.danmaku.bili", "-c", "android.intent.category.LAUNCHER", "1")
cmdOutput, err := dev.RunShellCommand("ls /sdcard")
// cmdOutput, err := dev.RunShellCommandWithBytes("screencap -p")
if err != nil {
t.Fatal(dev.serial, err)
}
t.Log("\n⬇"+dev.serial+"⬇️\n", cmdOutput)
}
t.Log("\n⬇"+dev.serial+"⬇️\n", cmdOutput)
}
func TestDevice_EnableAdbOverTCP(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
dev := devices[len(devices)-1]
dev = devices[0]
err = dev.EnableAdbOverTCP()
if err != nil {
t.Fatal(err)
for _, dev := range devices {
err := dev.EnableAdbOverTCP()
if err != nil {
t.Fatal(err)
}
}
}
func TestDevice_List(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
for _, dev := range devices {
// fileEntries, err := dev.List("/sdcard")
fileEntries, err := dev.List("/sdcard/Download")
if err != nil {
t.Fatal(err)
}
dev := devices[len(devices)-1]
dev = devices[0]
// fileEntries, err := dev.List("/sdcard")
fileEntries, err := dev.List("/sdcard/Download")
if err != nil {
t.Fatal(err)
}
for i := range fileEntries {
t.Log(fileEntries[i].Name, "\t", fileEntries[i].IsDir())
for i := range fileEntries {
t.Log(fileEntries[i].Name, "\t", fileEntries[i].IsDir())
}
}
}
func TestDevice_Push(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
for _, dev := range devices {
file, _ := os.Open("/Users/hero/Documents/temp/MuMu共享文件夹/test.txt")
err := dev.PushFile(file, "/sdcard/Download/push.txt", time.Now())
if err != nil {
t.Fatal(err)
}
dev := devices[len(devices)-1]
dev = devices[0]
file, _ := os.Open("/Users/hero/Documents/temp/MuMu共享文件夹/test.txt")
err = dev.PushFile(file, "/sdcard/Download/push.txt", time.Now())
if err != nil {
t.Fatal(err)
}
err = dev.Push(strings.NewReader("world"), "/sdcard/Download/hello.txt", time.Now())
if err != nil {
t.Fatal(err)
err = dev.Push(strings.NewReader("world"), "/sdcard/Download/hello.txt", time.Now())
if err != nil {
t.Fatal(err)
}
}
}
func TestDevice_Pull(t *testing.T) {
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
}
setupDevices(t)
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
for _, dev := range devices {
buffer := bytes.NewBufferString("")
err := dev.Pull("/sdcard/Download/hello.txt", buffer)
if err != nil {
t.Fatal(err)
}
dev := devices[len(devices)-1]
dev = devices[0]
buffer := bytes.NewBufferString("")
err = dev.Pull("/sdcard/Download/hello.txt", buffer)
if err != nil {
t.Fatal(err)
}
userHomeDir, _ := os.UserHomeDir()
if err = ioutil.WriteFile(userHomeDir+"/Desktop/hello.txt", buffer.Bytes(), DefaultFileMode); err != nil {
t.Fatal(err)
userHomeDir, _ := os.UserHomeDir()
if err = ioutil.WriteFile(userHomeDir+"/Desktop/hello.txt", buffer.Bytes(), DefaultFileMode); err != nil {
t.Fatal(err)
}
}
}
@@ -405,40 +303,22 @@ func TestDevice_RunShellCommandBackgroundWithBytes(t *testing.T) {
}
func TestDevice_InstallAPK(t *testing.T) {
setupDevices(t)
apk, _ := os.Open("test.apk")
adbClient, err := NewClient()
if err != nil {
t.Fatal(err)
for _, dev := range devices {
res, err := dev.InstallAPK(apk)
if err != nil {
t.Fatal(err)
}
t.Log(res)
}
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)
setupDevices(t)
for _, dev := range devices {
t.Log(dev.GetFeatures())
}
devices, err := adbClient.DeviceList()
if err != nil {
t.Fatal(err)
}
dev := devices[len(devices)-1]
dev = devices[0]
t.Log(dev.GetFeatures())
}

View File

@@ -17,6 +17,7 @@ type syncTransport struct {
readTimeout time.Duration
}
// newSyncTransport creates a new sync transport with existed tcp socket connection
func newSyncTransport(sock net.Conn, readTimeout time.Duration) syncTransport {
return syncTransport{sock: sock, readTimeout: readTimeout}
}

View File

@@ -7,6 +7,7 @@ import (
"net"
"regexp"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
@@ -19,19 +20,41 @@ var ErrConnBroken = errors.New("socket connection broken")
var DefaultAdbReadTimeout time.Duration = 60
var regexDeviceOffline = regexp.MustCompile("device .* not found")
type transport struct {
sock net.Conn
readTimeout time.Duration
}
// newTransport creates a new tcp socket connection
func newTransport(address string, readTimeout ...time.Duration) (tp transport, err error) {
if len(readTimeout) == 0 {
readTimeout = []time.Duration{DefaultAdbReadTimeout}
}
tp.readTimeout = readTimeout[0]
if tp.sock, err = net.Dial("tcp", address); err != nil {
err = fmt.Errorf("adb transport: %w", err)
tp = transport{
readTimeout: readTimeout[0],
}
tp.sock, err = net.Dial("tcp", address)
if err == nil {
// dial success
return tp, nil
}
// connection refused
if strings.Contains(err.Error(), "connect: connection refused") {
err = errors.Wrap(code.AndroidDeviceConnectionRefusedError, err.Error())
return
}
// device offline
if regexDeviceOffline.MatchString(err.Error()) {
err = errors.Wrap(code.AndroidDeviceOfflineError, err.Error())
return
}
// other connection errors
err = errors.Wrap(code.AndroidDeviceConnectionError, err.Error())
return
}
@@ -48,8 +71,6 @@ func (t transport) Conn() net.Conn {
return t.sock
}
var regexDeviceOffline = regexp.MustCompile("device .* not found")
func (t transport) VerifyResponse() (err error) {
var status string
if status, err = t.ReadStringN(4); err != nil {
@@ -64,11 +85,6 @@ func (t transport) VerifyResponse() (err error) {
return err
}
if regexDeviceOffline.MatchString(sError) {
// device offline
return errors.Wrap(code.AndroidDeviceConnectionError, sError)
}
log.Warn().Str("status", status).Str("err", sError).
Msg("verify adb response failed")
return errors.New(sError)
@@ -126,11 +142,15 @@ func (t transport) Close() (err error) {
return t.sock.Close()
}
func (t transport) CreateSyncTransport() (sTp syncTransport, err error) {
if err = t.Send("sync:"); err != nil {
return syncTransport{}, err
func (t transport) SendWithCheck(command string) (err error) {
if err = t.Send(command); err != nil {
return err
}
if err = t.VerifyResponse(); err != nil {
return t.VerifyResponse()
}
func (t transport) CreateSyncTransport() (sTp syncTransport, err error) {
if err = t.SendWithCheck("sync:"); err != nil {
return syncTransport{}, err
}
sTp = newSyncTransport(t.sock, t.readTimeout)

View File

@@ -377,7 +377,19 @@ func (dExt *DriverExt) GenAbsScope(x1, y1, x2, y2 float64) AbsScope {
}
func (dExt *DriverExt) DoAction(action MobileAction) error {
log.Info().Str("method", string(action.Method)).Interface("params", action.Params).Msg("start UI action")
log.Debug().
Str("method", string(action.Method)).
Interface("params", action.Params).
Msg("uixt action start")
actionStartTime := time.Now()
defer func() {
log.Debug().
Str("method", string(action.Method)).
Interface("params", action.Params).
Float64("elapsed(s)", time.Since(actionStartTime).Seconds()).
Msg("uixt action end")
}()
switch action.Method {
case ACTION_AppInstall:
@@ -506,7 +518,7 @@ func (dExt *DriverExt) DoAction(action MobileAction) error {
case ACTION_ScreenShot:
// take screenshot
log.Info().Msg("take screenshot for current screen")
_, _, err := dExt.TakeScreenShot(builtin.GenNameWithTimestamp("%d_screenshot"))
_, _, err := dExt.takeScreenShot(builtin.GenNameWithTimestamp("%d_screenshot"))
return err
case ACTION_StartCamera:
return dExt.Driver.StartCamera()

View File

@@ -91,7 +91,7 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er
deviceList, err := GetAndroidDevices(device.SerialNumber)
if err != nil {
return nil, errors.Wrap(code.AndroidDeviceConnectionError, err.Error())
return nil, err
}
dev := deviceList[0]
@@ -106,12 +106,11 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er
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 nil, err
}
if devices, err = adbClient.DeviceList(); err != nil {
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
fmt.Sprintf("list android devices failed: %v", err))
return nil, err
}
var deviceList []*gadb.Device
@@ -190,11 +189,11 @@ func (dev *AndroidDevice) NewDriver(capabilities Capabilities) (driverExt *Drive
func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver WebDriver, err error) {
var localPort int
if localPort, err = getFreePort(); err != nil {
return nil, errors.Wrap(code.AndroidDeviceUSBDriverError,
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
fmt.Sprintf("get free port failed: %v", err))
}
if err = dev.d.Forward(localPort, UIA2ServerPort); err != nil {
return nil, errors.Wrap(code.AndroidDeviceUSBDriverError,
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
fmt.Sprintf("forward port %d->%d failed: %v",
localPort, UIA2ServerPort, err))
}
@@ -204,7 +203,7 @@ func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver WebDri
uiaDriver, err := NewUIADriver(capabilities, rawURL)
if err != nil {
_ = dev.d.ForwardKill(localPort)
return nil, errors.Wrap(code.AndroidDeviceUSBDriverError, err.Error())
return nil, errors.Wrap(code.AndroidDeviceConnectionError, err.Error())
}
uiaDriver.adbClient = dev.d
uiaDriver.logcat = dev.logcat

View File

@@ -124,9 +124,8 @@ func NewDriverExt(device Device, driver WebDriver) (dExt *DriverExt, err error)
return dExt, nil
}
// TakeScreenShot takes screenshot and saves image file to $CWD/screenshots/ folder
// if fileName is empty, it will not save image file and only return raw image data
func (dExt *DriverExt) TakeScreenShot(fileName ...string) (raw *bytes.Buffer, path string, err error) {
// takeScreenShot takes screenshot and saves image file to $CWD/screenshots/ folder
func (dExt *DriverExt) takeScreenShot(fileName string) (raw *bytes.Buffer, path string, err error) {
// iOS 优先使用 MJPEG 流进行截图,性能最优
// 如果 MJPEG 流未开启,则使用 WebDriver 的截图接口
if dExt.frame != nil {
@@ -145,17 +144,13 @@ func (dExt *DriverExt) TakeScreenShot(fileName ...string) (raw *bytes.Buffer, pa
}
// save screenshot to file
if len(fileName) > 0 && fileName[0] != "" {
path := filepath.Join(env.ScreenShotsPath, fileName[0])
path, err := dExt.saveScreenShot(compressed, path)
if err != nil {
log.Error().Err(err).Msg("save screenshot file failed")
return nil, "", err
}
return compressed, path, nil
path = filepath.Join(env.ScreenShotsPath, fileName)
path, err = dExt.saveScreenShot(compressed, path)
if err != nil {
log.Error().Err(err).Msg("save screenshot file failed")
return nil, "", err
}
return compressed, "", nil
return compressed, path, nil
}
func compressImageBuffer(raw *bytes.Buffer) (compressed *bytes.Buffer, err error) {

View File

@@ -59,7 +59,7 @@ type ImageResult struct {
LiveType string `json:"liveType"` // 直播间类型
}
type ImageResponse struct {
type APIResponseImage struct {
Code int `json:"code"`
Message string `json:"message"`
Result ImageResult `json:"result"`
@@ -248,7 +248,7 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer) (
log.Debug().
Str("X-TT-LOGID", logID).
Int("image_bytes", size).
Float64("elapsed_seconds", elapsed.Seconds()).
Float64("elapsed(s)", elapsed.Seconds()).
Msg("request OCR service success")
break
}
@@ -279,7 +279,7 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer) (
return
}
var imageResponse ImageResponse
var imageResponse APIResponseImage
err = json.Unmarshal(results, &imageResponse)
if err != nil {
log.Error().Err(err).
@@ -334,19 +334,20 @@ type IImageService interface {
}
// GetScreenResult takes a screenshot, returns the image recognization result
func (dExt *DriverExt) GetScreenResult() (imageResult ImageResult, err error) {
func (dExt *DriverExt) GetScreenResult() (screenResult *ScreenResult, err error) {
var bufSource *bytes.Buffer
var imagePath string
if bufSource, imagePath, err = dExt.TakeScreenShot(
if bufSource, imagePath, err = dExt.takeScreenShot(
builtin.GenNameWithTimestamp("%d_ocr")); err != nil {
return
}
imageResult, err = dExt.ImageService.GetImage(bufSource)
imageResult, err := dExt.ImageService.GetImage(bufSource)
if err != nil {
log.Error().Err(err).Msg("GetScreenResult failed")
log.Error().Err(err).Msg("GetImage from ImageService failed")
return
}
imageResult.imagePath = imagePath
imageUrl := imageResult.URL
if imageUrl != "" {
@@ -354,20 +355,25 @@ func (dExt *DriverExt) GetScreenResult() (imageResult ImageResult, err error) {
log.Debug().Str("imagePath", imagePath).Str("imageUrl", imageUrl).Msg("log screenshot")
}
dExt.cacheStepData.screenResults[imagePath] = &ScreenResult{
Texts: imageResult.OCRResult.ToOCRTexts(),
screenResult = &ScreenResult{
Texts: imageResult.OCRResult.ToOCRTexts(),
Tags: nil,
Popularity: Popularity{},
}
if imageResult.LiveType != "" {
screenResult.Tags = []string{imageResult.LiveType}
}
dExt.cacheStepData.screenResults[imagePath] = screenResult
imageResult.imagePath = imagePath
return imageResult, nil
return screenResult, nil
}
func (dExt *DriverExt) GetScreenTexts() (ocrTexts OCRTexts, err error) {
imageResult, err := dExt.GetScreenResult()
screenResult, err := dExt.GetScreenResult()
if err != nil {
return
}
return imageResult.OCRResult.ToOCRTexts(), nil
return screenResult.Texts, nil
}
func (dExt *DriverExt) FindScreenText(text string, options ...ActionOption) (point PointF, err error) {

View File

@@ -101,7 +101,7 @@ func (dExt *DriverExt) FindAllImageRect(search string) (rects []image.Rectangle,
if bufSearch, err = getBufFromDisk(search); err != nil {
return nil, err
}
if bufSource, _, err = dExt.TakeScreenShot(builtin.GenNameWithTimestamp("%d_cv")); err != nil {
if bufSource, _, err = dExt.takeScreenShot(builtin.GenNameWithTimestamp("%d_cv")); err != nil {
return nil, err
}

View File

@@ -12,8 +12,8 @@ var popups = [][]string{
{".*青少年.*", "我知道了"}, // 青少年弹窗
{".*个人信息保护.*", "同意"},
{".*通讯录.*", "拒绝"},
{".*更新.*", "以后再说|稍后"},
{".*升级.*", "以后再说|稍后"},
{".*更新.*", "以后再说|稍后|取消"},
{".*升级.*", "以后再说|稍后|取消"},
{".*定位.*", "仅.*允许"},
{".*拍照.*", "仅.*允许"},
{".*录音.*", "仅.*允许"},
@@ -24,16 +24,16 @@ var popups = [][]string{
{"管理使用时间", ".*忽略.*"},
}
func (dExt *DriverExt) autoPopupHandler(screenResult *ScreenResult) error {
func (dExt *DriverExt) AutoPopupHandler(screenTexts OCRTexts) error {
for _, popup := range popups {
if len(popup) != 2 {
continue
}
points, err := screenResult.Texts.FindTexts([]string{popup[0], popup[1]}, WithRegex(true))
points, err := screenTexts.FindTexts([]string{popup[0], popup[1]}, WithRegex(true))
if err == nil {
log.Warn().Interface("popup", popup).
Interface("texts", screenResult.Texts).Msg("text popup found")
Interface("texts", screenTexts).Msg("text popup found")
point := points[1].Center()
log.Info().Str("text", points[1].Text).Msg("close popup")
if err := dExt.TapAbsXY(point.X, point.Y); err != nil {

View File

@@ -135,14 +135,20 @@ func (dExt *DriverExt) swipeToTapTexts(texts []string, options ...ActionOption)
var point PointF
findTexts := func(d *DriverExt) error {
var err error
ocrTexts, err := d.GetScreenTexts()
screenTexts, err := d.GetScreenTexts()
if err != nil {
return err
}
points, err := ocrTexts.FindTexts(texts, dExt.ParseActionOptions(options...)...)
points, err := screenTexts.FindTexts(texts, dExt.ParseActionOptions(options...)...)
if err != nil {
// target texts not found, try to auto handle popup
if e := dExt.AutoPopupHandler(screenTexts); e != nil {
log.Error().Err(e).Msg("auto handle popup failed")
}
return err
}
// target texts found, pick the first one
point = points[0].Center() // FIXME
return nil
}

View File

@@ -250,17 +250,13 @@ func (l *LiveCrawler) Run(driver *DriverExt, enterPoint PointF) error {
}
// take screenshot and get screen texts by OCR
imageResult, err := l.driver.GetScreenResult()
screenResult, err := l.driver.GetScreenResult()
if err != nil {
log.Error().Err(err).Msg("OCR GetTexts failed")
time.Sleep(3 * time.Second)
continue
}
screenResult := l.driver.cacheStepData.screenResults[imageResult.imagePath]
screenResult.Tags = []string{"live"}
if imageResult.LiveType != "" {
screenResult.Tags = append(screenResult.Tags, imageResult.LiveType)
}
screenResult.Tags = append([]string{"live"}, screenResult.Tags...)
// check live type and incr live count
if err := l.currentStat.incrLive(screenResult, l.driver); err != nil {
@@ -364,7 +360,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) {
return errors.Wrap(code.InterruptError, "feed crawler interrupted")
default:
// take screenshot and get screen texts by OCR
imageResult, err := dExt.GetScreenResult()
screenResult, err := dExt.GetScreenResult()
if err != nil {
if strings.Contains(err.Error(), "connect: connection refused") {
return err
@@ -373,17 +369,15 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) {
time.Sleep(3 * time.Second)
continue
}
screenResult := dExt.cacheStepData.screenResults[imageResult.imagePath]
// automatic handling of pop-up windows
if err := dExt.autoPopupHandler(screenResult); err != nil {
if err := dExt.AutoPopupHandler(screenResult.Texts); err != nil {
log.Error().Err(err).Msg("auto handle popup failed")
return err
}
// check if live video && run live crawler
texts := imageResult.OCRResult.ToOCRTexts()
if enterPoint, isLive := liveCrawler.checkLiveVideo(texts); isLive {
if enterPoint, isLive := liveCrawler.checkLiveVideo(screenResult.Texts); isLive {
log.Info().Msg("live video found")
if !liveCrawler.currentStat.isLiveTargetAchieved() {
if err := liveCrawler.Run(dExt, enterPoint); err != nil {

View File

@@ -539,8 +539,9 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
parsedName = step.Name()
}
stepName := convertString(parsedName)
log.Info().Str("step", stepName).
Str("type", string(step.Type())).Msg("run step start")
stepType := string(step.Type())
log.Info().Str("step", stepName).Str("type", stepType).Msg("run step start")
stepStartTime := time.Now()
// run times of step
loopTimes := step.Struct().Loops
@@ -563,10 +564,10 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
}
// run step
stepStartTime := time.Now().Unix()
startTime := time.Now().Unix()
stepResult, err = step.Run(r)
stepResult.Name = stepName + loopIndex
stepResult.StartTime = stepStartTime
stepResult.StartTime = startTime
r.updateSummary(stepResult)
}
@@ -576,19 +577,22 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
r.sessionVariables[k] = v
}
stepElapsed := time.Since(stepStartTime).Seconds()
if err == nil {
log.Info().Str("step", stepResult.Name).
Str("type", string(stepResult.StepType)).
log.Info().Str("step", stepName).
Str("type", stepType).
Bool("success", true).
Float64("elapsed(s)", stepElapsed).
Interface("exportVars", stepResult.ExportVars).
Msg("run step end")
continue
}
// failed
log.Error().Err(err).Str("step", stepResult.Name).
Str("type", string(stepResult.StepType)).
log.Error().Err(err).Str("step", stepName).
Str("type", stepType).
Bool("success", false).
Float64("elapsed(s)", stepElapsed).
Msg("run step end")
// interrupted or timeout, abort running

View File

@@ -6,7 +6,6 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/code"
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
)
@@ -598,10 +597,13 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
}
}
// take screenshot after each step
if _, _, err2 := uiDriver.TakeScreenShot(
builtin.GenNameWithTimestamp("%d_step_") + step.Name); err2 != nil {
// take screenshot and get screen texts by OCR
screenTexts, err2 := uiDriver.GetScreenTexts()
if err2 != nil {
log.Error().Err(err2).Str("step", step.Name).Msg("take screenshot failed on step finished")
} else if err3 := uiDriver.AutoPopupHandler(screenTexts); err3 != nil {
// automatic handling of pop-up windows on each step finished
log.Error().Err(err3).Msg("auto handle popup failed")
}
// save attachments