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

@@ -119,9 +119,9 @@ func (c *TConfig) SetWebSocket(times, interval, timeout, size int64) *TConfig {
} }
func (c *TConfig) SetIOS(opts ...option.IOSDeviceOption) *TConfig { func (c *TConfig) SetIOS(opts ...option.IOSDeviceOption) *TConfig {
iosOptions := option.NewIOSDeviceConfig(opts...) iosOptions := option.NewIOSDeviceOptions(opts...)
device := &uixt.IOSDevice{ device := &uixt.IOSDevice{
IOSDeviceConfig: iosOptions, IOSDeviceOptions: iosOptions,
} }
// each device can have its own settings // each device can have its own settings
@@ -140,9 +140,9 @@ func (c *TConfig) SetIOS(opts ...option.IOSDeviceOption) *TConfig {
} }
func (c *TConfig) SetHarmony(opts ...option.HarmonyDeviceOption) *TConfig { func (c *TConfig) SetHarmony(opts ...option.HarmonyDeviceOption) *TConfig {
harmonyOptions := option.NewHarmonyDeviceConfig(opts...) harmonyOptions := option.NewHarmonyDeviceOptions(opts...)
device := &uixt.HarmonyDevice{ device := &uixt.HarmonyDevice{
HarmonyDeviceConfig: harmonyOptions, HarmonyDeviceOptions: harmonyOptions,
} }
// each device can have its own settings // each device can have its own settings
@@ -161,9 +161,9 @@ func (c *TConfig) SetHarmony(opts ...option.HarmonyDeviceOption) *TConfig {
} }
func (c *TConfig) SetAndroid(opts ...option.AndroidDeviceOption) *TConfig { func (c *TConfig) SetAndroid(opts ...option.AndroidDeviceOption) *TConfig {
uiaOptions := option.NewAndroidDeviceConfig(opts...) uiaOptions := option.NewAndroidDeviceOptions(opts...)
device := &uixt.AndroidDevice{ device := &uixt.AndroidDevice{
AndroidDeviceConfig: uiaOptions, AndroidDeviceOptions: uiaOptions,
} }
// each device can have its own settings // each device can have its own settings

View File

@@ -1 +1 @@
v5.0.0+2502062237 v5.0.0+2502071516

View File

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

View File

@@ -23,7 +23,6 @@ import (
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/config" "github.com/httprunner/httprunner/v5/internal/config"
"github.com/httprunner/httprunner/v5/internal/utf7" "github.com/httprunner/httprunner/v5/internal/utf7"
"github.com/httprunner/httprunner/v5/pkg/gadb"
"github.com/httprunner/httprunner/v5/pkg/uixt/option" "github.com/httprunner/httprunner/v5/pkg/uixt/option"
) )
@@ -32,20 +31,18 @@ const (
UnicodeImePackageName = "io.appium.settings/.UnicodeIME" 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") log.Info().Interface("device", device).Msg("init android adb driver")
driver := &ADBDriver{} driver := &ADBDriver{}
driver.NewSession(nil) driver.NewSession(nil)
driver.adbClient = device.d driver.Device = device.Device
driver.logcat = device.logcat driver.Logcat = device.Logcat
return driver, nil return driver, nil
} }
type ADBDriver struct { type ADBDriver struct {
DriverClient *AndroidDevice
*DriverClient
adbClient *gadb.Device
logcat *AdbLogcat
} }
func (ad *ADBDriver) runShellCommand(cmd string, args ...string) (output string, err error) { 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 // adb shell screencap -p
if cmd == "screencap" { if cmd == "screencap" {
resp, err := ad.adbClient.ScreenCap() resp, err := ad.Device.ScreenCap()
if err == nil { if err == nil {
driverResult.ResponseBody = "OMITTED" driverResult.ResponseBody = "OMITTED"
return string(resp), nil 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") 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) driverResult.ResponseBody = strings.TrimSpace(output)
return output, err return output, err
} }
@@ -792,7 +789,7 @@ func (ad *ADBDriver) IsHealthy() (healthy bool, err error) {
func (ad *ADBDriver) StartCaptureLog(identifier ...string) (err error) { func (ad *ADBDriver) StartCaptureLog(identifier ...string) (err error) {
log.Info().Msg("start adb log recording") log.Info().Msg("start adb log recording")
// start logcat // start logcat
err = ad.logcat.CatchLogcat("iesqaMonitor:V") err = ad.Logcat.CatchLogcat("iesqaMonitor:V")
if err != nil { if err != nil {
err = errors.Wrap(code.DeviceCaptureLogError, err = errors.Wrap(code.DeviceCaptureLogError,
fmt.Sprintf("start adb log recording failed: %v", err)) 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) { func (ad *ADBDriver) StopCaptureLog() (result interface{}, err error) {
defer func() { defer func() {
log.Info().Msg("stop adb log recording") log.Info().Msg("stop adb log recording")
err = ad.logcat.Stop() err = ad.Logcat.Stop()
if err != nil { if err != nil {
log.Error().Err(err).Msg("failed to get adb log recording") 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 { if err != nil {
log.Error().Err(err).Msg("failed to close adb log writer") 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 { if len(pointRes) == 0 {
log.Info().Msg("action log is null, use action file >>>") log.Info().Msg("action log is null, use action file >>>")
logFilePathPrefix := fmt.Sprintf("%v/data", config.ActionLogFilePath) logFilePathPrefix := fmt.Sprintf("%v/data", config.ActionLogFilePath)
files := []string{} 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 { err = filepath.Walk(config.ActionLogFilePath, func(path string, info fs.FileInfo, err error) error {
// 只是需要日志文件 // 只是需要日志文件
if ok := strings.Contains(path, logFilePathPrefix); ok { if ok := strings.Contains(path, logFilePathPrefix); ok {
@@ -898,7 +895,7 @@ func (ad *ADBDriver) SetIme(imeRegx string) error {
if ime == "" { if ime == "" {
return fmt.Errorf("failed to set ime by %s, ime list: %v", imeRegx, imeList) 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] packageName := strings.Split(ime, "/")[0]
res, err := ad.runShellCommand("ime", "set", ime) res, err := ad.runShellCommand("ime", "set", ime)
log.Info().Str("funcName", "SetIme").Interface("ime", 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 // scrcpy -s 7d21bb91 --record=file.mp4 -N
cmd := exec.Command( cmd := exec.Command(
"scrcpy", "scrcpy",
"-s", ad.adbClient.Serial(), "-s", ad.Device.Serial(),
fmt.Sprintf("--record=%s", fileName), fmt.Sprintf("--record=%s", fileName),
"-N", "-N",
) )

View File

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

View File

@@ -6,11 +6,13 @@ import (
// current implemeted device: IOSDevice, AndroidDevice, HarmonyDevice // current implemeted device: IOSDevice, AndroidDevice, HarmonyDevice
type IDevice interface { type IDevice interface {
Init() error // init android device Setup() error
Teardown() error
UUID() string // ios udid or android serial UUID() string // ios udid or android serial
LogEnabled() bool LogEnabled() bool
// TODO: add ctx to NewDriver // TODO: remove
NewDriver(...option.DriverOption) (driverExt *DriverExt, err error) NewDriver(...option.DriverOption) (driverExt *DriverExt, err error)
Install(appPath string, opts ...option.InstallOption) error Install(appPath string, opts ...option.InstallOption) error
@@ -18,10 +20,4 @@ type IDevice interface {
GetPackageInfo(packageName string) (AppInfo, error) GetPackageInfo(packageName string) (AppInfo, error)
GetCurrentWindow() (windowInfo WindowInfo, err 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" "github.com/httprunner/httprunner/v5/pkg/uixt/option"
) )
// IWebDriver defines methods supported by IWebDriver drivers. // current implemeted driver: ADBDriver, UIA2Driver, WDADriver, HDCDriver
type IWebDriver interface { type IDriver interface {
// NewSession starts a new session and returns the SessionInfo. // NewSession starts a new session and returns the SessionInfo.
NewSession(capabilities option.Capabilities) (SessionInfo, error) NewSession(capabilities option.Capabilities) (SessionInfo, error)
@@ -238,7 +238,7 @@ type DriverResult struct {
type DriverClient struct { type DriverClient struct {
urlPrefix *url.URL urlPrefix *url.URL
client *http.Client Client *http.Client
// cache to avoid repeated query // cache to avoid repeated query
scale float64 scale float64
@@ -323,7 +323,7 @@ func (wd *DriverClient) Request(method string, rawURL string, rawBody []byte) (r
driverResult.RequestTime = time.Now() driverResult.RequestTime = time.Now()
var resp *http.Response 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 return nil, err
} }
defer func() { defer func() {
@@ -371,14 +371,14 @@ func convertToHTTPClient(conn net.Conn) *http.Client {
type DriverExt struct { type DriverExt struct {
Ctx context.Context Ctx context.Context
Device IDevice Device IDevice
Driver IWebDriver Driver IDriver
ImageService IImageService // used to extract image data ImageService IImageService // used to extract image data
// funplugin // funplugin
plugin funplugin.IPlugin 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...) options := option.NewDriverOptions(opts...)
dExt = &DriverExt{ dExt = &DriverExt{
@@ -404,7 +404,7 @@ func newDriverExt(device IDevice, driver IWebDriver, opts ...option.DriverOption
return dExt, nil return dExt, nil
} }
func (dExt *DriverExt) Init() error { func (dExt *DriverExt) Setup() error {
// unlock device screen // unlock device screen
err := dExt.Driver.Unlock() err := dExt.Driver.Unlock()
if err != nil { 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 { type HarmonyDevice struct {
*option.HarmonyDeviceConfig *ghdc.Device
d *ghdc.Device *option.HarmonyDeviceOptions
} }
func NewHarmonyDevice(opts ...option.HarmonyDeviceOption) (device *HarmonyDevice, err error) { func NewHarmonyDevice(opts ...option.HarmonyDeviceOption) (device *HarmonyDevice, err error) {
deviceConfig := option.NewHarmonyDeviceConfig(opts...) deviceConfig := option.NewHarmonyDeviceOptions(opts...)
deviceList, err := GetHarmonyDevices(deviceConfig.ConnectKey) deviceList, err := GetHarmonyDevices(deviceConfig.ConnectKey)
if err != nil { if err != nil {
return nil, errors.Wrap(code.DeviceConnectionError, err.Error()) return nil, errors.Wrap(code.DeviceConnectionError, err.Error())
@@ -43,8 +42,8 @@ func NewHarmonyDevice(opts ...option.HarmonyDeviceOption) (device *HarmonyDevice
} }
device = &HarmonyDevice{ device = &HarmonyDevice{
HarmonyDeviceConfig: deviceConfig, HarmonyDeviceOptions: deviceConfig,
d: dev, Device: dev,
} }
log.Info().Str("connectKey", device.ConnectKey).Msg("init harmony device") log.Info().Str("connectKey", device.ConnectKey).Msg("init harmony device")
return device, nil return device, nil
@@ -83,7 +82,11 @@ func GetHarmonyDevices(serial ...string) (devices []*ghdc.Device, err error) {
return devices, nil return devices, nil
} }
func (dev *HarmonyDevice) Init() error { func (dev *HarmonyDevice) Setup() error {
return nil
}
func (dev *HarmonyDevice) Teardown() error {
return nil return nil
} }
@@ -96,7 +99,7 @@ func (dev *HarmonyDevice) LogEnabled() bool {
} }
func (dev *HarmonyDevice) NewDriver(opts ...option.DriverOption) (driverExt *DriverExt, err error) { func (dev *HarmonyDevice) NewDriver(opts ...option.DriverOption) (driverExt *DriverExt, err error) {
driver, err := newHarmonyDriver(dev.d) driver, err := newHarmonyDriver(dev.Device)
if err != nil { if err != nil {
log.Error().Err(err).Msg("failed to new harmony driver") log.Error().Err(err).Msg("failed to new harmony driver")
return nil, err return nil, err
@@ -110,8 +113,8 @@ func (dev *HarmonyDevice) NewDriver(opts ...option.DriverOption) (driverExt *Dri
return driverExt, nil return driverExt, nil
} }
func (dev *HarmonyDevice) NewUSBDriver(opts ...option.DriverOption) (driver IWebDriver, err error) { func (dev *HarmonyDevice) NewUSBDriver(opts ...option.DriverOption) (driver IDriver, err error) {
harmonyDriver, err := newHarmonyDriver(dev.d) harmonyDriver, err := newHarmonyDriver(dev.Device)
if err != nil { if err != nil {
log.Error().Err(err).Msg("failed to new harmony driver") log.Error().Err(err).Msg("failed to new harmony driver")
return nil, err return nil, err

View File

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

View File

@@ -31,28 +31,6 @@ import (
"github.com/httprunner/httprunner/v5/pkg/uixt/option" "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 var tunnelManager *tunnel.TunnelManager = nil
func GetIOSDevices(udid ...string) (deviceList []ios.DeviceEntry, err error) { 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) { func NewIOSDevice(opts ...option.IOSDeviceOption) (device *IOSDevice, err error) {
deviceOptions := &option.IOSDeviceConfig{ deviceOptions := option.NewIOSDeviceOptions(opts...)
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)
}
deviceList, err := GetIOSDevices(deviceOptions.UDID) deviceList, err := GetIOSDevices(deviceOptions.UDID)
if err != nil { if err != nil {
return nil, errors.Wrap(code.DeviceConnectionError, err.Error()) return nil, errors.Wrap(code.DeviceConnectionError, err.Error())
@@ -160,12 +125,12 @@ func NewIOSDevice(opts ...option.IOSDeviceOption) (device *IOSDevice, err error)
} }
device = &IOSDevice{ device = &IOSDevice{
IOSDeviceConfig: deviceOptions, IOSDeviceOptions: deviceOptions,
listeners: make(map[int]*forward.ConnListener), listeners: make(map[int]*forward.ConnListener),
d: dev, d: dev,
} }
log.Info().Str("udid", device.UDID).Msg("init ios device") log.Info().Str("udid", device.UDID).Msg("init ios device")
err = device.Init() err = device.Setup()
if err != nil { if err != nil {
_ = device.Teardown() _ = device.Teardown()
return nil, err return nil, err
@@ -174,7 +139,7 @@ func NewIOSDevice(opts ...option.IOSDeviceOption) (device *IOSDevice, err error)
} }
type IOSDevice struct { type IOSDevice struct {
*option.IOSDeviceConfig *option.IOSDeviceOptions
d ios.DeviceEntry d ios.DeviceEntry
listeners map[int]*forward.ConnListener listeners map[int]*forward.ConnListener
} }
@@ -207,7 +172,7 @@ const (
ApplicationTypeAny ApplicationType = "Any" ApplicationTypeAny ApplicationType = "Any"
) )
func (dev *IOSDevice) Init() error { func (dev *IOSDevice) Setup() error {
images, err := dev.ListImages() images, err := dev.ListImages()
if err != nil { if err != nil {
return err return err
@@ -270,7 +235,7 @@ func (dev *IOSDevice) NewDriver(opts ...option.DriverOption) (driverExt *DriverE
capabilities.WithDefaultAlertAction(option.AlertActionAccept) capabilities.WithDefaultAlertAction(option.AlertActionAccept)
} }
var driver IWebDriver var driver IDriver
if dev.STUB { if dev.STUB {
driver, err = dev.NewStubDriver() driver, err = dev.NewStubDriver()
if err != nil { 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. // 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 var localPort int
localPort, err = strconv.Atoi(os.Getenv("WDA_LOCAL_PORT")) localPort, err = strconv.Atoi(os.Getenv("WDA_LOCAL_PORT"))
if err != nil { if err != nil {
@@ -570,7 +535,7 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities option.Capabilities) (driver IW
return nil, errors.Wrap(code.DeviceHTTPDriverError, return nil, errors.Wrap(code.DeviceHTTPDriverError,
fmt.Sprintf("get free port failed: %v", err)) 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, return nil, errors.Wrap(code.DeviceHTTPDriverError,
fmt.Sprintf("forward tcp port failed: %v", err)) 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, return nil, errors.Wrap(code.DeviceHTTPDriverError,
fmt.Sprintf("get free port failed: %v", err)) 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, return nil, errors.Wrap(code.DeviceHTTPDriverError,
fmt.Sprintf("forward tcp port failed: %v", err)) 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") Msg("init WDA HTTP driver")
wd := new(wdaDriver) wd := new(wdaDriver)
wd.device = dev wd.IOSDevice = dev
wd.udid = dev.UDID wd.udid = dev.UDID
wd.client = &http.Client{ wd.Client = &http.Client{
Timeout: time.Second * 10, // 设置超时时间为 10 秒 Timeout: time.Second * 10, // 设置超时时间为 10 秒
} }
@@ -634,7 +599,12 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities option.Capabilities) (driver IW
return wd, nil 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() localStubPort, err := builtin.GetFreePort()
if err != nil { if err != nil {
return nil, errors.Wrap(code.DeviceHTTPDriverError, return nil, errors.Wrap(code.DeviceHTTPDriverError,

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,18 +1,31 @@
package option package option
type AndroidDeviceConfig struct { import "github.com/httprunner/httprunner/v5/pkg/gadb"
type AndroidDeviceOptions struct {
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"` SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
STUB bool `json:"stub,omitempty" yaml:"stub,omitempty"` // use stub 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
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"` 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 != "" { if dev.SerialNumber != "" {
deviceOptions = append(deviceOptions, WithSerialNumber(dev.SerialNumber)) deviceOptions = append(deviceOptions, WithSerialNumber(dev.SerialNumber))
} }
if dev.STUB {
deviceOptions = append(deviceOptions, WithStub(true))
}
if dev.UIA2 { if dev.UIA2 {
deviceOptions = append(deviceOptions, WithUIA2(true)) deviceOptions = append(deviceOptions, WithUIA2(true))
} }
@@ -28,54 +41,87 @@ func (dev *AndroidDeviceConfig) Options() (deviceOptions []AndroidDeviceOption)
return return
} }
func NewAndroidDeviceConfig(opts ...AndroidDeviceOption) *AndroidDeviceConfig { const (
config := &AndroidDeviceConfig{} // 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 { for _, opt := range opts {
opt(config) 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 return config
} }
type AndroidDeviceOption func(*AndroidDeviceConfig) type AndroidDeviceOption func(*AndroidDeviceOptions)
func WithDriverTypeADB() AndroidDeviceOption { func WithDriverTypeADB() AndroidDeviceOption {
return func(device *AndroidDeviceConfig) { return func(device *AndroidDeviceOptions) {
device.STUB = false device.STUB = false
} }
} }
func WithSerialNumber(serial string) AndroidDeviceOption { func WithSerialNumber(serial string) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) { return func(device *AndroidDeviceOptions) {
device.SerialNumber = serial device.SerialNumber = serial
} }
} }
func WithUIA2(uia2On bool) AndroidDeviceOption { func WithUIA2(uia2On bool) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) { return func(device *AndroidDeviceOptions) {
device.UIA2 = uia2On device.UIA2 = uia2On
} }
} }
func WithStub(stubOn bool) AndroidDeviceOption { func WithStub(stubOn bool) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) { return func(device *AndroidDeviceOptions) {
device.STUB = stubOn device.STUB = stubOn
} }
} }
func WithUIA2IP(ip string) AndroidDeviceOption { func WithUIA2IP(ip string) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) { return func(device *AndroidDeviceOptions) {
device.UIA2IP = ip device.UIA2IP = ip
} }
} }
func WithUIA2Port(port int) AndroidDeviceOption { func WithUIA2Port(port int) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) { return func(device *AndroidDeviceOptions) {
device.UIA2Port = port device.UIA2Port = port
} }
} }
func WithAdbLogOn(logOn bool) AndroidDeviceOption { func WithAdbLogOn(logOn bool) AndroidDeviceOption {
return func(device *AndroidDeviceConfig) { return func(device *AndroidDeviceOptions) {
device.LogOn = logOn device.LogOn = logOn
} }
} }

View File

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

View File

@@ -1,11 +1,11 @@
package option package option
type IOSDeviceConfig struct { type IOSDeviceOptions struct {
UDID string `json:"udid,omitempty" yaml:"udid,omitempty"` UDID string `json:"udid,omitempty" yaml:"udid,omitempty"`
Port int `json:"port,omitempty" yaml:"port,omitempty"` // WDA remote port WDAPort int `json:"port,omitempty" yaml:"port,omitempty"` // WDA remote port
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG 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 STUB bool `json:"stub,omitempty" yaml:"stub,omitempty"` // use stub
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"` LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
// switch to iOS springboard before init WDA session // switch to iOS springboard before init WDA session
ResetHomeOnStartup bool `json:"reset_home_on_startup,omitempty" yaml:"reset_home_on_startup,omitempty"` 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"` 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 != "" { if dev.UDID != "" {
deviceOptions = append(deviceOptions, WithUDID(dev.UDID)) deviceOptions = append(deviceOptions, WithUDID(dev.UDID))
} }
if dev.Port != 0 { if dev.WDAPort != 0 {
deviceOptions = append(deviceOptions, WithWDAPort(dev.Port)) deviceOptions = append(deviceOptions, WithWDAPort(dev.WDAPort))
} }
if dev.MjpegPort != 0 { if dev.WDAMjpegPort != 0 {
deviceOptions = append(deviceOptions, WithWDAMjpegPort(dev.MjpegPort)) deviceOptions = append(deviceOptions, WithWDAMjpegPort(dev.WDAMjpegPort))
} }
if dev.STUB { if dev.STUB {
deviceOptions = append(deviceOptions, WithIOSStub(true)) deviceOptions = append(deviceOptions, WithIOSStub(true))
@@ -47,66 +47,108 @@ func (dev *IOSDeviceConfig) Options() (deviceOptions []IOSDeviceOption) {
return return
} }
func NewIOSDeviceConfig(opts ...IOSDeviceOption) *IOSDeviceConfig { const (
config := &IOSDeviceConfig{} 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 { for _, opt := range opts {
opt(config) 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 return config
} }
type IOSDeviceOption func(*IOSDeviceConfig) type IOSDeviceOption func(*IOSDeviceOptions)
func WithUDID(udid string) IOSDeviceOption { func WithUDID(udid string) IOSDeviceOption {
return func(device *IOSDeviceConfig) { return func(device *IOSDeviceOptions) {
device.UDID = udid device.UDID = udid
} }
} }
func WithWDAPort(port int) IOSDeviceOption { func WithWDAPort(port int) IOSDeviceOption {
return func(device *IOSDeviceConfig) { return func(device *IOSDeviceOptions) {
device.Port = port device.WDAPort = port
} }
} }
func WithWDAMjpegPort(port int) IOSDeviceOption { func WithWDAMjpegPort(port int) IOSDeviceOption {
return func(device *IOSDeviceConfig) { return func(device *IOSDeviceOptions) {
device.MjpegPort = port device.WDAMjpegPort = port
} }
} }
func WithWDALogOn(logOn bool) IOSDeviceOption { func WithWDALogOn(logOn bool) IOSDeviceOption {
return func(device *IOSDeviceConfig) { return func(device *IOSDeviceOptions) {
device.LogOn = logOn device.LogOn = logOn
} }
} }
func WithIOSStub(stub bool) IOSDeviceOption { func WithIOSStub(stub bool) IOSDeviceOption {
return func(device *IOSDeviceConfig) { return func(device *IOSDeviceOptions) {
device.STUB = stub device.STUB = stub
} }
} }
func WithResetHomeOnStartup(reset bool) IOSDeviceOption { func WithResetHomeOnStartup(reset bool) IOSDeviceOption {
return func(device *IOSDeviceConfig) { return func(device *IOSDeviceOptions) {
device.ResetHomeOnStartup = reset device.ResetHomeOnStartup = reset
} }
} }
func WithSnapshotMaxDepth(depth int) IOSDeviceOption { func WithSnapshotMaxDepth(depth int) IOSDeviceOption {
return func(device *IOSDeviceConfig) { return func(device *IOSDeviceOptions) {
device.SnapshotMaxDepth = depth device.SnapshotMaxDepth = depth
} }
} }
func WithAcceptAlertButtonSelector(selector string) IOSDeviceOption { func WithAcceptAlertButtonSelector(selector string) IOSDeviceOption {
return func(device *IOSDeviceConfig) { return func(device *IOSDeviceOptions) {
device.AcceptAlertButtonSelector = selector device.AcceptAlertButtonSelector = selector
} }
} }
func WithDismissAlertButtonSelector(selector string) IOSDeviceOption { func WithDismissAlertButtonSelector(selector string) IOSDeviceOption {
return func(device *IOSDeviceConfig) { return func(device *IOSDeviceOptions) {
device.DismissAlertButtonSelector = selector device.DismissAlertButtonSelector = selector
} }
} }

View File

@@ -1,6 +1,9 @@
package uixt package uixt
import "math" import (
"fmt"
"math"
)
type DeviceStatus struct { type DeviceStatus struct {
Message string `json:"message"` 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 { type Size struct {
Width int `json:"width"` Width int `json:"width"`
Height int `json:"height"` Height int `json:"height"`
@@ -233,7 +264,7 @@ type Rotation struct {
Z int `json:"z"` Z int `json:"z"`
} }
type Condition func(wd IWebDriver) (bool, error) type Condition func(wd IDriver) (bool, error)
type Direction string type Direction string

View File

@@ -427,14 +427,14 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "init android device failed") return nil, errors.Wrap(err, "init android device failed")
} }
if err := device.Init(); err != nil { if err := device.Setup(); err != nil {
return nil, err return nil, err
} }
driver, err := device.NewDriver() driver, err := device.NewDriver()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := driver.Init(); err != nil { if err := driver.Setup(); err != nil {
return nil, err return nil, err
} }
r.uixtDrivers[device.SerialNumber] = driver r.uixtDrivers[device.SerialNumber] = driver
@@ -450,14 +450,14 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "init ios device failed") return nil, errors.Wrap(err, "init ios device failed")
} }
if err := device.Init(); err != nil { if err := device.Setup(); err != nil {
return nil, err return nil, err
} }
driver, err := device.NewDriver() driver, err := device.NewDriver()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := driver.Init(); err != nil { if err := driver.Setup(); err != nil {
return nil, err return nil, err
} }
r.uixtDrivers[device.UDID] = driver r.uixtDrivers[device.UDID] = driver
@@ -473,14 +473,14 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "init harmony device failed") return nil, errors.Wrap(err, "init harmony device failed")
} }
if err := device.Init(); err != nil { if err := device.Setup(); err != nil {
return nil, err return nil, err
} }
driver, err := device.NewDriver() driver, err := device.NewDriver()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := driver.Init(); err != nil { if err := driver.Setup(); err != nil {
return nil, err return nil, err
} }
r.uixtDrivers[device.ConnectKey] = driver r.uixtDrivers[device.ConnectKey] = driver

View File

@@ -52,7 +52,7 @@ func handleDeviceContext() gin.HandlerFunc {
c.Abort() c.Abort()
return return
} }
device.Init() device.Setup()
driver, err := device.NewDriver( driver, err := device.NewDriver(
option.WithDriverImageService(true), option.WithDriverImageService(true),

View File

@@ -760,7 +760,7 @@ func (s *StepRequest) MobileUI() *StepMobile {
// Android creates a new android step session // Android creates a new android step session
func (s *StepRequest) Android(opts ...option.AndroidDeviceOption) *StepMobile { func (s *StepRequest) Android(opts ...option.AndroidDeviceOption) *StepMobile {
androidOptions := option.NewAndroidDeviceConfig(opts...) androidOptions := option.NewAndroidDeviceOptions(opts...)
return &StepMobile{ return &StepMobile{
StepConfig: s.StepConfig, StepConfig: s.StepConfig,
Android: &MobileUI{ Android: &MobileUI{
@@ -771,7 +771,7 @@ func (s *StepRequest) Android(opts ...option.AndroidDeviceOption) *StepMobile {
// IOS creates a new ios step session // IOS creates a new ios step session
func (s *StepRequest) IOS(opts ...option.IOSDeviceOption) *StepMobile { func (s *StepRequest) IOS(opts ...option.IOSDeviceOption) *StepMobile {
iosOptions := option.NewIOSDeviceConfig(opts...) iosOptions := option.NewIOSDeviceOptions(opts...)
return &StepMobile{ return &StepMobile{
StepConfig: s.StepConfig, StepConfig: s.StepConfig,
IOS: &MobileUI{ IOS: &MobileUI{
@@ -782,7 +782,7 @@ func (s *StepRequest) IOS(opts ...option.IOSDeviceOption) *StepMobile {
// Harmony creates a new harmony step session // Harmony creates a new harmony step session
func (s *StepRequest) Harmony(opts ...option.HarmonyDeviceOption) *StepMobile { func (s *StepRequest) Harmony(opts ...option.HarmonyDeviceOption) *StepMobile {
harmonyOptions := option.NewHarmonyDeviceConfig(opts...) harmonyOptions := option.NewHarmonyDeviceOptions(opts...)
return &StepMobile{ return &StepMobile{
StepConfig: s.StepConfig, StepConfig: s.StepConfig,
Harmony: &MobileUI{ Harmony: &MobileUI{