diff --git a/hrp/code/code.go b/hrp/code/code.go index e6301196..da2619da 100644 --- a/hrp/code/code.go +++ b/hrp/code/code.go @@ -67,6 +67,7 @@ var ( DeviceHTTPDriverError = errors.New("device HTTP driver error") // 51 DeviceUSBDriverError = errors.New("device USB driver error") // 52 DeviceGetInfoError = errors.New("device get info error") // 60 + DeviceConfigureError = errors.New("device configure error") // 61 DeviceShellExecError = errors.New("device shell exec error") // 62 DeviceOfflineError = errors.New("device offline") // 63 DeviceScreenShotError = errors.New("device screenshot error") // 65 @@ -76,6 +77,8 @@ var ( // UI automation related: [70, 80) var ( + MobileUIDriverAppNotInstalled = errors.New("mobile UI driver app not installed") // 68 + MobileUIDriverAppCrashed = errors.New("mobile UI driver app crashed") // 69 MobileUIDriverError = errors.New("mobile UI driver error") // 70 MobileUILaunchAppError = errors.New("mobile UI launch app error") // 71 MobileUITapError = errors.New("mobile UI tap error") // 72 @@ -96,7 +99,7 @@ var ( CVResponseError = errors.New("CV parse response error") // 83 CVResultNotFoundError = errors.New("CV result not found") // 84 - ContextUnknowError = errors.New("detect context failed") // 85 + StateUnknowError = errors.New("detect state failed") // 85 ) // trackings related: [90, 100) @@ -159,6 +162,7 @@ var errorsMap = map[error]int{ DeviceHTTPDriverError: 51, DeviceUSBDriverError: 52, DeviceGetInfoError: 60, + DeviceConfigureError: 61, DeviceShellExecError: 62, DeviceOfflineError: 63, DeviceScreenShotError: 65, @@ -166,6 +170,8 @@ var errorsMap = map[error]int{ DeviceUIResponseSlow: 67, // UI automation related + MobileUIDriverAppNotInstalled: 68, + MobileUIDriverAppCrashed: 69, MobileUIDriverError: 70, MobileUILaunchAppError: 71, MobileUITapError: 72, @@ -183,7 +189,7 @@ var errorsMap = map[error]int{ CVServiceConnectionError: 82, CVResponseError: 83, CVResultNotFoundError: 84, - ContextUnknowError: 85, + StateUnknowError: 85, // trackings related TrackingGetError: 90, diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index b3301444..a64d3cae 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.6.10 \ No newline at end of file +v5.0.0-beta-2410112229 \ No newline at end of file diff --git a/hrp/pkg/gadb/device.go b/hrp/pkg/gadb/device.go index 76d9371e..4d743214 100644 --- a/hrp/pkg/gadb/device.go +++ b/hrp/pkg/gadb/device.go @@ -675,7 +675,7 @@ func (d *Device) ListPackages() ([]string, error) { return packages, nil } -func (d *Device) IsPackagesInstalled(packageName string) bool { +func (d *Device) IsPackageInstalled(packageName string) bool { packages, err := d.ListPackages() if err != nil { return false @@ -687,6 +687,14 @@ func (d *Device) IsPackagesInstalled(packageName string) bool { return builtin.Contains(packages, packageName) } +func (d *Device) IsPackageRunning(packageName string) bool { + output, err := d.RunShellCommand("pidof", packageName) + if err != nil { + return false + } + return strings.TrimSpace(output) != "" +} + func (d *Device) ScreenCap() ([]byte, error) { if d.HasFeature(FeatShellV2) { return d.RunShellCommandV2WithBytes("screencap", "-p") diff --git a/hrp/pkg/gadb/device_test.go b/hrp/pkg/gadb/device_test.go index 9692f799..a8038e29 100644 --- a/hrp/pkg/gadb/device_test.go +++ b/hrp/pkg/gadb/device_test.go @@ -349,7 +349,7 @@ func TestDevice_ListPackages(t *testing.T) { t.Fatal(err) } t.Log(res) - installed := dev.IsPackagesInstalled("io.appium.uiautomator2.server") + installed := dev.IsPackageInstalled("io.appium.uiautomator2.server") if err != nil { t.Fatal(err) } diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 22b41b64..e280bc3e 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -29,11 +29,18 @@ import ( ) var ( - DouyinServerPort = 32316 - AdbServerHost = "localhost" - AdbServerPort = gadb.AdbServerPort // 5037 - UIA2ServerHost = "localhost" - UIA2ServerPort = 6790 + DouyinServerPort = 32316 + + // 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" ) @@ -100,8 +107,6 @@ func GetAndroidDeviceOptions(dev *AndroidDevice) (deviceOptions []AndroidDeviceO return } -// uiautomator2 server must be started before -// adb shell am instrument -w io.appium.uiautomator2.server.test/androidx.test.runner.AndroidJUnitRunner func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, err error) { device = &AndroidDevice{ UIA2IP: UIA2ServerHost, @@ -191,6 +196,33 @@ type AndroidDevice struct { func (dev *AndroidDevice) Init() error { dev.d.RunShellCommand("ime", "enable", "io.appium.settings/.UnicodeIME") dev.d.RunShellCommand("rm", "-r", env.DeviceActionLogFilePath) + + if dev.UIA2 { + // uiautomator2 server must be started before + + // check uiautomator server package installed + if !dev.d.IsPackageInstalled(UIA2ServerPackageName) { + return errors.Wrapf(code.MobileUIDriverAppNotInstalled, + "%s not installed", UIA2ServerPackageName) + } + if !dev.d.IsPackageInstalled(UIA2ServerTestPackageName) { + return errors.Wrapf(code.MobileUIDriverAppNotInstalled, + "%s not installed", UIA2ServerTestPackageName) + } + + // TODO: check uiautomator server package running + // if dev.d.IsPackageRunning(UIA2ServerPackageName) { + // return nil + // } + + // start uiautomator2 server + go func() { + if err := dev.startUIA2Server(); err != nil { + log.Error().Err(err).Msg("start UIA2 failed") + } + }() + time.Sleep(5 * time.Second) // wait for uiautomator2 server start + } return nil } @@ -237,15 +269,15 @@ func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverE // NewUSBDriver creates new client via USB connected device, this will also start a new session. func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver IWebDriver, err error) { - localPort, err := dev.d.Forward(UIA2ServerPort) + localPort, err := dev.d.Forward(dev.UIA2Port) if err != nil { return nil, errors.Wrap(code.DeviceConnectionError, fmt.Sprintf("forward port %d->%d failed: %v", - localPort, UIA2ServerPort, err)) + localPort, dev.UIA2Port, err)) } rawURL := fmt.Sprintf("http://%s%d:%d/wd/hub", - forwardToPrefix, localPort, UIA2ServerPort) + forwardToPrefix, localPort, dev.UIA2Port) uiaDriver, err := NewUIADriver(capabilities, rawURL) if err != nil { _ = dev.d.ForwardKill(localPort) @@ -356,7 +388,7 @@ func (dev *AndroidDevice) Install(appPath string, opts *InstallOptions) error { case "vivo": return dev.installVivoSilent(app, args...) case "oppo", "realme", "oneplus": - if dev.d.IsPackagesInstalled(EvalInstallerPackageName) { + if dev.d.IsPackageInstalled(EvalInstallerPackageName) { return dev.installViaInstaller(app, args...) } log.Warn().Msg("oppo not install eval installer") @@ -511,6 +543,32 @@ func (dev *AndroidDevice) getPackageMD5(packagePath string) (string, error) { return "", errors.New("failed to get package md5") } +func (dev *AndroidDevice) startUIA2Server() error { + const maxRetries = 3 + for attempt := 1; attempt <= maxRetries; attempt++ { + log.Info().Str("package", 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) + if err != nil { + return errors.Wrap(err, "start uiautomator server failed") + } + if strings.Contains(out, "Process crashed") { + log.Error().Msg("uiautomator server crashed, retrying...") + } + } + + return errors.Wrapf(code.MobileUIDriverAppCrashed, + "uiautomator server crashed %d times", maxRetries) +} + +func (dev *AndroidDevice) stopUIA2Server() error { + _, err := dev.d.RunShellCommand("am", "force-stop", UIA2ServerPackageName) + return err +} + type LineCallback func(string) type AdbLogcat struct { diff --git a/hrp/pkg/uixt/android_test.go b/hrp/pkg/uixt/android_test.go index 8e6f4aa2..930b1d66 100644 --- a/hrp/pkg/uixt/android_test.go +++ b/hrp/pkg/uixt/android_test.go @@ -384,6 +384,7 @@ func TestDriver_AppLaunch(t *testing.T) { func TestDriver_IsAppInForeground(t *testing.T) { setupAndroidUIA2Driver(t) + // setupAndroidAdbDriver(t) err := driverExt.Driver.AppLaunch("com.android.settings") checkErr(t, err) diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index cae5a85a..3d2ebbf2 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -1,6 +1,7 @@ package uixt import ( + "context" _ "image/gif" _ "image/png" @@ -13,6 +14,7 @@ import ( ) type DriverExt struct { + Ctx context.Context Device IDevice Driver IWebDriver ImageService IImageService // used to extract image data diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index 2b2ab451..adc4c578 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -481,6 +481,8 @@ type IDevice interface { Init() error // init android device UUID() string // ios udid or android serial LogEnabled() bool + + // TODO: add ctx to NewDriver NewDriver(...DriverOption) (driverExt *DriverExt, err error) StartPerf() error