mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 11:29:48 +08:00
feat: run Android UI automation with adb by default, add uixt.WithUIA2(true) option to use uiautomator2
This commit is contained in:
@@ -1,5 +1,14 @@
|
||||
# Release History
|
||||
|
||||
## v4.3.2 (2022-12-26)
|
||||
|
||||
**go version**
|
||||
|
||||
- feat: run Android UI automation with adb by default, add `uixt.WithUIA2(true)` option to use uiautomator2
|
||||
- refactor: remove unused APIs in UI automation
|
||||
- refactor: convert cases by specifying from/to format
|
||||
- change: remove traceroute/curl sub commands
|
||||
|
||||
## v4.3.1 (2022-12-22)
|
||||
|
||||
**go version**
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func TestAndroidDouYinLive(t *testing.T) {
|
||||
testCase := &hrp.TestCase{
|
||||
Config: hrp.NewConfig("通过 feed 头像进入抖音直播间").
|
||||
SetAndroid(uixt.WithAdbLogOn(true)),
|
||||
SetAndroid(uixt.WithUIA2(false), uixt.WithAdbLogOn(true)),
|
||||
TestSteps: []hrp.IStep{
|
||||
hrp.NewStep("启动抖音").
|
||||
Android().
|
||||
|
||||
@@ -1 +1 @@
|
||||
v4.3.1
|
||||
v4.3.2
|
||||
337
hrp/pkg/uixt/android_adb_driver.go
Normal file
337
hrp/pkg/uixt/android_adb_driver.go
Normal file
@@ -0,0 +1,337 @@
|
||||
package uixt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
|
||||
)
|
||||
|
||||
type adbDriver struct {
|
||||
Driver
|
||||
|
||||
adbClient gadb.Device
|
||||
logcat *AdbLogcat
|
||||
}
|
||||
|
||||
func NewAdbDriver() *adbDriver {
|
||||
log.Info().Msg("init adb driver")
|
||||
return &adbDriver{}
|
||||
}
|
||||
|
||||
func (ad *adbDriver) NewSession(capabilities Capabilities) (sessionInfo SessionInfo, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) DeleteSession() (err error) {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Status() (deviceStatus DeviceStatus, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) DeviceInfo() (deviceInfo DeviceInfo, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Location() (location Location, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) BatteryInfo() (batteryInfo BatteryInfo, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) WindowSize() (size Size, err error) {
|
||||
// adb shell wm size
|
||||
resp, err := ad.adbClient.RunShellCommand("wm", "size")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Physical size: 1080x2340
|
||||
s := strings.Trim(strings.Split(resp, ": ")[1], "\n")
|
||||
ss := strings.Split(s, "x")
|
||||
width, _ := strconv.Atoi(ss[0])
|
||||
height, _ := strconv.Atoi(ss[1])
|
||||
size = Size{Width: width, Height: height}
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Screen() (screen Screen, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Scale() (scale float64, err error) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// PressBack simulates a short press on the BACK button.
|
||||
func (ad *adbDriver) PressBack(options ...DataOption) (err error) {
|
||||
// adb shell input keyevent 4
|
||||
_, err = ad.adbClient.RunShellCommand("input", "keyevent", fmt.Sprintf("%d", KCBack))
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) StartCamera() (err error) {
|
||||
if _, err = ad.adbClient.RunShellCommand("rm", "-r", "/sdcard/DCIM/Camera"); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
var version string
|
||||
if version, err = ad.adbClient.RunShellCommand("getprop", "ro.build.version.release"); err != nil {
|
||||
return err
|
||||
}
|
||||
if version == "11" || version == "12" {
|
||||
if _, err = ad.adbClient.RunShellCommand("am", "start", "-a", "android.media.action.STILL_IMAGE_CAMERA"); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
if _, err = ad.adbClient.RunShellCommand("input", "swipe", "750", "1000", "250", "1000"); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
if _, err = ad.adbClient.RunShellCommand("input", "keyevent", fmt.Sprintf("%d", KCCamera)); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
} else {
|
||||
if _, err = ad.adbClient.RunShellCommand("am", "start", "-a", "android.media.action.VIDEO_CAPTURE"); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
if _, err = ad.adbClient.RunShellCommand("input", "keyevent", fmt.Sprintf("%d", KCCamera)); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (ad *adbDriver) StopCamera() (err error) {
|
||||
err = ad.PressBack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ad.Homescreen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// kill samsung shell command
|
||||
if _, err = ad.adbClient.RunShellCommand("am", "force-stop", "com.sec.android.app.camera"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// kill other camera (huawei mi)
|
||||
if _, err = ad.adbClient.RunShellCommand("am", "force-stop", "com.android.camera2"); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Homescreen() (err error) {
|
||||
return ad.PressKeyCode(KCHome, KMEmpty)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta) (err error) {
|
||||
// adb shell input keyevent <keyCode>
|
||||
_, err = ad.adbClient.RunShellCommand(
|
||||
"input", "keyevent", fmt.Sprintf("%d", keyCode))
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) AppLaunch(bundleId 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",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(sOutput, "monkey aborted") {
|
||||
return fmt.Errorf("app launch: %s", strings.TrimSpace(sOutput))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) AppTerminate(bundleId string) (successful bool, err error) {
|
||||
// 强制停止应用,停止 <packagename> 相关的进程
|
||||
// adb shell am force-stop <packagename>
|
||||
_, err = ad.adbClient.RunShellCommand("am", "force-stop", bundleId)
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Tap(x, y int, options ...DataOption) error {
|
||||
return ad.TapFloat(float64(x), float64(y), options...)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) TapFloat(x, y float64, options ...DataOption) (err error) {
|
||||
// adb shell input tap x y
|
||||
_, err = ad.adbClient.RunShellCommand(
|
||||
"input", "tap", fmt.Sprintf("%.1f", x), fmt.Sprintf("%.1f", y))
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) DoubleTap(x, y int) error {
|
||||
return ad.DoubleTapFloat(float64(x), float64(y))
|
||||
}
|
||||
|
||||
func (ad *adbDriver) DoubleTapFloat(x, y float64) (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) TouchAndHold(x, y int, second ...float64) (err error) {
|
||||
return ad.TouchAndHoldFloat(float64(x), float64(y), second...)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) TouchAndHoldFloat(x, y float64, second ...float64) (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Drag(fromX, fromY, toX, toY int, options ...DataOption) error {
|
||||
return ad.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) DragFloat(fromX, fromY, toX, toY float64, options ...DataOption) (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Swipe(fromX, fromY, toX, toY int, options ...DataOption) error {
|
||||
return ad.SwipeFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...DataOption) error {
|
||||
// adb shell input swipe fromX fromY toX toY
|
||||
_, err := ad.adbClient.RunShellCommand(
|
||||
"input", "swipe",
|
||||
fmt.Sprintf("%.1f", fromX), fmt.Sprintf("%.1f", fromY),
|
||||
fmt.Sprintf("%.1f", toX), fmt.Sprintf("%.1f", toY),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ad *adbDriver) ForceTouch(x, y int, pressure float64, second ...float64) error {
|
||||
return ad.ForceTouchFloat(float64(x), float64(y), pressure, second...)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) ForceTouchFloat(x, y, pressure float64, second ...float64) (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) SetPasteboard(contentType PasteboardType, content string) (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) SendKeys(text string, options ...DataOption) (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Input(text string, options ...DataOption) (err error) {
|
||||
return ad.SendKeys(text, options...)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) PressButton(devBtn DeviceButton) (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Rotation() (rotation Rotation, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) SetRotation(rotation Rotation) (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Screenshot() (raw *bytes.Buffer, err error) {
|
||||
// adb shell screencap -p
|
||||
resp, err := ad.adbClient.RunShellCommandWithBytes(
|
||||
"screencap", "-p",
|
||||
)
|
||||
if err == nil {
|
||||
return bytes.NewBuffer(resp), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Source(srcOpt ...SourceOption) (source string, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) AccessibleSource() (source string, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) HealthCheck() (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) GetAppiumSettings() (settings map[string]interface{}, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) SetAppiumSettings(settings map[string]interface{}) (ret map[string]interface{}, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) IsHealthy() (healthy bool, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) StartCaptureLog(identifier ...string) (err error) {
|
||||
log.Info().Msg("start adb log recording")
|
||||
err = ad.logcat.CatchLogcat()
|
||||
if err != nil {
|
||||
err = errors.Wrap(code.AndroidCaptureLogError,
|
||||
fmt.Sprintf("start adb log recording failed: %v", err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) {
|
||||
log.Info().Msg("stop adb log recording")
|
||||
err = ad.logcat.Stop()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to get adb log recording")
|
||||
err = errors.Wrap(code.AndroidCaptureLogError,
|
||||
fmt.Sprintf("get adb log recording failed: %v", err))
|
||||
return "", err
|
||||
}
|
||||
content := ad.logcat.logBuffer.String()
|
||||
return ConvertPoints(content), nil
|
||||
}
|
||||
@@ -35,6 +35,12 @@ func WithSerialNumber(serial string) AndroidDeviceOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithUIA2(uia2On bool) AndroidDeviceOption {
|
||||
return func(device *AndroidDevice) {
|
||||
device.UIA2 = uia2On
|
||||
}
|
||||
}
|
||||
|
||||
func WithUIA2IP(ip string) AndroidDeviceOption {
|
||||
return func(device *AndroidDevice) {
|
||||
device.UIA2IP = ip
|
||||
@@ -57,6 +63,9 @@ func GetAndroidDeviceOptions(dev *AndroidDevice) (deviceOptions []AndroidDeviceO
|
||||
if dev.SerialNumber != "" {
|
||||
deviceOptions = append(deviceOptions, WithSerialNumber(dev.SerialNumber))
|
||||
}
|
||||
if dev.UIA2 {
|
||||
deviceOptions = append(deviceOptions, WithUIA2(true))
|
||||
}
|
||||
if dev.UIA2IP != "" {
|
||||
deviceOptions = append(deviceOptions, WithUIA2IP(dev.UIA2IP))
|
||||
}
|
||||
@@ -119,6 +128,7 @@ type AndroidDevice struct {
|
||||
d gadb.Device
|
||||
logcat *AdbLogcat
|
||||
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
|
||||
UIA2 bool `json:"uia2,omitempty" yaml:"uia2,omitempty"` // use uiautomator2
|
||||
UIA2IP string `json:"uia2_ip,omitempty" yaml:"uia2_ip,omitempty"` // uiautomator2 server ip
|
||||
UIA2Port int `json:"uia2_port,omitempty" yaml:"uia2_port,omitempty"` // uiautomator2 server port
|
||||
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
|
||||
@@ -129,7 +139,12 @@ func (dev *AndroidDevice) UUID() string {
|
||||
}
|
||||
|
||||
func (dev *AndroidDevice) NewDriver(capabilities Capabilities) (driverExt *DriverExt, err error) {
|
||||
driver, err := dev.NewUSBDriver(capabilities)
|
||||
var driver WebDriver
|
||||
if dev.UIA2 {
|
||||
driver, err = dev.NewUSBDriver(capabilities)
|
||||
} else {
|
||||
driver, err = dev.NewAdbDriver()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to init UIA driver")
|
||||
}
|
||||
@@ -155,8 +170,7 @@ func (dev *AndroidDevice) NewDriver(capabilities Capabilities) (driverExt *Drive
|
||||
}
|
||||
|
||||
// NewUSBDriver creates new client via USB connected device, this will also start a new session.
|
||||
// TODO: replace uiaDriver with WebDriver
|
||||
func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver *uiaDriver, err error) {
|
||||
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,
|
||||
@@ -168,27 +182,37 @@ func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver *uiaDr
|
||||
localPort, UIA2ServerPort, err))
|
||||
}
|
||||
|
||||
rawURL := fmt.Sprintf("http://%s%d:6790/wd/hub", forwardToPrefix, localPort)
|
||||
driver, err = NewUIADriver(capabilities, rawURL)
|
||||
rawURL := fmt.Sprintf("http://%s%d:%d/wd/hub",
|
||||
forwardToPrefix, localPort, UIA2ServerPort)
|
||||
uiaDriver, err := NewUIADriver(capabilities, rawURL)
|
||||
if err != nil {
|
||||
_ = dev.d.ForwardKill(localPort)
|
||||
return nil, errors.Wrap(code.AndroidDeviceUSBDriverError, err.Error())
|
||||
}
|
||||
driver.adbClient = dev.d
|
||||
driver.logcat = dev.logcat
|
||||
uiaDriver.adbClient = dev.d
|
||||
uiaDriver.logcat = dev.logcat
|
||||
|
||||
return driver, nil
|
||||
return uiaDriver, nil
|
||||
}
|
||||
|
||||
// NewHTTPDriver creates new remote HTTP client, this will also start a new session.
|
||||
// TODO: replace uiaDriver with WebDriver
|
||||
func (dev *AndroidDevice) NewHTTPDriver(capabilities Capabilities) (driver *uiaDriver, err error) {
|
||||
func (dev *AndroidDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver, err error) {
|
||||
rawURL := fmt.Sprintf("http://%s:%d/wd/hub", dev.UIA2IP, dev.UIA2Port)
|
||||
if driver, err = NewUIADriver(capabilities, rawURL); err != nil {
|
||||
uiaDriver, err := NewUIADriver(capabilities, rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
driver.adbClient = dev.d
|
||||
return driver, nil
|
||||
|
||||
uiaDriver.adbClient = dev.d
|
||||
uiaDriver.logcat = dev.logcat
|
||||
return uiaDriver, nil
|
||||
}
|
||||
|
||||
func (dev *AndroidDevice) NewAdbDriver() (driver WebDriver, err error) {
|
||||
adbDriver := NewAdbDriver()
|
||||
adbDriver.adbClient = dev.d
|
||||
adbDriver.logcat = dev.logcat
|
||||
return adbDriver, nil
|
||||
}
|
||||
|
||||
func (dev *AndroidDevice) StartPerf() error {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package uixt
|
||||
|
||||
// See https://developer.android.com/reference/android/view/KeyEvent
|
||||
|
||||
type KeyMeta int
|
||||
|
||||
const (
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestDriver_Quit(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = driver.Close(); err != nil {
|
||||
if err = driver.DeleteSession(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -335,17 +335,17 @@ func TestDeviceList(t *testing.T) {
|
||||
|
||||
func TestDriver_AppLaunch(t *testing.T) {
|
||||
device, _ := NewAndroidDevice()
|
||||
driver, err := device.NewUSBDriver(nil)
|
||||
driver, err := device.NewDriver(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = driver.AppLaunch("com.android.settings")
|
||||
err = driver.Driver.AppLaunch("com.android.settings")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
raw, err := driver.Screenshot()
|
||||
raw, err := driver.Driver.Screenshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -355,24 +355,24 @@ func TestDriver_AppLaunch(t *testing.T) {
|
||||
|
||||
func TestDriver_KeepAlive(t *testing.T) {
|
||||
device, _ := NewAndroidDevice()
|
||||
driver, err := device.NewUSBDriver(nil)
|
||||
driver, err := device.NewDriver(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = driver.AppLaunch("com.android.settings")
|
||||
err = driver.Driver.AppLaunch("com.android.settings")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = driver.Screenshot()
|
||||
_, err = driver.Driver.Screenshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(60 * time.Second)
|
||||
|
||||
_, err = driver.Screenshot()
|
||||
_, err = driver.Driver.Screenshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -380,12 +380,12 @@ func TestDriver_KeepAlive(t *testing.T) {
|
||||
|
||||
func TestDriver_AppTerminate(t *testing.T) {
|
||||
device, _ := NewAndroidDevice()
|
||||
driver, err := device.NewUSBDriver(nil)
|
||||
driver, err := device.NewDriver(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = driver.AppTerminate("tv.danmaku.bili")
|
||||
_, err = driver.Driver.AppTerminate("tv.danmaku.bili")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -9,36 +9,21 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
|
||||
)
|
||||
|
||||
// See https://developer.android.com/reference/android/view/KeyEvent
|
||||
const (
|
||||
KEYCODE_BACK string = "4"
|
||||
KEYCODE_CAMERA string = "27"
|
||||
KEYCODE_ALT_LEFT string = "57"
|
||||
KEYCODE_ALT_RIGHT string = "58"
|
||||
KEYCODE_MENU string = "82"
|
||||
KEYCODE_BREAK string = "121"
|
||||
KEYCODE_ALL_APPS string = "284"
|
||||
)
|
||||
|
||||
var errDriverNotImplemented = errors.New("driver method not implemented")
|
||||
|
||||
type uiaDriver struct {
|
||||
Driver
|
||||
|
||||
adbClient gadb.Device
|
||||
logcat *AdbLogcat
|
||||
adbDriver
|
||||
}
|
||||
|
||||
func NewUIADriver(capabilities Capabilities, urlPrefix string) (driver *uiaDriver, err error) {
|
||||
log.Info().Msg("init uiautomator2 driver")
|
||||
if capabilities == nil {
|
||||
capabilities = NewCapabilities()
|
||||
}
|
||||
@@ -98,17 +83,6 @@ func (bs BatteryStatus) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) Close() (err error) {
|
||||
if ud.sessionId == "" {
|
||||
return nil
|
||||
}
|
||||
if _, err = ud.httpDELETE("/session", ud.sessionId); err == nil {
|
||||
ud.sessionId = ""
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionInfo, err error) {
|
||||
// register(postHandler, new NewSession("/wd/hub/session"))
|
||||
var rawResp rawResponse
|
||||
@@ -126,8 +100,14 @@ func (ud *uiaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionI
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) DeleteSession() (err error) {
|
||||
// TODO
|
||||
return errDriverNotImplemented
|
||||
if ud.sessionId == "" {
|
||||
return nil
|
||||
}
|
||||
if _, err = ud.httpDELETE("/session", ud.sessionId); err == nil {
|
||||
ud.sessionId = ""
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) Status() (deviceStatus DeviceStatus, err error) {
|
||||
@@ -185,18 +165,6 @@ func (ud *uiaDriver) BatteryInfo() (batteryInfo BatteryInfo, err error) {
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) WindowSize() (size Size, err error) {
|
||||
// adb shell wm size
|
||||
resp, err := ud.adbClient.RunShellCommand("wm", "size")
|
||||
if err == nil {
|
||||
// Physical size: 1080x2340
|
||||
s := strings.Trim(strings.Split(resp, ": ")[1], "\n")
|
||||
ss := strings.Split(s, "x")
|
||||
width, _ := strconv.Atoi(ss[0])
|
||||
height, _ := strconv.Atoi(ss[1])
|
||||
size = Size{Width: width, Height: height}
|
||||
return
|
||||
}
|
||||
|
||||
// register(getHandler, new GetDeviceSize("/wd/hub/session/:sessionId/window/:windowHandle/size"))
|
||||
var rawResp rawResponse
|
||||
if rawResp, err = ud.httpGET("/session", ud.sessionId, "window/:windowHandle/size"); err != nil {
|
||||
@@ -221,84 +189,16 @@ func (ud *uiaDriver) Scale() (scale float64, err error) {
|
||||
|
||||
// PressBack simulates a short press on the BACK button.
|
||||
func (ud *uiaDriver) PressBack(options ...DataOption) (err error) {
|
||||
// adb shell input keyevent 4
|
||||
_, err = ud.adbClient.RunShellCommand("input", "keyevent", KEYCODE_BACK)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// register(postHandler, new PressBack("/wd/hub/session/:sessionId/back"))
|
||||
_, err = ud.httpPOST(nil, "/session", ud.sessionId, "back")
|
||||
return
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) StartCamera() (err error) {
|
||||
if _, err = ud.adbClient.RunShellCommand("rm", "-r", "/sdcard/DCIM/Camera"); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
var version string
|
||||
if version, err = ud.adbClient.RunShellCommand("getprop", "ro.build.version.release"); err != nil {
|
||||
return err
|
||||
}
|
||||
if version == "11" || version == "12" {
|
||||
if _, err = ud.adbClient.RunShellCommand("am", "start", "-a", "android.media.action.STILL_IMAGE_CAMERA"); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
if _, err = ud.adbClient.RunShellCommand("input", "swipe", "750", "1000", "250", "1000"); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
if _, err = ud.adbClient.RunShellCommand("input", "keyevent", KEYCODE_CAMERA); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
} else {
|
||||
if _, err = ud.adbClient.RunShellCommand("am", "start", "-a", "android.media.action.VIDEO_CAPTURE"); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
if _, err = ud.adbClient.RunShellCommand("input", "keyevent", KEYCODE_CAMERA); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) StopCamera() (err error) {
|
||||
err = ud.PressBack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ud.Homescreen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// kill samsung shell command
|
||||
if _, err = ud.adbClient.RunShellCommand("am", "force-stop", "com.sec.android.app.camera"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// kill other camera (huawei mi)
|
||||
if _, err = ud.adbClient.RunShellCommand("am", "force-stop", "com.android.camera2"); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) Homescreen() (err error) {
|
||||
return ud.PressKeyCode(KCHome, KMEmpty)
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) {
|
||||
// adb shell input keyevent <keyCode>
|
||||
_, err = ud.adbClient.RunShellCommand(
|
||||
"input", "keyevent", fmt.Sprintf("%d", keyCode))
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// register(postHandler, new PressKeyCodeAsync("/wd/hub/session/:sessionId/appium/device/press_keycode"))
|
||||
data := map[string]interface{}{
|
||||
"keycode": keyCode,
|
||||
@@ -313,40 +213,11 @@ func (ud *uiaDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...K
|
||||
return
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) AppLaunch(bundleId string) (err error) {
|
||||
// 不指定 Activity 名称启动(启动主 Activity)
|
||||
// adb shell monkey -p <packagename> -c android.intent.category.LAUNCHER 1
|
||||
sOutput, err := ud.adbClient.RunShellCommand(
|
||||
"monkey", "-p", bundleId, "-c", "android.intent.category.LAUNCHER", "1",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(sOutput, "monkey aborted") {
|
||||
return fmt.Errorf("app launch: %s", strings.TrimSpace(sOutput))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) AppTerminate(bundleId string) (successful bool, err error) {
|
||||
// 强制停止应用,停止 <packagename> 相关的进程
|
||||
// adb shell am force-stop <packagename>
|
||||
_, err = ud.adbClient.RunShellCommand("am", "force-stop", bundleId)
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) Tap(x, y int, options ...DataOption) error {
|
||||
return ud.TapFloat(float64(x), float64(y), options...)
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) TapFloat(x, y float64, options ...DataOption) (err error) {
|
||||
// adb shell input tap x y
|
||||
_, err = ud.adbClient.RunShellCommand(
|
||||
"input", "tap", fmt.Sprintf("%.1f", x), fmt.Sprintf("%.1f", y))
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// register(postHandler, new Tap("/wd/hub/session/:sessionId/appium/tap"))
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
@@ -421,16 +292,6 @@ func (ud *uiaDriver) Swipe(fromX, fromY, toX, toY int, options ...DataOption) er
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...DataOption) error {
|
||||
// adb shell input swipe fromX fromY toX toY
|
||||
_, err := ud.adbClient.RunShellCommand(
|
||||
"input", "swipe",
|
||||
fmt.Sprintf("%.1f", fromX), fmt.Sprintf("%.1f", fromY),
|
||||
fmt.Sprintf("%.1f", toX), fmt.Sprintf("%.1f", toY),
|
||||
)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// register(postHandler, new Swipe("/wd/hub/session/:sessionId/touch/perform"))
|
||||
data := map[string]interface{}{
|
||||
"startX": fromX,
|
||||
@@ -442,7 +303,7 @@ func (ud *uiaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...DataO
|
||||
// new data options in post data for extra uiautomator configurations
|
||||
newData := NewData(data, options...)
|
||||
|
||||
_, err = ud.httpPOST(newData, "/session", ud.sessionId, "touch/perform")
|
||||
_, err := ud.httpPOST(newData, "/session", ud.sessionId, "touch/perform")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -623,53 +484,3 @@ func (ud *uiaDriver) IsHealthy() (healthy bool, err error) {
|
||||
// TODO
|
||||
return healthy, errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) WaitWithTimeoutAndInterval(condition Condition, timeout, interval time.Duration) error {
|
||||
startTime := time.Now()
|
||||
for {
|
||||
done, err := condition(ud)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if done {
|
||||
return nil
|
||||
}
|
||||
|
||||
if elapsed := time.Since(startTime); elapsed > timeout {
|
||||
return fmt.Errorf("timeout after %v", elapsed)
|
||||
}
|
||||
time.Sleep(interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) WaitWithTimeout(condition Condition, timeout time.Duration) error {
|
||||
return ud.WaitWithTimeoutAndInterval(condition, timeout, DefaultWaitInterval)
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) Wait(condition Condition) error {
|
||||
return ud.WaitWithTimeoutAndInterval(condition, DefaultWaitTimeout, DefaultWaitInterval)
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) StartCaptureLog(identifier ...string) (err error) {
|
||||
log.Info().Msg("start adb log recording")
|
||||
err = ud.logcat.CatchLogcat()
|
||||
if err != nil {
|
||||
err = errors.Wrap(code.AndroidCaptureLogError,
|
||||
fmt.Sprintf("start adb log recording failed: %v", err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) StopCaptureLog() (result interface{}, err error) {
|
||||
log.Info().Msg("stop adb log recording")
|
||||
err = ud.logcat.Stop()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to get adb log recording")
|
||||
err = errors.Wrap(code.AndroidCaptureLogError,
|
||||
fmt.Sprintf("get adb log recording failed: %v", err))
|
||||
return "", err
|
||||
}
|
||||
content := ud.logcat.logBuffer.String()
|
||||
return ConvertPoints(content), nil
|
||||
}
|
||||
@@ -695,9 +695,6 @@ type WebDriver interface {
|
||||
|
||||
IsHealthy() (bool, error)
|
||||
|
||||
// Close inner connections properly
|
||||
Close() error
|
||||
|
||||
// triggers the log capture and returns the log entries
|
||||
StartCaptureLog(identifier ...string) (err error)
|
||||
StopCaptureLog() (result interface{}, err error)
|
||||
|
||||
@@ -35,20 +35,6 @@ func (wd *wdaDriver) GetMjpegClient() *http.Client {
|
||||
return wd.mjpegClient
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) Close() error {
|
||||
if wd.defaultConn != nil {
|
||||
wd.defaultConn.Close()
|
||||
}
|
||||
if wd.mjpegUSBConn != nil {
|
||||
wd.mjpegUSBConn.Close()
|
||||
}
|
||||
|
||||
if wd.mjpegClient != nil {
|
||||
wd.mjpegClient.CloseIdleConnections()
|
||||
}
|
||||
return wd.mjpegHTTPConn.Close()
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionInfo, err error) {
|
||||
// [[FBRoute POST:@"/session"].withoutSession respondWithTarget:self action:@selector(handleCreateSession:)]
|
||||
data := make(map[string]interface{})
|
||||
@@ -70,6 +56,20 @@ func (wd *wdaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionI
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) DeleteSession() (err error) {
|
||||
if wd.defaultConn != nil {
|
||||
wd.defaultConn.Close()
|
||||
}
|
||||
if wd.mjpegUSBConn != nil {
|
||||
wd.mjpegUSBConn.Close()
|
||||
}
|
||||
|
||||
if wd.mjpegClient != nil {
|
||||
wd.mjpegClient.CloseIdleConnections()
|
||||
}
|
||||
if wd.mjpegHTTPConn != nil {
|
||||
wd.mjpegHTTPConn.Close()
|
||||
}
|
||||
|
||||
// [[FBRoute DELETE:@""] respondWithTarget:self action:@selector(handleDeleteSession:)]
|
||||
_, err = wd.httpDELETE("/session", wd.sessionId)
|
||||
return
|
||||
|
||||
@@ -24,12 +24,12 @@ func checkOCR(buff *bytes.Buffer) error {
|
||||
|
||||
func TestOCRWithScreenshot(t *testing.T) {
|
||||
device, _ := NewAndroidDevice()
|
||||
driver, err := device.NewUSBDriver(nil)
|
||||
driver, err := device.NewDriver(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
raw, err := driver.Screenshot()
|
||||
raw, err := driver.Driver.Screenshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user