refactor: api

This commit is contained in:
lilong.129
2025-02-07 15:16:13 +08:00
parent 6cbab3c066
commit ae5a137353
23 changed files with 635 additions and 267 deletions

View File

@@ -27,19 +27,11 @@ import (
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
var (
DouyinServerPort = 32316
const (
// adb server
AdbServerHost = "localhost"
AdbServerPort = gadb.AdbServerPort // 5037
// uiautomator2 server
UIA2ServerHost = "localhost"
UIA2ServerPort = 6790
UIA2ServerPackageName = "io.appium.uiautomator2.server"
UIA2ServerTestPackageName = "io.appium.uiautomator2.server.test"
EvalInstallerPackageName = "sogou.mobile.explorer"
InstallViaInstallerCommand = "am start -S -n sogou.mobile.explorer/.PackageInstallerActivity -d"
)
@@ -48,13 +40,8 @@ var (
var evalite embed.FS
func NewAndroidDevice(opts ...option.AndroidDeviceOption) (device *AndroidDevice, err error) {
androidOptions := &option.AndroidDeviceConfig{
UIA2IP: UIA2ServerHost,
UIA2Port: UIA2ServerPort,
}
for _, option := range opts {
option(androidOptions)
}
androidOptions := option.NewAndroidDeviceOptions(opts...)
deviceList, err := GetAndroidDevices(androidOptions.SerialNumber)
if err != nil {
return nil, errors.Wrap(code.DeviceConnectionError, err.Error())
@@ -75,9 +62,9 @@ func NewAndroidDevice(opts ...option.AndroidDeviceOption) (device *AndroidDevice
}
device = &AndroidDevice{
AndroidDeviceConfig: androidOptions,
d: dev,
logcat: NewAdbLogcat(device.SerialNumber),
Device: dev,
AndroidDeviceOptions: androidOptions,
Logcat: NewAdbLogcat(androidOptions.SerialNumber),
}
evalToolRaw, err := evalite.ReadFile("evalite")
@@ -126,30 +113,30 @@ func GetAndroidDevices(serial ...string) (devices []*gadb.Device, err error) {
}
type AndroidDevice struct {
*option.AndroidDeviceConfig
d *gadb.Device
logcat *AdbLogcat
*gadb.Device
*option.AndroidDeviceOptions
Logcat *AdbLogcat
}
func (dev *AndroidDevice) Init() error {
dev.d.RunShellCommand("ime", "enable", UnicodeImePackageName)
dev.d.RunShellCommand("rm", "-r", config.DeviceActionLogFilePath)
func (dev *AndroidDevice) Setup() error {
dev.RunShellCommand("ime", "enable", UnicodeImePackageName)
dev.RunShellCommand("rm", "-r", config.DeviceActionLogFilePath)
if dev.UIA2 {
// uiautomator2 server must be started before
// check uiautomator server package installed
if !dev.d.IsPackageInstalled(UIA2ServerPackageName) {
if !dev.IsPackageInstalled(dev.UIA2ServerPackageName) {
return errors.Wrapf(code.MobileUIDriverAppNotInstalled,
"%s not installed", UIA2ServerPackageName)
"%s not installed", dev.UIA2ServerPackageName)
}
if !dev.d.IsPackageInstalled(UIA2ServerTestPackageName) {
if !dev.IsPackageInstalled(dev.UIA2ServerTestPackageName) {
return errors.Wrapf(code.MobileUIDriverAppNotInstalled,
"%s not installed", UIA2ServerTestPackageName)
"%s not installed", dev.UIA2ServerTestPackageName)
}
// TODO: check uiautomator server package running
// if dev.d.IsPackageRunning(UIA2ServerPackageName) {
// if dev.IsPackageRunning(UIA2ServerPackageName) {
// return nil
// }
@@ -164,6 +151,10 @@ func (dev *AndroidDevice) Init() error {
return nil
}
func (dev *AndroidDevice) Teardown() error {
return nil
}
func (dev *AndroidDevice) UUID() string {
return dev.SerialNumber
}
@@ -173,13 +164,13 @@ func (dev *AndroidDevice) LogEnabled() bool {
}
func (dev *AndroidDevice) NewDriver(opts ...option.DriverOption) (driverExt *DriverExt, err error) {
var driver IWebDriver
var driver IDriver
if dev.UIA2 || dev.LogOn {
driver, err = NewUIA2Driver(dev, opts...)
driver, err = NewUIA2Driver(dev)
} else if dev.STUB {
driver, err = NewStubDriver(dev, opts...)
driver, err = NewStubDriver(dev)
} else {
driver, err = NewADBDriver(dev, opts...)
driver, err = NewADBDriver(dev)
}
if err != nil {
return nil, errors.Wrap(err, "failed to init UIA driver")
@@ -221,13 +212,13 @@ func (dev *AndroidDevice) StopPcap() string {
}
func (dev *AndroidDevice) Uninstall(packageName string) error {
_, err := dev.d.RunShellCommand("uninstall", packageName)
_, err := dev.RunShellCommand("uninstall", packageName)
return err
}
func (dev *AndroidDevice) Install(apkPath string, opts ...option.InstallOption) error {
installOpts := option.NewInstallOptions(opts...)
brand, err := dev.d.Brand()
brand, err := dev.Brand()
if err != nil {
return err
}
@@ -245,7 +236,7 @@ func (dev *AndroidDevice) Install(apkPath string, opts ...option.InstallOption)
case "vivo":
return dev.installVivoSilent(apkPath, args...)
case "oppo", "realme", "oneplus":
if dev.d.IsPackageInstalled(EvalInstallerPackageName) {
if dev.IsPackageInstalled(EvalInstallerPackageName) {
return dev.installViaInstaller(apkPath, args...)
}
log.Warn().Msg("oppo not install eval installer")
@@ -263,13 +254,13 @@ func (dev *AndroidDevice) installVivoSilent(apkPath string, args ...string) erro
verifyCode = verifyCode[:8]
verifyCode = "-V" + verifyCode
args = append([]string{verifyCode}, args...)
_, err := dev.d.InstallAPK(apkPath, args...)
_, err := dev.InstallAPK(apkPath, args...)
return err
}
func (dev *AndroidDevice) installViaInstaller(apkPath string, args ...string) error {
appRemotePath := "/data/local/tmp/" + strconv.FormatInt(time.Now().UnixMilli(), 10) + ".apk"
err := dev.d.PushFile(apkPath, appRemotePath, time.Now())
err := dev.PushFile(apkPath, appRemotePath, time.Now())
if err != nil {
return err
}
@@ -277,7 +268,7 @@ func (dev *AndroidDevice) installViaInstaller(apkPath string, args ...string) er
defer func() {
close(done)
}()
logcat := NewAdbLogcatWithCallback(dev.d.Serial(), func(line string) {
logcat := NewAdbLogcatWithCallback(dev.Serial(), func(line string) {
re := regexp.MustCompile(`\{.*?}`)
match := re.FindString(line)
if match == "" {
@@ -307,7 +298,7 @@ func (dev *AndroidDevice) installViaInstaller(apkPath string, args ...string) er
// 需要监听是否完成安装
command := strings.Split(InstallViaInstallerCommand, " ")
args = append(command, appRemotePath)
_, err = dev.d.RunShellCommand("am", args[1:]...)
_, err = dev.RunShellCommand("am", args[1:]...)
if err != nil {
return err
}
@@ -322,13 +313,13 @@ func (dev *AndroidDevice) installViaInstaller(apkPath string, args ...string) er
}
func (dev *AndroidDevice) installCommon(apkPath string, args ...string) error {
_, err := dev.d.InstallAPK(apkPath, args...)
_, err := dev.InstallAPK(apkPath, args...)
return err
}
func (dev *AndroidDevice) GetCurrentWindow() (windowInfo WindowInfo, err error) {
// adb shell dumpsys window | grep -E 'mCurrentFocus|mFocusedApp'
output, err := dev.d.RunShellCommand("dumpsys", "window", "|", "grep", "-E", "'mCurrentFocus|mFocusedApp'")
output, err := dev.RunShellCommand("dumpsys", "window", "|", "grep", "-E", "'mCurrentFocus|mFocusedApp'")
if err != nil {
return WindowInfo{}, errors.Wrap(err, "get current window failed")
}
@@ -354,7 +345,7 @@ func (dev *AndroidDevice) GetCurrentWindow() (windowInfo WindowInfo, err error)
}
// adb shell dumpsys activity activities | grep mResumedActivity
output, err = dev.d.RunShellCommand("dumpsys", "activity", "activities", "|", "grep", "mResumedActivity")
output, err = dev.RunShellCommand("dumpsys", "activity", "activities", "|", "grep", "mResumedActivity")
if err != nil {
return WindowInfo{}, errors.Wrap(err, "get current activity failed")
}
@@ -408,7 +399,7 @@ func (dev *AndroidDevice) GetPackageInfo(packageName string) (AppInfo, error) {
}
func (dev *AndroidDevice) getPackageVersion(packageName string) (string, error) {
output, err := dev.d.RunShellCommand("dumpsys", "package", packageName, "|", "grep", "versionName")
output, err := dev.RunShellCommand("dumpsys", "package", packageName, "|", "grep", "versionName")
if err != nil {
return "", errors.Wrap(err, "get package version failed")
}
@@ -423,7 +414,7 @@ func (dev *AndroidDevice) getPackageVersion(packageName string) (string, error)
}
func (dev *AndroidDevice) getPackagePath(packageName string) (string, error) {
output, err := dev.d.RunShellCommand("pm", "path", packageName)
output, err := dev.RunShellCommand("pm", "path", packageName)
if err != nil {
return "", errors.Wrap(err, "get package path failed")
}
@@ -436,7 +427,7 @@ func (dev *AndroidDevice) getPackagePath(packageName string) (string, error) {
}
func (dev *AndroidDevice) getPackageMD5(packagePath string) (string, error) {
output, err := dev.d.RunShellCommand("md5sum", packagePath)
output, err := dev.RunShellCommand("md5sum", packagePath)
if err != nil {
return "", errors.Wrap(err, "get package md5 failed")
}
@@ -450,12 +441,12 @@ func (dev *AndroidDevice) getPackageMD5(packagePath string) (string, error) {
func (dev *AndroidDevice) startUIA2Server() error {
const maxRetries = 3
for attempt := 1; attempt <= maxRetries; attempt++ {
log.Info().Str("package", UIA2ServerTestPackageName).
log.Info().Str("package", dev.UIA2ServerTestPackageName).
Int("attempt", attempt).Msg("start uiautomator server")
// $ adb shell am instrument -w $UIA2ServerTestPackageName
// -w: wait for instrumentation to finish before returning.
// Required for test runners.
out, err := dev.d.RunShellCommand("am", "instrument", "-w", UIA2ServerTestPackageName)
out, err := dev.RunShellCommand("am", "instrument", "-w", dev.UIA2ServerTestPackageName)
if err != nil {
return errors.Wrap(err, "start uiautomator server failed")
}
@@ -469,7 +460,7 @@ func (dev *AndroidDevice) startUIA2Server() error {
}
func (dev *AndroidDevice) stopUIA2Server() error {
_, err := dev.d.RunShellCommand("am", "force-stop", UIA2ServerPackageName)
_, err := dev.RunShellCommand("am", "force-stop", dev.UIA2ServerPackageName)
return err
}

View File

@@ -23,7 +23,6 @@ import (
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/config"
"github.com/httprunner/httprunner/v5/internal/utf7"
"github.com/httprunner/httprunner/v5/pkg/gadb"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
@@ -32,20 +31,18 @@ const (
UnicodeImePackageName = "io.appium.settings/.UnicodeIME"
)
func NewADBDriver(device *AndroidDevice, opts ...option.DriverOption) (*ADBDriver, error) {
func NewADBDriver(device *AndroidDevice) (*ADBDriver, error) {
log.Info().Interface("device", device).Msg("init android adb driver")
driver := &ADBDriver{}
driver.NewSession(nil)
driver.adbClient = device.d
driver.logcat = device.logcat
driver.Device = device.Device
driver.Logcat = device.Logcat
return driver, nil
}
type ADBDriver struct {
DriverClient
adbClient *gadb.Device
logcat *AdbLogcat
*AndroidDevice
*DriverClient
}
func (ad *ADBDriver) runShellCommand(cmd string, args ...string) (output string, err error) {
@@ -68,7 +65,7 @@ func (ad *ADBDriver) runShellCommand(cmd string, args ...string) (output string,
// adb shell screencap -p
if cmd == "screencap" {
resp, err := ad.adbClient.ScreenCap()
resp, err := ad.Device.ScreenCap()
if err == nil {
driverResult.ResponseBody = "OMITTED"
return string(resp), nil
@@ -76,7 +73,7 @@ func (ad *ADBDriver) runShellCommand(cmd string, args ...string) (output string,
return "", errors.Wrap(err, "adb screencap failed")
}
output, err = ad.adbClient.RunShellCommand(cmd, args...)
output, err = ad.Device.RunShellCommand(cmd, args...)
driverResult.ResponseBody = strings.TrimSpace(output)
return output, err
}
@@ -792,7 +789,7 @@ func (ad *ADBDriver) IsHealthy() (healthy bool, err error) {
func (ad *ADBDriver) StartCaptureLog(identifier ...string) (err error) {
log.Info().Msg("start adb log recording")
// start logcat
err = ad.logcat.CatchLogcat("iesqaMonitor:V")
err = ad.Logcat.CatchLogcat("iesqaMonitor:V")
if err != nil {
err = errors.Wrap(code.DeviceCaptureLogError,
fmt.Sprintf("start adb log recording failed: %v", err))
@@ -804,7 +801,7 @@ func (ad *ADBDriver) StartCaptureLog(identifier ...string) (err error) {
func (ad *ADBDriver) StopCaptureLog() (result interface{}, err error) {
defer func() {
log.Info().Msg("stop adb log recording")
err = ad.logcat.Stop()
err = ad.Logcat.Stop()
if err != nil {
log.Error().Err(err).Msg("failed to get adb log recording")
}
@@ -812,14 +809,14 @@ func (ad *ADBDriver) StopCaptureLog() (result interface{}, err error) {
if err != nil {
log.Error().Err(err).Msg("failed to close adb log writer")
}
pointRes := ConvertPoints(ad.logcat.logs)
pointRes := ConvertPoints(ad.Logcat.logs)
// 没有解析到打点日志,走兜底逻辑
if len(pointRes) == 0 {
log.Info().Msg("action log is null, use action file >>>")
logFilePathPrefix := fmt.Sprintf("%v/data", config.ActionLogFilePath)
files := []string{}
ad.adbClient.RunShellCommand("pull", config.DeviceActionLogFilePath, config.ActionLogFilePath)
ad.Device.RunShellCommand("pull", config.DeviceActionLogFilePath, config.ActionLogFilePath)
err = filepath.Walk(config.ActionLogFilePath, func(path string, info fs.FileInfo, err error) error {
// 只是需要日志文件
if ok := strings.Contains(path, logFilePathPrefix); ok {
@@ -898,7 +895,7 @@ func (ad *ADBDriver) SetIme(imeRegx string) error {
if ime == "" {
return fmt.Errorf("failed to set ime by %s, ime list: %v", imeRegx, imeList)
}
brand, _ := ad.adbClient.Brand()
brand, _ := ad.Device.Brand()
packageName := strings.Split(ime, "/")[0]
res, err := ad.runShellCommand("ime", "set", ime)
log.Info().Str("funcName", "SetIme").Interface("ime", ime).
@@ -1061,7 +1058,7 @@ func (ad *ADBDriver) RecordScreen(folderPath string, duration time.Duration) (vi
// scrcpy -s 7d21bb91 --record=file.mp4 -N
cmd := exec.Command(
"scrcpy",
"-s", ad.adbClient.Serial(),
"-s", ad.Device.Serial(),
fmt.Sprintf("--record=%s", fileName),
"-N",
)

View File

@@ -17,17 +17,20 @@ import (
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
const StubSocketName = "com.bytest.device"
const (
StubSocketName = "com.bytest.device"
DouyinServerPort = 32316
)
func NewStubDriver(device *AndroidDevice, opts ...option.DriverOption) (driver *StubAndroidDriver, err error) {
socketLocalPort, err := device.d.Forward(StubSocketName)
func NewStubDriver(device *AndroidDevice) (driver *StubAndroidDriver, err error) {
socketLocalPort, err := device.Forward(StubSocketName)
if err != nil {
return nil, errors.Wrap(code.DeviceConnectionError,
fmt.Sprintf("forward port %d->%s failed: %v",
socketLocalPort, StubSocketName, err))
}
serverLocalPort, err := device.d.Forward(DouyinServerPort)
serverLocalPort, err := device.Forward(DouyinServerPort)
if err != nil {
return nil, errors.Wrap(code.DeviceConnectionError,
fmt.Sprintf("forward port %d->%d failed: %v",
@@ -53,16 +56,16 @@ func NewStubDriver(device *AndroidDevice, opts ...option.DriverOption) (driver *
}
driver.NewSession(nil)
driver.adbClient = device.d
driver.logcat = device.logcat
driver.Device = device.Device
driver.Logcat = device.Logcat
return driver, nil
}
type StubAndroidDriver struct {
*ADBDriver
socket net.Conn
seq int
timeout time.Duration
ADBDriver
}
type AppLoginInfo struct {
@@ -85,7 +88,7 @@ func (sad *StubAndroidDriver) httpGET(pathElem ...string) (rawResp rawResponse,
if err != nil {
return nil, fmt.Errorf("adb forward: %w", err)
}
sad.client = convertToHTTPClient(conn)
sad.Client = convertToHTTPClient(conn)
return sad.Request(http.MethodGet, sad.concatURL(nil, pathElem...), nil)
}
@@ -103,7 +106,7 @@ func (sad *StubAndroidDriver) httpPOST(data interface{}, pathElem ...string) (ra
if err != nil {
return nil, fmt.Errorf("adb forward: %w", err)
}
sad.client = convertToHTTPClient(conn)
sad.Client = convertToHTTPClient(conn)
var bsJSON []byte = nil
if data != nil {
@@ -137,7 +140,7 @@ func (sad *StubAndroidDriver) sendCommand(packageName string, cmdType string, pa
return nil, err
}
res, err := sad.adbClient.RunStubCommand(append(data, '\n'), packageName)
res, err := sad.Device.RunStubCommand(append(data, '\n'), packageName)
if err != nil {
return nil, err
}
@@ -266,7 +269,7 @@ func (sad *StubAndroidDriver) LoginNoneUIDynamic(packageName, phoneNumber string
}
func (sad *StubAndroidDriver) SetHDTStatus(status bool) error {
_, err := sad.adbClient.RunShellCommand("settings", "put", "global", "feedbacker_sso_bypass_token", "default_sso_bypass_token")
_, err := sad.Device.RunShellCommand("settings", "put", "global", "feedbacker_sso_bypass_token", "default_sso_bypass_token")
if err != nil {
log.Warn().Msg(fmt.Sprintf("failed to disable sso, error: %v", err))
}

View File

@@ -23,9 +23,9 @@ var errDriverNotImplemented = errors.New("driver method not implemented")
const forwardToPrefix = "forward-to-"
func NewUIA2Driver(device *AndroidDevice, opts ...option.DriverOption) (*UIA2Driver, error) {
func NewUIA2Driver(device *AndroidDevice) (*UIA2Driver, error) {
log.Info().Interface("device", device).Msg("init android UIA2 driver")
localPort, err := device.d.Forward(device.UIA2Port)
localPort, err := device.Forward(device.UIA2Port)
if err != nil {
return nil, errors.Wrap(code.DeviceConnectionError,
fmt.Sprintf("forward port %d->%d failed: %v",
@@ -36,9 +36,9 @@ func NewUIA2Driver(device *AndroidDevice, opts ...option.DriverOption) (*UIA2Dri
return nil, fmt.Errorf("adb forward: %w", err)
}
driver := new(UIA2Driver)
driver.client = convertToHTTPClient(conn)
driver.adbClient = device.d
driver.logcat = device.logcat
driver.Client = convertToHTTPClient(conn)
driver.Device = device.Device
driver.Logcat = device.Logcat
_, err = driver.NewSession(nil)
if err != nil {
@@ -48,35 +48,7 @@ func NewUIA2Driver(device *AndroidDevice, opts ...option.DriverOption) (*UIA2Dri
}
type UIA2Driver struct {
ADBDriver
}
type BatteryStatus int
const (
_ = iota
BatteryStatusUnknown BatteryStatus = iota
BatteryStatusCharging
BatteryStatusDischarging
BatteryStatusNotCharging
BatteryStatusFull
)
func (bs BatteryStatus) String() string {
switch bs {
case BatteryStatusUnknown:
return "unknown"
case BatteryStatusCharging:
return "charging"
case BatteryStatusDischarging:
return "discharging"
case BatteryStatusNotCharging:
return "not charging"
case BatteryStatusFull:
return "full"
default:
return fmt.Sprintf("unknown status code (%d)", bs)
}
*ADBDriver
}
func (ud *UIA2Driver) resetDriver() error {

View File

@@ -6,11 +6,13 @@ import (
// current implemeted device: IOSDevice, AndroidDevice, HarmonyDevice
type IDevice interface {
Init() error // init android device
Setup() error
Teardown() error
UUID() string // ios udid or android serial
LogEnabled() bool
// TODO: add ctx to NewDriver
// TODO: remove
NewDriver(...option.DriverOption) (driverExt *DriverExt, err error)
Install(appPath string, opts ...option.InstallOption) error
@@ -18,10 +20,4 @@ type IDevice interface {
GetPackageInfo(packageName string) (AppInfo, error)
GetCurrentWindow() (windowInfo WindowInfo, err error)
// Teardown() error
}
func NewDriver(device IDevice, opts ...option.DriverOption) (driver IWebDriver, err error) {
return
}

View File

@@ -25,8 +25,8 @@ import (
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
// IWebDriver defines methods supported by IWebDriver drivers.
type IWebDriver interface {
// current implemeted driver: ADBDriver, UIA2Driver, WDADriver, HDCDriver
type IDriver interface {
// NewSession starts a new session and returns the SessionInfo.
NewSession(capabilities option.Capabilities) (SessionInfo, error)
@@ -238,7 +238,7 @@ type DriverResult struct {
type DriverClient struct {
urlPrefix *url.URL
client *http.Client
Client *http.Client
// cache to avoid repeated query
scale float64
@@ -323,7 +323,7 @@ func (wd *DriverClient) Request(method string, rawURL string, rawBody []byte) (r
driverResult.RequestTime = time.Now()
var resp *http.Response
if resp, err = wd.client.Do(req); err != nil {
if resp, err = wd.Client.Do(req); err != nil {
return nil, err
}
defer func() {
@@ -371,14 +371,14 @@ func convertToHTTPClient(conn net.Conn) *http.Client {
type DriverExt struct {
Ctx context.Context
Device IDevice
Driver IWebDriver
Driver IDriver
ImageService IImageService // used to extract image data
// funplugin
plugin funplugin.IPlugin
}
func newDriverExt(device IDevice, driver IWebDriver, opts ...option.DriverOption) (dExt *DriverExt, err error) {
func newDriverExt(device IDevice, driver IDriver, opts ...option.DriverOption) (dExt *DriverExt, err error) {
options := option.NewDriverOptions(opts...)
dExt = &DriverExt{
@@ -404,7 +404,7 @@ func newDriverExt(device IDevice, driver IWebDriver, opts ...option.DriverOption
return dExt, nil
}
func (dExt *DriverExt) Init() error {
func (dExt *DriverExt) Setup() error {
// unlock device screen
err := dExt.Driver.Unlock()
if err != nil {

View File

@@ -0,0 +1,318 @@
package uixt_ext
import (
"context"
"fmt"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/json"
"github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
const (
StubSocketName = "com.bytest.device"
DouyinServerPort = 32316
)
func NewStubDriver(device *uixt.AndroidDevice, opts ...option.DriverOption) (driver *StubAndroidDriver, err error) {
socketLocalPort, err := device.Forward(StubSocketName)
if err != nil {
return nil, errors.Wrap(code.DeviceConnectionError,
fmt.Sprintf("forward port %d->%s failed: %v",
socketLocalPort, StubSocketName, err))
}
serverLocalPort, err := device.Forward(DouyinServerPort)
if err != nil {
return nil, errors.Wrap(code.DeviceConnectionError,
fmt.Sprintf("forward port %d->%d failed: %v",
serverLocalPort, DouyinServerPort, err))
}
address := fmt.Sprintf("127.0.0.1:%d", socketLocalPort)
conn, err := net.Dial("tcp", address)
if err != nil {
log.Err(err).Msg(fmt.Sprintf("failed to connect %s", address))
return nil, err
}
driver = &StubAndroidDriver{
socket: conn,
timeout: 10 * time.Second,
}
rawURL := fmt.Sprintf("http://forward-to-%d:%d",
serverLocalPort, DouyinServerPort)
if driver.urlPrefix, err = url.Parse(rawURL); err != nil {
return nil, err
}
driver.Device = device.Device
driver.Logcat = device.Logcat
return driver, nil
}
type StubAndroidDriver struct {
uixt.ADBDriver
socket net.Conn
seq int
timeout time.Duration
}
type AppLoginInfo struct {
Did string `json:"did,omitempty" yaml:"did,omitempty"`
Uid string `json:"uid,omitempty" yaml:"uid,omitempty"`
IsLogin bool `json:"is_login,omitempty" yaml:"is_login,omitempty"`
}
func (sad *StubAndroidDriver) httpGET(pathElem ...string) (rawResp rawResponse, err error) {
var localPort int
{
tmpURL, _ := url.Parse(sad.urlPrefix.String())
hostname := tmpURL.Hostname()
if strings.HasPrefix(hostname, forwardToPrefix) {
localPort, _ = strconv.Atoi(strings.TrimPrefix(hostname, forwardToPrefix))
}
}
conn, err := net.Dial("tcp", fmt.Sprintf(":%d", localPort))
if err != nil {
return nil, fmt.Errorf("adb forward: %w", err)
}
sad.Client = convertToHTTPClient(conn)
return sad.Request(http.MethodGet, sad.concatURL(nil, pathElem...), nil)
}
func (sad *StubAndroidDriver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, err error) {
var localPort int
{
tmpURL, _ := url.Parse(sad.urlPrefix.String())
hostname := tmpURL.Hostname()
if strings.HasPrefix(hostname, forwardToPrefix) {
localPort, _ = strconv.Atoi(strings.TrimPrefix(hostname, forwardToPrefix))
}
}
conn, err := net.Dial("tcp", fmt.Sprintf(":%d", localPort))
if err != nil {
return nil, fmt.Errorf("adb forward: %w", err)
}
sad.Client = convertToHTTPClient(conn)
var bsJSON []byte = nil
if data != nil {
if bsJSON, err = json.Marshal(data); err != nil {
return nil, err
}
}
return sad.Request(http.MethodPost, sad.concatURL(nil, pathElem...), bsJSON)
}
func (sad *StubAndroidDriver) sendCommand(packageName string, cmdType string, params map[string]interface{}, readTimeout ...time.Duration) (interface{}, error) {
sad.seq++
packet := map[string]interface{}{
"Seq": sad.seq,
"Cmd": cmdType,
"v": "",
}
for key, value := range params {
if key == "Cmd" || key == "Seq" {
return "", errors.New("params cannot be Cmd or Seq")
}
packet[key] = value
}
data, err := json.Marshal(packet)
if err != nil {
return nil, err
}
res, err := sad.Device.RunStubCommand(append(data, '\n'), packageName)
if err != nil {
return nil, err
}
var resultMap map[string]interface{}
if err := json.Unmarshal([]byte(res), &resultMap); err != nil {
return nil, err
}
if resultMap["Error"] != nil {
return nil, fmt.Errorf("failed to call stub command: %s", resultMap["Error"].(string))
}
return resultMap["Result"], nil
}
func (sad *StubAndroidDriver) DeleteSession() error {
return sad.close()
}
func (sad *StubAndroidDriver) close() error {
if sad.socket != nil {
return sad.socket.Close()
}
return nil
}
func (sad *StubAndroidDriver) Status() (uixt.DeviceStatus, error) {
app, err := sad.GetForegroundApp()
if err != nil {
return uixt.DeviceStatus{}, err
}
res, err := sad.sendCommand(app.PackageName, "Hello", nil)
if err != nil {
return uixt.DeviceStatus{}, err
}
log.Info().Msg(fmt.Sprintf("ping stub result :%v", res))
return uixt.DeviceStatus{}, nil
}
func (sad *StubAndroidDriver) Source(srcOpt ...option.SourceOption) (source string, err error) {
app, err := sad.GetForegroundApp()
if err != nil {
return "", err
}
params := map[string]interface{}{
"ClassName": "com.bytedance.byteinsight.MockOperator",
"Method": "getLayout",
"RetType": "",
"Args": []string{},
}
res, err := sad.sendCommand(app.PackageName, "CallStaticMethod", params)
if err != nil {
return "", err
}
return res.(string), nil
}
func (sad *StubAndroidDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
params := map[string]interface{}{
"phone": phoneNumber,
}
if captcha != "" {
params["captcha"] = captcha
} else if password != "" {
params["password"] = password
} else {
return info, fmt.Errorf("password and capcha is empty")
}
resp, err := sad.httpPOST(params, "/host", "/login", "account")
if err != nil {
return info, err
}
res, err := resp.valueConvertToJsonObject()
if err != nil {
return info, err
}
log.Info().Msgf("%v", res)
if res["isSuccess"] != true {
err = fmt.Errorf("falied to login %s", res["data"])
log.Err(err).Msgf("%v", res)
return info, err
}
time.Sleep(20 * time.Second)
info, err = sad.getLoginAppInfo(packageName)
if err != nil || !info.IsLogin {
return info, fmt.Errorf("falied to login %v", info)
}
return info, nil
}
func (sad *StubAndroidDriver) LogoutNoneUI(packageName string) error {
resp, err := sad.httpGET("/host", "/logout")
if err != nil {
return err
}
res, err := resp.valueConvertToJsonObject()
if err != nil {
return err
}
log.Info().Msgf("%v", res)
if res["isSuccess"] != true {
err = fmt.Errorf("falied to logout %s", res["data"])
log.Err(err).Msgf("%v", res)
return err
}
fmt.Printf("%v", resp)
if err != nil {
return err
}
time.Sleep(3 * time.Second)
return nil
}
func (sad *StubAndroidDriver) LoginNoneUIDynamic(packageName, phoneNumber string, captcha string) error {
params := map[string]interface{}{
"ClassName": "qe.python.test.LoginUtil",
"Method": "loginSync",
"RetType": "",
"Args": []string{phoneNumber, captcha},
}
res, err := sad.sendCommand(packageName, "CallStaticMethod", params)
if err != nil {
return err
}
log.Info().Msg(res.(string))
return nil
}
func (sad *StubAndroidDriver) SetHDTStatus(status bool) error {
_, err := sad.Device.RunShellCommand("settings", "put", "global", "feedbacker_sso_bypass_token", "default_sso_bypass_token")
if err != nil {
log.Warn().Msg(fmt.Sprintf("failed to disable sso, error: %v", err))
}
params := map[string]interface{}{
"ClassName": "com.bytedance.ies.stark.framework.HybridDevTool",
"Method": "setEnabled",
"RetType": "",
"Args": []bool{status},
}
res, err := sad.sendCommand("com.ss.android.ugc.aweme", "CallStaticMethod", params)
if err != nil {
return fmt.Errorf("failed to set hds status %v, error: %v", status, err)
}
log.Info().Msg(fmt.Sprintf("set hdt status result: %s", res))
return nil
}
func (sad *StubAndroidDriver) getLoginAppInfo(packageName string) (info AppLoginInfo, err error) {
resp, err := sad.httpGET("/host", "/app", "/info")
if err != nil {
return info, err
}
res, err := resp.valueConvertToJsonObject()
if err != nil {
return info, err
}
if res["isSuccess"] != true {
err = fmt.Errorf("falied to get app info %s", res["data"])
log.Err(err).Msgf("%v", res)
return info, err
}
err = json.Unmarshal([]byte(res["data"].(string)), &info)
if err != nil {
err = fmt.Errorf("falied to parse app info %s", res["data"])
return
}
return info, nil
}
func convertToHTTPClient(conn net.Conn) *http.Client {
return &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return conn, nil
},
},
Timeout: 30 * time.Second,
}
}

View File

@@ -17,13 +17,12 @@ var (
)
type HarmonyDevice struct {
*option.HarmonyDeviceConfig
d *ghdc.Device
*ghdc.Device
*option.HarmonyDeviceOptions
}
func NewHarmonyDevice(opts ...option.HarmonyDeviceOption) (device *HarmonyDevice, err error) {
deviceConfig := option.NewHarmonyDeviceConfig(opts...)
deviceConfig := option.NewHarmonyDeviceOptions(opts...)
deviceList, err := GetHarmonyDevices(deviceConfig.ConnectKey)
if err != nil {
return nil, errors.Wrap(code.DeviceConnectionError, err.Error())
@@ -43,8 +42,8 @@ func NewHarmonyDevice(opts ...option.HarmonyDeviceOption) (device *HarmonyDevice
}
device = &HarmonyDevice{
HarmonyDeviceConfig: deviceConfig,
d: dev,
HarmonyDeviceOptions: deviceConfig,
Device: dev,
}
log.Info().Str("connectKey", device.ConnectKey).Msg("init harmony device")
return device, nil
@@ -83,7 +82,11 @@ func GetHarmonyDevices(serial ...string) (devices []*ghdc.Device, err error) {
return devices, nil
}
func (dev *HarmonyDevice) Init() error {
func (dev *HarmonyDevice) Setup() error {
return nil
}
func (dev *HarmonyDevice) Teardown() error {
return nil
}
@@ -96,7 +99,7 @@ func (dev *HarmonyDevice) LogEnabled() bool {
}
func (dev *HarmonyDevice) NewDriver(opts ...option.DriverOption) (driverExt *DriverExt, err error) {
driver, err := newHarmonyDriver(dev.d)
driver, err := newHarmonyDriver(dev.Device)
if err != nil {
log.Error().Err(err).Msg("failed to new harmony driver")
return nil, err
@@ -110,8 +113,8 @@ func (dev *HarmonyDevice) NewDriver(opts ...option.DriverOption) (driverExt *Dri
return driverExt, nil
}
func (dev *HarmonyDevice) NewUSBDriver(opts ...option.DriverOption) (driver IWebDriver, err error) {
harmonyDriver, err := newHarmonyDriver(dev.d)
func (dev *HarmonyDevice) NewUSBDriver(opts ...option.DriverOption) (driver IDriver, err error) {
harmonyDriver, err := newHarmonyDriver(dev.Device)
if err != nil {
log.Error().Err(err).Msg("failed to new harmony driver")
return nil, err

View File

@@ -14,9 +14,9 @@ import (
)
type hdcDriver struct {
points []ExportPoint
DriverClient
device *ghdc.Device
*HarmonyDevice
*DriverClient
points []ExportPoint
uiDriver *ghdc.UIDriver
}
@@ -30,7 +30,7 @@ const (
func newHarmonyDriver(device *ghdc.Device) (driver *hdcDriver, err error) {
driver = new(hdcDriver)
driver.device = device
driver.Device = device
uiDriver, err := ghdc.NewUIDriver(*device)
if err != nil {
log.Error().Err(err).Msg("failed to new harmony ui driver")
@@ -100,7 +100,7 @@ func (hd *hdcDriver) Homescreen() error {
func (hd *hdcDriver) Unlock() (err error) {
// Todo 检查是否锁屏 hdc shell hidumper -s RenderService -a screen
screenInfo, err := hd.device.RunShellCommand("hidumper", "-s", "RenderService", "-a", "screen")
screenInfo, err := hd.RunShellCommand("hidumper", "-s", "RenderService", "-a", "screen")
if err != nil {
return err
}
@@ -126,7 +126,7 @@ func (hd *hdcDriver) AppLaunch(packageName string) error {
}
func (hd *hdcDriver) AppTerminate(packageName string) (bool, error) {
_, err := hd.device.RunShellCommand("aa", "force-stop", packageName)
_, err := hd.RunShellCommand("aa", "force-stop", packageName)
if err != nil {
log.Error().Err(err).Msg("failed to terminal app")
return false, err

View File

@@ -31,28 +31,6 @@ import (
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
const (
defaultWDAPort = 8100
defaultMjpegPort = 9100
defaultBightInsightPort = 8000
defaultDouyinServerPort = 32921
)
const (
// Changes the value of maximum depth for traversing elements source tree.
// It may help to prevent out of memory or timeout errors while getting the elements source tree,
// but it might restrict the depth of source tree.
// A part of elements source tree might be lost if the value was too small. Defaults to 50
snapshotMaxDepth = 10
// Allows to customize accept/dismiss alert button selector.
// It helps you to handle an arbitrary element as accept button in accept alert command.
// The selector should be a valid class chain expression, where the search root is the alert element itself.
// The default button location algorithm is used if the provided selector is wrong or does not match any element.
// e.g. **/XCUIElementTypeButton[`label CONTAINS[c] accept`]
acceptAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'允许','好','仅在使用应用期间','稍后再说'}`]"
dismissAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'不允许','暂不'}`]"
)
var tunnelManager *tunnel.TunnelManager = nil
func GetIOSDevices(udid ...string) (deviceList []ios.DeviceEntry, err error) {
@@ -126,20 +104,7 @@ func RebootTunnel() (err error) {
}
func NewIOSDevice(opts ...option.IOSDeviceOption) (device *IOSDevice, err error) {
deviceOptions := &option.IOSDeviceConfig{
Port: defaultWDAPort,
MjpegPort: defaultMjpegPort,
SnapshotMaxDepth: snapshotMaxDepth,
AcceptAlertButtonSelector: acceptAlertButtonSelector,
DismissAlertButtonSelector: dismissAlertButtonSelector,
// switch to iOS springboard before init WDA session
// avoid getting stuck when some super app is active such as douyin or wexin
ResetHomeOnStartup: true,
}
for _, option := range opts {
option(deviceOptions)
}
deviceOptions := option.NewIOSDeviceOptions(opts...)
deviceList, err := GetIOSDevices(deviceOptions.UDID)
if err != nil {
return nil, errors.Wrap(code.DeviceConnectionError, err.Error())
@@ -160,12 +125,12 @@ func NewIOSDevice(opts ...option.IOSDeviceOption) (device *IOSDevice, err error)
}
device = &IOSDevice{
IOSDeviceConfig: deviceOptions,
listeners: make(map[int]*forward.ConnListener),
d: dev,
IOSDeviceOptions: deviceOptions,
listeners: make(map[int]*forward.ConnListener),
d: dev,
}
log.Info().Str("udid", device.UDID).Msg("init ios device")
err = device.Init()
err = device.Setup()
if err != nil {
_ = device.Teardown()
return nil, err
@@ -174,7 +139,7 @@ func NewIOSDevice(opts ...option.IOSDeviceOption) (device *IOSDevice, err error)
}
type IOSDevice struct {
*option.IOSDeviceConfig
*option.IOSDeviceOptions
d ios.DeviceEntry
listeners map[int]*forward.ConnListener
}
@@ -207,7 +172,7 @@ const (
ApplicationTypeAny ApplicationType = "Any"
)
func (dev *IOSDevice) Init() error {
func (dev *IOSDevice) Setup() error {
images, err := dev.ListImages()
if err != nil {
return err
@@ -270,7 +235,7 @@ func (dev *IOSDevice) NewDriver(opts ...option.DriverOption) (driverExt *DriverE
capabilities.WithDefaultAlertAction(option.AlertActionAccept)
}
var driver IWebDriver
var driver IDriver
if dev.STUB {
driver, err = dev.NewStubDriver()
if err != nil {
@@ -561,7 +526,7 @@ func (dev *IOSDevice) Reboot() error {
}
// NewHTTPDriver creates new remote HTTP client, this will also start a new session.
func (dev *IOSDevice) NewHTTPDriver(capabilities option.Capabilities) (driver IWebDriver, err error) {
func (dev *IOSDevice) NewHTTPDriver(capabilities option.Capabilities) (driver IDriver, err error) {
var localPort int
localPort, err = strconv.Atoi(os.Getenv("WDA_LOCAL_PORT"))
if err != nil {
@@ -570,7 +535,7 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities option.Capabilities) (driver IW
return nil, errors.Wrap(code.DeviceHTTPDriverError,
fmt.Sprintf("get free port failed: %v", err))
}
if err = dev.forward(localPort, dev.Port); err != nil {
if err = dev.forward(localPort, dev.WDAPort); err != nil {
return nil, errors.Wrap(code.DeviceHTTPDriverError,
fmt.Sprintf("forward tcp port failed: %v", err))
}
@@ -586,7 +551,7 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities option.Capabilities) (driver IW
return nil, errors.Wrap(code.DeviceHTTPDriverError,
fmt.Sprintf("get free port failed: %v", err))
}
if err = dev.forward(localMjpegPort, dev.MjpegPort); err != nil {
if err = dev.forward(localMjpegPort, dev.WDAMjpegPort); err != nil {
return nil, errors.Wrap(code.DeviceHTTPDriverError,
fmt.Sprintf("forward tcp port failed: %v", err))
}
@@ -600,9 +565,9 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities option.Capabilities) (driver IW
Msg("init WDA HTTP driver")
wd := new(wdaDriver)
wd.device = dev
wd.IOSDevice = dev
wd.udid = dev.UDID
wd.client = &http.Client{
wd.Client = &http.Client{
Timeout: time.Second * 10, // 设置超时时间为 10 秒
}
@@ -634,7 +599,12 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities option.Capabilities) (driver IW
return wd, nil
}
func (dev *IOSDevice) NewStubDriver() (driver IWebDriver, err error) {
const (
defaultBightInsightPort = 8000
defaultDouyinServerPort = 32921
)
func (dev *IOSDevice) NewStubDriver() (driver IDriver, err error) {
localStubPort, err := builtin.GetFreePort()
if err != nil {
return nil, errors.Wrap(code.DeviceHTTPDriverError,

View File

@@ -14,7 +14,6 @@ import (
type stubIOSDriver struct {
*wdaDriver
DriverClient
bightInsightPrefix string
serverPrefix string
@@ -32,7 +31,7 @@ func newStubIOSDriver(bightInsightAddr, serverAddr string, dev *IOSDevice, readT
driver.bightInsightPrefix = bightInsightAddr
driver.serverPrefix = serverAddr
driver.timeout = timeout
driver.DriverClient.client = &http.Client{
driver.DriverClient.Client = &http.Client{
Timeout: time.Second * 10, // 设置超时时间为 10 秒
}
return driver, nil
@@ -516,7 +515,7 @@ func (s *stubIOSDriver) LogoutNoneUI(packageName string) error {
}
func (s *stubIOSDriver) TearDown() error {
s.DriverClient.client.CloseIdleConnections()
s.DriverClient.Client.CloseIdleConnections()
return nil
}

View File

@@ -11,7 +11,7 @@ import (
)
var (
iOSStubDriver IWebDriver
iOSStubDriver IDriver
iOSDevice *IOSDevice
)

View File

@@ -28,9 +28,9 @@ import (
)
type wdaDriver struct {
DriverClient
*IOSDevice
*DriverClient
udid string
device *IOSDevice
mjpegHTTPConn net.Conn // via HTTP
mjpegClient *http.Client
mjpegUrl string
@@ -467,7 +467,7 @@ func (wd *wdaDriver) GetForegroundApp() (appInfo AppInfo, err error) {
if err != nil {
return appInfo, err
}
apps, err := wd.device.ListApps(ApplicationTypeAny)
apps, err := wd.ListApps(ApplicationTypeAny)
if err != nil {
return appInfo, err
}
@@ -1021,7 +1021,7 @@ func (wd *wdaDriver) GetDriverResults() []*DriverResult {
func (wd *wdaDriver) TearDown() error {
wd.mjpegClient.CloseIdleConnections()
wd.client.CloseIdleConnections()
wd.Client.CloseIdleConnections()
return nil
}

View File

@@ -15,7 +15,7 @@ import (
var (
bundleId = "com.apple.Preferences"
driver IWebDriver
driver IDriver
iOSDriverExt *DriverExt
)

View File

@@ -1,18 +1,31 @@
package option
type AndroidDeviceConfig struct {
import "github.com/httprunner/httprunner/v5/pkg/gadb"
type AndroidDeviceOptions struct {
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
STUB bool `json:"stub,omitempty" yaml:"stub,omitempty"` // use stub
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
STUB bool `json:"stub,omitempty" yaml:"stub,omitempty"` // use stub
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
// adb
AdbServerHost string `json:"adb_server_host,omitempty" yaml:"adb_server_host,omitempty"`
AdbServerPort int `json:"adb_server_port,omitempty" yaml:"adb_server_port,omitempty"`
// uiautomator2
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
UIA2ServerPackageName string `json:"uia2_server_package_name,omitempty" yaml:"uia2_server_package_name,omitempty"`
UIA2ServerTestPackageName string `json:"uia2_server_test_package_name,omitempty" yaml:"uia2_server_test_package_name,omitempty"`
}
func (dev *AndroidDeviceConfig) Options() (deviceOptions []AndroidDeviceOption) {
func (dev *AndroidDeviceOptions) Options() (deviceOptions []AndroidDeviceOption) {
if dev.SerialNumber != "" {
deviceOptions = append(deviceOptions, WithSerialNumber(dev.SerialNumber))
}
if dev.STUB {
deviceOptions = append(deviceOptions, WithStub(true))
}
if dev.UIA2 {
deviceOptions = append(deviceOptions, WithUIA2(true))
}
@@ -28,54 +41,87 @@ func (dev *AndroidDeviceConfig) Options() (deviceOptions []AndroidDeviceOption)
return
}
func NewAndroidDeviceConfig(opts ...AndroidDeviceOption) *AndroidDeviceConfig {
config := &AndroidDeviceConfig{}
const (
// adb server
defaultAdbServerHost = "localhost"
defaultAdbServerPort = gadb.AdbServerPort // 5037
// uiautomator2 server
defaultUIA2ServerHost = "localhost"
defaultUIA2ServerPort = 6790
defaultUIA2ServerPackageName = "io.appium.uiautomator2.server"
defaultUIA2ServerTestPackageName = "io.appium.uiautomator2.server.test"
)
func NewAndroidDeviceOptions(opts ...AndroidDeviceOption) *AndroidDeviceOptions {
config := &AndroidDeviceOptions{}
for _, opt := range opts {
opt(config)
}
// set default
if config.AdbServerHost == "" {
config.AdbServerHost = defaultAdbServerHost
}
if config.AdbServerPort == 0 {
config.AdbServerPort = defaultAdbServerPort
}
if config.UIA2 {
if config.UIA2IP == "" && config.UIA2Port == 0 {
config.UIA2IP = defaultUIA2ServerHost
config.UIA2Port = defaultUIA2ServerPort
}
if config.UIA2ServerPackageName == "" {
config.UIA2ServerPackageName = defaultUIA2ServerPackageName
}
if config.UIA2ServerTestPackageName == "" {
config.UIA2ServerTestPackageName = defaultUIA2ServerTestPackageName
}
}
return config
}
type AndroidDeviceOption func(*AndroidDeviceConfig)
type AndroidDeviceOption func(*AndroidDeviceOptions)
func WithDriverTypeADB() AndroidDeviceOption {
return func(device *AndroidDeviceConfig) {
return func(device *AndroidDeviceOptions) {
device.STUB = false
}
}
func WithSerialNumber(serial string) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) {
return func(device *AndroidDeviceOptions) {
device.SerialNumber = serial
}
}
func WithUIA2(uia2On bool) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) {
return func(device *AndroidDeviceOptions) {
device.UIA2 = uia2On
}
}
func WithStub(stubOn bool) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) {
return func(device *AndroidDeviceOptions) {
device.STUB = stubOn
}
}
func WithUIA2IP(ip string) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) {
return func(device *AndroidDeviceOptions) {
device.UIA2IP = ip
}
}
func WithUIA2Port(port int) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) {
return func(device *AndroidDeviceOptions) {
device.UIA2Port = port
}
}
func WithAdbLogOn(logOn bool) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) {
return func(device *AndroidDeviceOptions) {
device.LogOn = logOn
}
}

View File

@@ -1,11 +1,11 @@
package option
type HarmonyDeviceConfig struct {
type HarmonyDeviceOptions struct {
ConnectKey string `json:"connect_key,omitempty" yaml:"connect_key,omitempty"`
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
}
func (dev *HarmonyDeviceConfig) Options() (deviceOptions []HarmonyDeviceOption) {
func (dev *HarmonyDeviceOptions) Options() (deviceOptions []HarmonyDeviceOption) {
if dev.ConnectKey != "" {
deviceOptions = append(deviceOptions, WithConnectKey(dev.ConnectKey))
}
@@ -15,24 +15,24 @@ func (dev *HarmonyDeviceConfig) Options() (deviceOptions []HarmonyDeviceOption)
return
}
func NewHarmonyDeviceConfig(opts ...HarmonyDeviceOption) (device *HarmonyDeviceConfig) {
device = &HarmonyDeviceConfig{}
func NewHarmonyDeviceOptions(opts ...HarmonyDeviceOption) (device *HarmonyDeviceOptions) {
device = &HarmonyDeviceOptions{}
for _, option := range opts {
option(device)
}
return
}
type HarmonyDeviceOption func(*HarmonyDeviceConfig)
type HarmonyDeviceOption func(*HarmonyDeviceOptions)
func WithConnectKey(connectKey string) HarmonyDeviceOption {
return func(device *HarmonyDeviceConfig) {
return func(device *HarmonyDeviceOptions) {
device.ConnectKey = connectKey
}
}
func WithLogOn(logOn bool) HarmonyDeviceOption {
return func(device *HarmonyDeviceConfig) {
return func(device *HarmonyDeviceOptions) {
device.LogOn = logOn
}
}

View File

@@ -1,11 +1,11 @@
package option
type IOSDeviceConfig struct {
UDID string `json:"udid,omitempty" yaml:"udid,omitempty"`
Port int `json:"port,omitempty" yaml:"port,omitempty"` // WDA remote port
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
STUB bool `json:"stub,omitempty" yaml:"stub,omitempty"` // use stub
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
type IOSDeviceOptions struct {
UDID string `json:"udid,omitempty" yaml:"udid,omitempty"`
WDAPort int `json:"port,omitempty" yaml:"port,omitempty"` // WDA remote port
WDAMjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
STUB bool `json:"stub,omitempty" yaml:"stub,omitempty"` // use stub
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
// switch to iOS springboard before init WDA session
ResetHomeOnStartup bool `json:"reset_home_on_startup,omitempty" yaml:"reset_home_on_startup,omitempty"`
@@ -16,15 +16,15 @@ type IOSDeviceConfig struct {
DismissAlertButtonSelector string `json:"dismiss_alert_button_selector,omitempty" yaml:"dismiss_alert_button_selector,omitempty"`
}
func (dev *IOSDeviceConfig) Options() (deviceOptions []IOSDeviceOption) {
func (dev *IOSDeviceOptions) Options() (deviceOptions []IOSDeviceOption) {
if dev.UDID != "" {
deviceOptions = append(deviceOptions, WithUDID(dev.UDID))
}
if dev.Port != 0 {
deviceOptions = append(deviceOptions, WithWDAPort(dev.Port))
if dev.WDAPort != 0 {
deviceOptions = append(deviceOptions, WithWDAPort(dev.WDAPort))
}
if dev.MjpegPort != 0 {
deviceOptions = append(deviceOptions, WithWDAMjpegPort(dev.MjpegPort))
if dev.WDAMjpegPort != 0 {
deviceOptions = append(deviceOptions, WithWDAMjpegPort(dev.WDAMjpegPort))
}
if dev.STUB {
deviceOptions = append(deviceOptions, WithIOSStub(true))
@@ -47,66 +47,108 @@ func (dev *IOSDeviceConfig) Options() (deviceOptions []IOSDeviceOption) {
return
}
func NewIOSDeviceConfig(opts ...IOSDeviceOption) *IOSDeviceConfig {
config := &IOSDeviceConfig{}
const (
defaultWDAPort = 8100
defaultMjpegPort = 9100
)
const (
// Changes the value of maximum depth for traversing elements source tree.
// It may help to prevent out of memory or timeout errors while getting the elements source tree,
// but it might restrict the depth of source tree.
// A part of elements source tree might be lost if the value was too small. Defaults to 50
defaultSnapshotMaxDepth = 10
// Allows to customize accept/dismiss alert button selector.
// It helps you to handle an arbitrary element as accept button in accept alert command.
// The selector should be a valid class chain expression, where the search root is the alert element itself.
// The default button location algorithm is used if the provided selector is wrong or does not match any element.
// e.g. **/XCUIElementTypeButton[`label CONTAINS[c] accept`]
acceptAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'允许','好','仅在使用应用期间','稍后再说'}`]"
dismissAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'不允许','暂不'}`]"
)
func NewIOSDeviceOptions(opts ...IOSDeviceOption) *IOSDeviceOptions {
config := &IOSDeviceOptions{}
for _, opt := range opts {
opt(config)
}
if config.WDAPort == 0 {
config.WDAPort = defaultWDAPort
}
if config.WDAMjpegPort == 0 {
config.WDAMjpegPort = defaultMjpegPort
}
if config.SnapshotMaxDepth == 0 {
config.SnapshotMaxDepth = defaultSnapshotMaxDepth
}
if config.AcceptAlertButtonSelector == "" {
config.AcceptAlertButtonSelector = acceptAlertButtonSelector
}
if config.DismissAlertButtonSelector == "" {
config.DismissAlertButtonSelector = dismissAlertButtonSelector
}
// switch to iOS springboard before init WDA session
// avoid getting stuck when some super app is active such as douyin or wexin
config.ResetHomeOnStartup = true
return config
}
type IOSDeviceOption func(*IOSDeviceConfig)
type IOSDeviceOption func(*IOSDeviceOptions)
func WithUDID(udid string) IOSDeviceOption {
return func(device *IOSDeviceConfig) {
return func(device *IOSDeviceOptions) {
device.UDID = udid
}
}
func WithWDAPort(port int) IOSDeviceOption {
return func(device *IOSDeviceConfig) {
device.Port = port
return func(device *IOSDeviceOptions) {
device.WDAPort = port
}
}
func WithWDAMjpegPort(port int) IOSDeviceOption {
return func(device *IOSDeviceConfig) {
device.MjpegPort = port
return func(device *IOSDeviceOptions) {
device.WDAMjpegPort = port
}
}
func WithWDALogOn(logOn bool) IOSDeviceOption {
return func(device *IOSDeviceConfig) {
return func(device *IOSDeviceOptions) {
device.LogOn = logOn
}
}
func WithIOSStub(stub bool) IOSDeviceOption {
return func(device *IOSDeviceConfig) {
return func(device *IOSDeviceOptions) {
device.STUB = stub
}
}
func WithResetHomeOnStartup(reset bool) IOSDeviceOption {
return func(device *IOSDeviceConfig) {
return func(device *IOSDeviceOptions) {
device.ResetHomeOnStartup = reset
}
}
func WithSnapshotMaxDepth(depth int) IOSDeviceOption {
return func(device *IOSDeviceConfig) {
return func(device *IOSDeviceOptions) {
device.SnapshotMaxDepth = depth
}
}
func WithAcceptAlertButtonSelector(selector string) IOSDeviceOption {
return func(device *IOSDeviceConfig) {
return func(device *IOSDeviceOptions) {
device.AcceptAlertButtonSelector = selector
}
}
func WithDismissAlertButtonSelector(selector string) IOSDeviceOption {
return func(device *IOSDeviceConfig) {
return func(device *IOSDeviceOptions) {
device.DismissAlertButtonSelector = selector
}
}

View File

@@ -1,6 +1,9 @@
package uixt
import "math"
import (
"fmt"
"math"
)
type DeviceStatus struct {
Message string `json:"message"`
@@ -122,6 +125,34 @@ func (v BatteryState) String() string {
}
}
type BatteryStatus int
const (
_ = iota
BatteryStatusUnknown BatteryStatus = iota
BatteryStatusCharging
BatteryStatusDischarging
BatteryStatusNotCharging
BatteryStatusFull
)
func (bs BatteryStatus) String() string {
switch bs {
case BatteryStatusUnknown:
return "unknown"
case BatteryStatusCharging:
return "charging"
case BatteryStatusDischarging:
return "discharging"
case BatteryStatusNotCharging:
return "not charging"
case BatteryStatusFull:
return "full"
default:
return fmt.Sprintf("unknown status code (%d)", bs)
}
}
type Size struct {
Width int `json:"width"`
Height int `json:"height"`
@@ -233,7 +264,7 @@ type Rotation struct {
Z int `json:"z"`
}
type Condition func(wd IWebDriver) (bool, error)
type Condition func(wd IDriver) (bool, error)
type Direction string