merge master

This commit is contained in:
lilong.129
2024-08-27 20:14:33 +08:00
34 changed files with 2027 additions and 154 deletions

View File

@@ -17,6 +17,7 @@ type ActionMethod string
const (
ACTION_AppInstall ActionMethod = "install"
ACTION_AppUninstall ActionMethod = "uninstall"
ACTION_AppClear ActionMethod = "app_clear"
ACTION_AppStart ActionMethod = "app_start"
ACTION_AppLaunch ActionMethod = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成
ACTION_AppTerminate ActionMethod = "app_terminate"
@@ -26,6 +27,10 @@ const (
ACTION_SleepRandom ActionMethod = "sleep_random"
ACTION_StartCamera ActionMethod = "camera_start" // alias for app_launch camera
ACTION_StopCamera ActionMethod = "camera_stop" // alias for app_terminate camera
ACTION_SetClipboard ActionMethod = "set_clipboard"
ACTION_GetClipboard ActionMethod = "get_clipboard"
ACTION_SetIme ActionMethod = "set_ime"
ACTION_GetSource ActionMethod = "get_source"
// UI validation
// selectors
@@ -60,6 +65,9 @@ const (
ACTION_VideoCrawler ActionMethod = "video_crawler"
ACTION_ClosePopups ActionMethod = "close_popups"
ACTION_EndToEndDelay ActionMethod = "live_e2e"
ACTION_InstallApp ActionMethod = "install_app"
ACTION_UninstallApp ActionMethod = "uninstall_app"
ACTION_DownloadApp ActionMethod = "download_app"
)
type MobileAction struct {
@@ -554,8 +562,23 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
switch action.Method {
case ACTION_AppInstall:
// TODO
return errActionNotImplemented
if appUrl, ok := action.Params.(string); ok {
if err = dExt.InstallByUrl(appUrl, NewInstallOptions(WithRetryTime(action.MaxRetryTimes))); err != nil {
return errors.Wrap(err, "failed to install app")
}
}
case ACTION_AppUninstall:
if packageName, ok := action.Params.(string); ok {
if err = dExt.Uninstall(packageName, action.GetOptions()...); err != nil {
return errors.Wrap(err, "failed to uninstall app")
}
}
case ACTION_AppClear:
if packageName, ok := action.Params.(string); ok {
if err = dExt.Driver.Clear(packageName); err != nil {
return errors.Wrap(err, "failed to clear app")
}
}
case ACTION_AppLaunch:
if bundleId, ok := action.Params.(string); ok {
return dExt.Driver.AppLaunch(bundleId)
@@ -594,8 +617,34 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
return nil
}
return fmt.Errorf("app_terminate params should be bundleId(string), got %v", action.Params)
case ACTION_SetClipboard:
if text, ok := action.Params.(string); ok {
err := dExt.Driver.SetPasteboard(PasteboardTypePlaintext, text)
if err != nil {
return errors.Wrap(err, "failed to set clipboard")
}
return nil
}
return fmt.Errorf("set_clioboard params should be text(string), got %v", action.Params)
case ACTION_Home:
return dExt.Driver.Homescreen()
case ACTION_SetIme:
if ime, ok := action.Params.(string); ok {
err = dExt.Driver.SetIme(ime)
if err != nil {
return errors.Wrap(err, "failed to set ime")
}
return nil
}
case ACTION_GetSource:
if packageName, ok := action.Params.(string); ok {
source := NewSourceOption().WithProcessName(packageName)
_, err = dExt.Driver.Source(source)
if err != nil {
return errors.Wrap(err, "failed to set ime")
}
return nil
}
case ACTION_TapXY:
if location, ok := action.Params.([]interface{}); ok {
// relative x,y of window size: [0.5, 0.5]

View File

@@ -3,6 +3,7 @@ package uixt
import (
"bufio"
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io/fs"
@@ -218,10 +219,18 @@ func (ad *adbDriver) Orientation() (orientation Orientation, err error) {
}
func (ad *adbDriver) Homescreen() (err error) {
return ad.PressKeyCode(KCHome, KMEmpty)
return ad.PressKeyCodes(KCHome, KMEmpty)
}
func (ad *adbDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta) (err error) {
func (ad *adbDriver) Unlock() (err error) {
return ad.PressKeyCodes(KCMenu, KMEmpty)
}
func (ad *adbDriver) PressKeyCode(keyCode KeyCode) (err error) {
return ad.PressKeyCodes(keyCode, KMEmpty)
}
func (ad *adbDriver) PressKeyCodes(keyCode KeyCode, metaState KeyMeta) (err error) {
// adb shell input keyevent <keyCode>
_, err = ad.adbClient.RunShellCommand(
"input", "keyevent", fmt.Sprintf("%d", keyCode))
@@ -421,6 +430,14 @@ func (ad *adbDriver) IsUnicodeIMEInstalled() bool {
return strings.Contains(output, UnicodeImePackageName)
}
func (ad *adbDriver) ListIme() []string {
output, err := ad.adbClient.RunShellCommand("ime", "list", "-s")
if err != nil {
return []string{}
}
return strings.Split(output, "\n")
}
func (ad *adbDriver) SendKeysByAdbKeyBoard(text string) (err error) {
defer func() {
// Reset to default, don't care which keyboard was chosen before switch:
@@ -458,6 +475,15 @@ func (ad *adbDriver) Input(text string, options ...ActionOption) (err error) {
return ad.SendKeys(text, options...)
}
func (ad *adbDriver) Clear(packageName string) error {
if _, err := ad.adbClient.RunShellCommand("pm", "clear", packageName); err != nil {
log.Error().Str("packageName", packageName).Err(err).Msg("failed to clear package cache")
return err
}
return nil
}
func (ad *adbDriver) PressButton(devBtn DeviceButton) (err error) {
err = errDriverNotImplemented
return
@@ -499,6 +525,14 @@ func (ad *adbDriver) Source(srcOpt ...SourceOption) (source string, err error) {
return
}
func (ad *adbDriver) LoginNoneUI(packageName, phoneNumber string, captcha string) error {
return errDriverNotImplemented
}
func (ad *adbDriver) LogoutNoneUI(packageName string) error {
return errDriverNotImplemented
}
func (ad *adbDriver) sourceTree(srcOpt ...SourceOption) (sourceTree *Hierarchy, err error) {
source, err := ad.Source()
if err != nil {
@@ -680,46 +714,57 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) {
return pointRes, nil
}
func (ad *adbDriver) GetDriverResults() []*DriverResult {
return nil
}
func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) {
// adb shell dumpsys activity activities
output, err := ad.adbClient.RunShellCommand("dumpsys", "activity", "activities")
packageInfo, err := ad.adbClient.RunShellCommand("CLASSPATH=/data/local/tmp/evalite", "app_process", "/", "com.bytedance.iesqa.eval_process.PackageService")
if err != nil {
log.Error().Err(err).Msg("failed to dumpsys activities")
return AppInfo{}, errors.Wrap(err, "dumpsys activities failed")
return app, err
}
log.Info().Msg(packageInfo)
err = json.Unmarshal([]byte(strings.TrimSpace(packageInfo)), &app)
return
}
func (ad *adbDriver) SetIme(imeRegx string) error {
imeList := ad.ListIme()
ime := ""
for _, imeName := range imeList {
if regexp.MustCompile(imeRegx).MatchString(imeName) {
ime = imeName
break
}
}
if ime == "" {
return fmt.Errorf("failed to set ime by %s, ime list: %v", imeRegx, imeList)
}
brand, _ := ad.adbClient.Brand()
packageName := strings.Split(ime, "/")[0]
res, err := ad.adbClient.RunShellCommand("ime", "set", ime)
log.Info().Str("funcName", "SetIme").Interface("ime", ime).
Interface("output", res).Msg("set ime")
if err != nil {
return err
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
trimmedLine := strings.TrimSpace(line)
// grep mResumedActivity|ResumedActivity
if strings.HasPrefix(trimmedLine, "mResumedActivity:") || strings.HasPrefix(trimmedLine, "ResumedActivity:") {
// mResumedActivity: ActivityRecord{9656d74 u0 com.android.settings/.Settings t407}
// ResumedActivity: ActivityRecord{8265c25 u0 com.android.settings/.Settings t73}
strs := strings.Split(trimmedLine, " ")
for _, str := range strs {
if strings.Contains(str, "/") {
// com.android.settings/.Settings
s := strings.Split(str, "/")
app := AppInfo{
AppBaseInfo: AppBaseInfo{
PackageName: s[0],
Activity: s[1],
},
}
return app, nil
if strings.ToLower(brand) == "oppo" {
time.Sleep(1 * time.Second)
pid, _ := ad.adbClient.RunShellCommand("pidof", packageName)
if strings.TrimSpace(pid) == "" {
appInfo, err := ad.GetForegroundApp()
_ = ad.AppLaunch(packageName)
if err == nil && packageName != UnicodeImePackageName {
time.Sleep(10 * time.Second)
nextAppInfo, err := ad.GetForegroundApp()
log.Info().Str("beforeFocusedPackage", appInfo.PackageName).Str("afterFocusedPackage", nextAppInfo.PackageName).Msg("")
if err == nil && nextAppInfo.PackageName != appInfo.PackageName {
_ = ad.PressKeyCodes(KCBack, KMEmpty)
}
}
}
}
return AppInfo{}, errors.Wrap(code.MobileUIAssertForegroundAppError, "get foreground app failed")
}
func (ad *adbDriver) SetIme(ime string) error {
_, err := ad.adbClient.RunShellCommand("ime", "set", ime)
if err != nil {
return err
}
// even if the shell command has returned,
// as there might be a situation where the input method has not been completely switched yet
// Listen to the following message.

View File

@@ -4,26 +4,43 @@ import (
"bufio"
"bytes"
"context"
"crypto/md5"
"embed"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
"github.com/httprunner/funplugin/myexec"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/code"
"github.com/httprunner/httprunner/v4/hrp/internal/env"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
)
var (
AdbServerHost = "localhost"
AdbServerPort = gadb.AdbServerPort // 5037
UIA2ServerHost = "localhost"
UIA2ServerPort = 6790
DouyinServerPort = 32316
AdbServerHost = "localhost"
AdbServerPort = gadb.AdbServerPort // 5037
UIA2ServerHost = "localhost"
UIA2ServerPort = 6790
EvalInstallerPackageName = "sogou.mobile.explorer"
InstallViaInstallerCommand = "am start -S -n sogou.mobile.explorer/.PackageInstallerActivity -d"
)
//go:embed evalite
var evalite embed.FS
const forwardToPrefix = "forward-to-"
type AndroidDeviceOption func(*AndroidDevice)
@@ -40,6 +57,12 @@ func WithUIA2(uia2On bool) AndroidDeviceOption {
}
}
func WithStub(stubOn bool) AndroidDeviceOption {
return func(device *AndroidDevice) {
device.STUB = stubOn
}
}
func WithUIA2IP(ip string) AndroidDeviceOption {
return func(device *AndroidDevice) {
device.UIA2IP = ip
@@ -108,7 +131,14 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er
device.d = dev
device.logcat = NewAdbLogcat(device.SerialNumber)
evalToolRaw, err := evalite.ReadFile("evalite")
if err != nil {
return nil, errors.Wrap(code.LoadFileError, err.Error())
}
err = dev.Push(bytes.NewReader(evalToolRaw), "/data/local/tmp/evalite", time.Now())
if err != nil {
return nil, errors.Wrap(code.AndroidShellExecError, err.Error())
}
log.Info().Str("serial", device.SerialNumber).Msg("init android device")
return device, nil
}
@@ -151,9 +181,18 @@ type AndroidDevice struct {
logcat *AdbLogcat
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
UIA2 bool `json:"uia2,omitempty" yaml:"uia2,omitempty"` // use uiautomator2
STUB bool `json:"stub,omitempty" yaml:"stub,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"`
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
}
func (dev *AndroidDevice) Init() error {
myexec.RunCommand("adb", "-s", dev.SerialNumber, "shell",
"ime", "enable", "io.appium.settings/.UnicodeIME")
myexec.RunCommand("adb", "-s", dev.SerialNumber, "shell", "rm", "-r", env.DeviceActionLogFilePath)
return nil
}
func (dev *AndroidDevice) System() string {
@@ -169,7 +208,7 @@ func (dev *AndroidDevice) LogEnabled() bool {
}
func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt, err error) {
driverOptions := &DriverOptions{}
driverOptions := NewDriverOptions()
for _, option := range options {
option(driverOptions)
}
@@ -177,6 +216,8 @@ func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverE
var driver WebDriver
if dev.UIA2 || dev.LogOn {
driver, err = dev.NewUSBDriver(driverOptions.capabilities)
} else if dev.STUB {
driver, err = dev.NewStubDriver(driverOptions.capabilities)
} else {
driver, err = dev.NewAdbDriver()
}
@@ -184,7 +225,7 @@ func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverE
return nil, errors.Wrap(err, "failed to init UIA driver")
}
driverExt, err = newDriverExt(dev, driver, driverOptions.plugin)
driverExt, err = newDriverExt(dev, driver, options...)
if err != nil {
return nil, err
}
@@ -221,6 +262,36 @@ func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver WebDri
return uiaDriver, nil
}
func (dev *AndroidDevice) NewStubDriver(capabilities Capabilities) (driver *stubAndroidDriver, err error) {
socketLocalPort, err := dev.d.Forward(StubSocketName)
if err != nil {
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
fmt.Sprintf("forward port %d->%s failed: %v",
socketLocalPort, StubSocketName, err))
}
serverLocalPort, err := dev.d.Forward(DouyinServerPort)
if err != nil {
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
fmt.Sprintf("forward port %d->%d failed: %v",
serverLocalPort, DouyinServerPort, err))
}
rawURL := fmt.Sprintf("http://%s%d:%d",
forwardToPrefix, serverLocalPort, DouyinServerPort)
stubDriver, err := newStubAndroidDriver(fmt.Sprintf("127.0.0.1:%d", socketLocalPort), rawURL)
if err != nil {
_ = dev.d.ForwardKill(socketLocalPort)
_ = dev.d.ForwardKill(serverLocalPort)
return nil, errors.Wrap(code.AndroidDeviceConnectionError, err.Error())
}
stubDriver.adbClient = dev.d
stubDriver.logcat = dev.logcat
return stubDriver, nil
}
// NewHTTPDriver creates new remote HTTP client, this will also start a new session.
func (dev *AndroidDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver, err error) {
rawURL := fmt.Sprintf("http://%s:%d/wd/hub", dev.UIA2IP, dev.UIA2Port)
@@ -261,6 +332,116 @@ func (dev *AndroidDevice) StopPcap() string {
return ""
}
func (dev *AndroidDevice) Uninstall(packageName string) error {
return myexec.RunCommand("adb", "-s", dev.SerialNumber, "uninstall", packageName)
}
func (dev *AndroidDevice) Install(appPath string, opts *InstallOptions) error {
app, err := os.Open(appPath)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("install %s open file failed", appPath))
}
defer app.Close()
brand, err := dev.d.Brand()
if err != nil {
return err
}
args := []string{}
if opts.Reinstall {
args = append(args, "-r")
}
if opts.GrantPermission {
args = append(args, "-g")
}
if opts.Downgrade {
args = append(args, "-d")
}
switch strings.ToLower(brand) {
case "vivo":
return dev.installVivoSilent(app, args...)
case "oppo", "realme", "oneplus":
if dev.d.IsPackagesInstalled(EvalInstallerPackageName) {
return dev.installViaInstaller(app, args...)
}
log.Warn().Msg("oppo not install eval installer")
return dev.installCommon(app, args...)
default:
return dev.installCommon(app, args...)
}
}
func (dev *AndroidDevice) installVivoSilent(app io.ReadSeeker, args ...string) error {
currentTime := builtin.GetCurrentDay()
md5HashInBytes := md5.Sum([]byte(currentTime))
verifyCode := hex.EncodeToString(md5HashInBytes[:])
verifyCode = base64.StdEncoding.EncodeToString([]byte(verifyCode))
verifyCode = verifyCode[:8]
verifyCode = "-V" + verifyCode
args = append([]string{verifyCode}, args...)
_, err := dev.d.InstallAPK(app, args...)
return err
}
func (dev *AndroidDevice) installViaInstaller(app io.ReadSeeker, args ...string) error {
appRemotePath := "/data/local/tmp/" + strconv.FormatInt(time.Now().UnixMilli(), 10) + ".apk"
err := dev.d.Push(app, appRemotePath, time.Now())
if err != nil {
return err
}
done := make(chan error)
defer func() {
close(done)
}()
logcat := NewAdbLogcatWithCallback(dev.d.Serial(), func(line string) {
re := regexp.MustCompile(`\{.*?}`)
match := re.FindString(line)
if match == "" {
return
}
var result InstallResult
err := json.Unmarshal([]byte(match), &result)
if err != nil {
log.Warn().Msg("parse Install msg line error: " + match)
return
}
if result.Result == 0 {
// 安装成功
done <- nil
} else {
done <- errors.New(match)
}
})
err = logcat.CatchLogcat("PackageInstallerCallback")
if err != nil {
return err
}
defer func() {
_ = logcat.Stop()
}()
// 需要监听是否完成安装
command := strings.Split(InstallViaInstallerCommand, " ")
args = append(command, appRemotePath)
_, err = dev.d.RunShellCommand("am", args[1:]...)
if err != nil {
return err
}
// 等待安装完成或超时
timeout := 3 * time.Minute
select {
case err := <-done:
return err
case <-time.After(timeout):
return fmt.Errorf("installation timed out after %v", timeout)
}
}
func (dev *AndroidDevice) installCommon(app io.ReadSeeker, args ...string) error {
_, err := dev.d.InstallAPK(app, args...)
return err
}
type LineCallback func(string)
type AdbLogcat struct {

View File

@@ -0,0 +1,273 @@
package uixt
import (
"errors"
"fmt"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
)
type stubAndroidDriver struct {
socket net.Conn
seq int
timeout time.Duration
adbDriver
}
const StubSocketName = "com.bytest.device"
// newStubAndroidDriver
// 创建stub Driver address为forward后的端口格式127.0.0.1:${port}
func newStubAndroidDriver(address string, urlPrefix string, readTimeout ...time.Duration) (*stubAndroidDriver, error) {
timeout := 10 * time.Second
if len(readTimeout) > 0 {
timeout = readTimeout[0]
}
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: timeout,
}
if driver.urlPrefix, err = url.Parse(urlPrefix); err != nil {
return nil, err
}
return driver, nil
}
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.httpRequest(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.httpRequest(http.MethodPost, sad.concatURL(nil, pathElem...), bsJSON)
}
func (sad *stubAndroidDriver) NewSession(capabilities Capabilities) (SessionInfo, error) {
return SessionInfo{}, errDriverNotImplemented
}
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.adbClient.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() (DeviceStatus, error) {
app, err := sad.GetForegroundApp()
if err != nil {
return DeviceStatus{}, err
}
res, err := sad.sendCommand(app.PackageName, "Hello", nil)
if err != nil {
return DeviceStatus{}, err
}
log.Info().Msg(fmt.Sprintf("ping stub result :%v", res))
return DeviceStatus{}, nil
}
func (sad *stubAndroidDriver) Source(srcOpt ...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) LoginNoneUIBak(packageName, phoneNumber, captcha string) error {
_, err := sad.adbClient.RunShellCommand("am", "broadcast", "-a", fmt.Sprintf("%s.util.crony.action_login", packageName), "-e", "phone", phoneNumber, "-e", "code", captcha)
time.Sleep(10 * time.Second)
login, err := sad.isLogin(packageName)
if err != nil || !login {
log.Err(err).Msg("failed to login")
return fmt.Errorf("failed to login")
}
return err
}
func (sad *stubAndroidDriver) LoginNoneUI(packageName, phoneNumber, captcha string) error {
params := map[string]interface{}{
"phone": phoneNumber,
"code": captcha,
}
resp, err := sad.httpPOST(params, "/host", "/login", "account")
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
}
time.Sleep(10 * time.Second)
login, err := sad.isLogin(packageName)
if err != nil {
return err
}
if !login {
return fmt.Errorf("failed to login")
}
return 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
}
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) isLogin(packageName string) (login bool, err error) {
resp, err := sad.httpGET("/host", "/login", "/check")
if err != nil {
return false, err
}
res, err := resp.valueConvertToJsonObject()
if err != nil {
return false, err
}
log.Info().Msgf("%v", res)
if res["isSuccess"] != true {
err = fmt.Errorf("falied to get is login %s", res["data"])
log.Err(err).Msgf("%v", res)
return false, err
}
fmt.Printf("%v", resp)
if err != nil {
return false, err
}
return true, nil
}

View File

@@ -0,0 +1,56 @@
package uixt
import "testing"
var androidStubDriver *stubAndroidDriver
func setupStubDriver(t *testing.T) {
device, err := NewAndroidDevice()
checkErr(t, err)
device.STUB = true
androidStubDriver, err = device.NewStubDriver(Capabilities{})
checkErr(t, err)
}
func TestHello(t *testing.T) {
setupStubDriver(t)
status, err := androidStubDriver.Status()
if err != nil {
t.Fatal(err)
}
t.Log(status)
}
func TestSource(t *testing.T) {
setupStubDriver(t)
source, err := androidStubDriver.Source()
if err != nil {
t.Fatal(err)
}
t.Log(source)
}
func TestIsLogin(t *testing.T) {
setupStubDriver(t)
res, err := androidStubDriver.isLogin("com.ss.android.ugc.aweme")
if err != nil {
t.Fatal(err)
}
t.Log(res)
}
func TestLogin(t *testing.T) {
setupStubDriver(t)
err := androidStubDriver.LoginNoneUI("com.ss.android.ugc.aweme", "12342316231", "8517")
if err != nil {
t.Fatal(err)
}
}
func TestLogout(t *testing.T) {
setupStubDriver(t)
err := androidStubDriver.LogoutNoneUI("com.ss.android.ugc.aweme")
if err != nil {
t.Fatal(err)
}
}

View File

@@ -3,8 +3,7 @@
package uixt
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
@@ -21,8 +20,8 @@ var (
func setupAndroid(t *testing.T) {
device, err := NewAndroidDevice()
checkErr(t, err)
device.UIA2 = false
device.LogOn = true
device.UIA2 = true
device.LogOn = false
driverExt, err = device.NewDriver()
checkErr(t, err)
}
@@ -124,12 +123,9 @@ func TestDriver_DeviceSize(t *testing.T) {
}
func TestDriver_Source(t *testing.T) {
driver, err := NewUIADriver(nil, uiaServerURL)
if err != nil {
t.Fatal(err)
}
setupAndroid(t)
source, err := driver.Source()
source, err := driverExt.Driver.Source()
if err != nil {
t.Fatal(err)
}
@@ -199,33 +195,24 @@ func TestDriver_DeviceInfo(t *testing.T) {
func TestDriver_Tap(t *testing.T) {
setupAndroid(t)
driverExt.Driver.StartCaptureLog("")
err := driverExt.Driver.Tap(150, 340, WithIdentifier("test"))
err := driverExt.TapXY(0.5, 0.5, WithIdentifier("test"), WithPressDuration(4))
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Second)
err = driverExt.Driver.TapFloat(60.5, 125.5, WithIdentifier("test"))
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Second)
result, _ := driverExt.Driver.StopCaptureLog()
t.Log(result)
//time.Sleep(time.Second)
//
//err = driverExt.Driver.TapFloat(60.5, 125.5, WithIdentifier("test"))
//if err != nil {
// t.Fatal(err)
//}
//time.Sleep(time.Second)
//result, _ := driverExt.Driver.StopCaptureLog()
//t.Log(result)
}
func TestDriver_Swipe(t *testing.T) {
driver, err := NewUIADriver(nil, uiaServerURL)
if err != nil {
t.Fatal(err)
}
err = driver.Swipe(400, 1000, 400, 500, WithPressDuration(2000))
if err != nil {
t.Fatal(err)
}
err = driver.SwipeFloat(400, 555.5, 400, 1255.5)
setupAndroid(t)
err := driverExt.Driver.Swipe(400, 1000, 400, 500, WithPressDuration(0.5))
if err != nil {
t.Fatal(err)
}
@@ -261,7 +248,7 @@ func TestDriver_Drag(t *testing.T) {
func TestDriver_SendKeys(t *testing.T) {
setupAndroid(t)
err := driverExt.Driver.SendKeys("Android\"输入速度测试", WithIdentifier("test"))
err := driverExt.Driver.SendKeys("辽宁省沈阳市新民市民族街36-4", WithIdentifier("test"))
if err != nil {
t.Fatal(err)
}
@@ -306,6 +293,14 @@ func TestDriver_SetRotation(t *testing.T) {
}
}
func TestDriver_GetOrientation(t *testing.T) {
setupAndroid(t)
_, _ = driverExt.Driver.AppTerminate("com.quark.browser")
_ = driverExt.Driver.AppLaunch("com.quark.browser")
time.Sleep(2 * time.Second)
_ = driverExt.Driver.Homescreen()
}
func TestUiSelectorHelper_NewUiSelectorHelper(t *testing.T) {
uiSelector := NewUiSelectorHelper().Text("a").String()
if uiSelector != `new UiSelector().text("a");` {
@@ -448,8 +443,6 @@ func TestConvertPoints(t *testing.T) {
if len(eps) != 3 {
t.Fatal()
}
jsons, _ := json.Marshal(eps)
println(fmt.Sprintf("%v", string(jsons)))
}
func TestDriver_ShellInputUnicode(t *testing.T) {

View File

@@ -29,6 +29,7 @@ func NewUIADriver(capabilities Capabilities, urlPrefix string) (driver *uiaDrive
log.Info().Msg("init uiautomator2 driver")
if capabilities == nil {
capabilities = NewCapabilities()
capabilities.WithWaitForIdleTimeout(0)
}
driver = new(uiaDriver)
if driver.urlPrefix, err = url.Parse(urlPrefix); err != nil {
@@ -141,7 +142,12 @@ func (ud *uiaDriver) httpDELETE(pathElem ...string) (rawResp rawResponse, err er
func (ud *uiaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionInfo, err error) {
// register(postHandler, new NewSession("/wd/hub/session"))
var rawResp rawResponse
data := map[string]interface{}{"capabilities": capabilities}
data := make(map[string]interface{})
if len(capabilities) == 0 {
data["capabilities"] = make(map[string]interface{})
} else {
data["capabilities"] = map[string]interface{}{"alwaysMatch": capabilities}
}
if rawResp, err = ud.Driver.httpPOST(data, "/session"); err != nil {
return SessionInfo{SessionId: ""}, err
}
@@ -244,10 +250,14 @@ func (ud *uiaDriver) PressBack(options ...ActionOption) (err error) {
}
func (ud *uiaDriver) Homescreen() (err error) {
return ud.PressKeyCode(KCHome, KMEmpty)
return ud.PressKeyCodes(KCHome, KMEmpty)
}
func (ud *uiaDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) {
func (ud *uiaDriver) PressKeyCode(keyCode KeyCode) (err error) {
return ud.PressKeyCodes(keyCode, KMEmpty)
}
func (ud *uiaDriver) PressKeyCodes(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) {
// register(postHandler, new PressKeyCodeAsync("/wd/hub/session/:sessionId/appium/device/press_keycode"))
data := map[string]interface{}{
"keycode": keyCode,
@@ -293,7 +303,7 @@ func (ud *uiaDriver) TapFloat(x, y float64, options ...ActionOption) (err error)
duration := 100.0
if actionOptions.PressDuration > 0 {
duration = actionOptions.PressDuration
duration = actionOptions.PressDuration * 1000
}
data := map[string]interface{}{
"actions": []interface{}{
@@ -399,7 +409,7 @@ func (ud *uiaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...Actio
duration := 200.0
if actionOptions.PressDuration > 0 {
duration = actionOptions.PressDuration
duration = actionOptions.PressDuration * 1000
}
data := map[string]interface{}{
"actions": []interface{}{
@@ -618,3 +628,10 @@ func (ud *uiaDriver) TapByTexts(actions ...TapTextAction) error {
}
return nil
}
func (ud *uiaDriver) GetDriverResults() []*DriverResult {
defer func() {
ud.Driver.driverResults = nil
}()
return ud.Driver.driverResults
}

View File

@@ -17,10 +17,18 @@ import (
)
type Driver struct {
urlPrefix *url.URL
sessionId string
client *http.Client
scale float64
urlPrefix *url.URL
sessionId string
client *http.Client
scale float64
driverResults []*DriverResult
}
type DriverResult struct {
RequestUrl string `json:"request_driver_url"`
RequestBody string `json:"request_driver_body,omitempty"`
RequestDuration time.Duration `json:"request_driver_duration"`
RequestTime time.Time `json:"request_driver_time"`
}
func (wd *Driver) concatURL(u *url.URL, elem ...string) string {
@@ -73,7 +81,15 @@ func (wd *Driver) httpRequest(method string, rawURL string, rawBody []byte) (raw
}()
rawResp, err = io.ReadAll(resp.Body)
logger := log.Debug().Int("statusCode", resp.StatusCode).Str("duration", time.Since(start).String())
duration := time.Since(start)
driverResult := &DriverResult{
RequestUrl: rawURL,
RequestBody: string(rawBody),
RequestDuration: duration,
RequestTime: time.Now(),
}
wd.driverResults = append(wd.driverResults, driverResult)
logger := log.Debug().Int("statusCode", resp.StatusCode).Str("duration", duration.String())
if !strings.HasSuffix(rawURL, "screenshot") {
// avoid printing screenshot data
logger.Str("response", string(rawResp))

BIN
hrp/pkg/uixt/evalite Normal file

Binary file not shown.

View File

@@ -126,11 +126,16 @@ type DriverExt struct {
lastPopup *PopupInfo
}
func newDriverExt(device Device, driver WebDriver, plugin funplugin.IPlugin) (dExt *DriverExt, err error) {
func newDriverExt(device Device, driver WebDriver, options ...DriverOption) (dExt *DriverExt, err error) {
driverOptions := NewDriverOptions()
for _, option := range options {
option(driverOptions)
}
dExt = &DriverExt{
Device: device,
Driver: driver,
plugin: plugin,
plugin: driverOptions.plugin,
cacheStepData: cacheStepData{},
interruptSignal: make(chan os.Signal, 1),
}
@@ -150,21 +155,86 @@ func newDriverExt(device Device, driver WebDriver, plugin funplugin.IPlugin) (dE
if err != nil {
return nil, errors.Wrap(err, "get screen resolution failed")
}
if dExt.ImageService, err = newVEDEMImageService(); err != nil {
return nil, err
if driverOptions.withImageService {
if dExt.ImageService, err = newVEDEMImageService(); err != nil {
return nil, err
}
}
// create results directory
if err = builtin.EnsureFolderExists(env.ResultsPath); err != nil {
return nil, errors.Wrap(err, "create results directory failed")
}
if err = builtin.EnsureFolderExists(env.ScreenShotsPath); err != nil {
return nil, errors.Wrap(err, "create screenshots directory failed")
if driverOptions.withResultFolder {
// create results directory
if err = builtin.EnsureFolderExists(env.ResultsPath); err != nil {
return nil, errors.Wrap(err, "create results directory failed")
}
if err = builtin.EnsureFolderExists(env.ScreenShotsPath); err != nil {
return nil, errors.Wrap(err, "create screenshots directory failed")
}
}
return dExt, nil
}
func (dExt *DriverExt) InstallByUrl(url string, opts *InstallOptions) error {
// 获取当前目录
cwd, err := os.Getwd()
if err != nil {
return err
}
// 将文件保存到当前目录
appPath := filepath.Join(cwd, fmt.Sprint(time.Now().UnixNano())) // 替换为你想保存的文件名
err = builtin.DownloadFile(appPath, url)
if err != nil {
return err
}
err = dExt.Install(appPath, opts)
if err != nil {
return err
}
return nil
}
func (dExt *DriverExt) Uninstall(packageName string, options ...ActionOption) error {
actionOptions := NewActionOptions(options...)
err := dExt.Device.Uninstall(packageName)
if err != nil {
log.Warn().Err(err).Msg("failed to uninstall")
}
if actionOptions.IgnoreNotFoundError {
return nil
}
return err
}
func (dExt *DriverExt) Install(filePath string, opts *InstallOptions) error {
if _, ok := dExt.Device.(*AndroidDevice); ok {
stopChan := make(chan struct{})
go func() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
actions := []TapTextAction{
{Text: "^.*无视风险安装$", Options: []ActionOption{WithTapOffset(100, 0), WithRegex(true), WithIgnoreNotFoundError(true)}},
{Text: "^已了解此应用未经检测.*", Options: []ActionOption{WithTapOffset(-450, 0), WithRegex(true), WithIgnoreNotFoundError(true)}},
}
_ = dExt.Driver.TapByTexts(actions...)
_ = dExt.TapByOCR("^(.*无视风险安装|确定|继续|完成|点击继续安装|继续安装旧版本|替换|安装|授权本次安装|继续安装|重新安装)$", WithRegex(true), WithIgnoreNotFoundError(true))
case <-stopChan:
fmt.Println("Ticker stopped")
return
}
}
}()
defer func() {
close(stopChan)
}()
}
return dExt.Device.Install(filePath, opts)
}
// takeScreenShot takes screenshot and saves image file to $CWD/screenshots/ folder
func (dExt *DriverExt) takeScreenShot(fileName string) (raw *bytes.Buffer, path string, err error) {
// iOS 优先使用 MJPEG 流进行截图,性能最优
@@ -267,7 +337,7 @@ func (dExt *DriverExt) GetStepCacheData() map[string]interface{} {
cacheData["screenshots_urls"] = dExt.cacheStepData.screenResults.getScreenShotUrls()
cacheData["screen_results"] = dExt.cacheStepData.screenResults
cacheData["e2e_results"] = dExt.cacheStepData.e2eDelay
cacheData["driver_request_results"] = dExt.Driver.GetDriverResults()
// clear cache
dExt.cacheStepData.reset()
return cacheData

48
hrp/pkg/uixt/install.go Normal file
View File

@@ -0,0 +1,48 @@
package uixt
type InstallOptions struct {
Reinstall bool
GrantPermission bool
Downgrade bool
RetryTime int
}
type InstallOption func(o *InstallOptions)
func NewInstallOptions(options ...InstallOption) *InstallOptions {
installOptions := &InstallOptions{}
for _, option := range options {
option(installOptions)
}
return installOptions
}
func WithReinstall(reinstall bool) InstallOption {
return func(o *InstallOptions) {
o.Reinstall = reinstall
}
}
func WithGrantPermission(grantPermission bool) InstallOption {
return func(o *InstallOptions) {
o.GrantPermission = grantPermission
}
}
func WithDowngrade(downgrade bool) InstallOption {
return func(o *InstallOptions) {
o.Downgrade = downgrade
}
}
func WithRetryTime(retryTime int) InstallOption {
return func(o *InstallOptions) {
o.RetryTime = retryTime
}
}
type InstallResult struct {
Result int `json:"result"`
ErrorCode int `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}

View File

@@ -250,11 +250,7 @@ type Screen struct {
}
type AppInfo struct {
ProcessArguments struct {
Env interface{} `json:"env"`
Args []interface{} `json:"args"`
} `json:"processArguments"`
Name string `json:"name"`
Name string `json:"name,omitempty"`
AppBaseInfo
}
@@ -264,6 +260,10 @@ type AppBaseInfo struct {
ViewController string `json:"viewController,omitempty"` // ios view controller
PackageName string `json:"packageName,omitempty"` // android package name
Activity string `json:"activity,omitempty"` // android activity
VersionName string `json:"versionName,omitempty"`
VersionCode int `json:"versionCode,omitempty"`
AppName string `json:"appName,omitempty"`
// AppIcon string `json:"appIcon,omitempty"`
}
type AppState int
@@ -374,6 +374,11 @@ func (opt SourceOption) WithFormatAsJson() SourceOption {
return opt
}
func (opt SourceOption) WithProcessName(processName string) SourceOption {
opt["processName"] = processName
return opt
}
// WithFormatAsXml Application elements tree in form of xml string
func (opt SourceOption) WithFormatAsXml() SourceOption {
opt["format"] = "xml"
@@ -446,8 +451,17 @@ type Rect struct {
}
type DriverOptions struct {
capabilities Capabilities
plugin funplugin.IPlugin
capabilities Capabilities
plugin funplugin.IPlugin
withImageService bool
withResultFolder bool
}
func NewDriverOptions() *DriverOptions {
return &DriverOptions{
withImageService: true,
withResultFolder: true,
}
}
type DriverOption func(*DriverOptions)
@@ -458,6 +472,18 @@ func WithDriverCapabilities(capabilities Capabilities) DriverOption {
}
}
func WithDriverImageService(withImageService bool) DriverOption {
return func(options *DriverOptions) {
options.withImageService = withImageService
}
}
func WithDriverResultFolder(withResultFolder bool) DriverOption {
return func(options *DriverOptions) {
options.withResultFolder = withResultFolder
}
}
func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption {
return func(options *DriverOptions) {
options.plugin = plugin
@@ -466,8 +492,8 @@ func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption {
// current implemeted device: IOSDevice, AndroidDevice
type Device interface {
System() string // ios or android
UUID() string // ios udid or android serial
Init() error // init android device
UUID() string // ios udid or android serial
LogEnabled() bool
NewDriver(...DriverOption) (driverExt *DriverExt, err error)
@@ -476,6 +502,10 @@ type Device interface {
StartPcap() error
StopPcap() string
Uninstall(packageName string) error
Install(appPath string, opts *InstallOptions) error
}
type ForegroundApp struct {
@@ -524,6 +554,8 @@ type WebDriver interface {
// Homescreen Forces the device under test to switch to the home screen
Homescreen() error
Unlock() (err error)
// AppLaunch Launch an application with given bundle identifier in scope of current session.
// !This method is only available since Xcode9 SDK
AppLaunch(packageName string) error
@@ -570,6 +602,8 @@ type WebDriver interface {
// It worked when `WDA` was foreground. https://github.com/appium/WebDriverAgent/issues/330
GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error)
SetIme(ime string) error
// SendKeys Types a string into active element. There must be element with keyboard focus,
// otherwise an error is raised.
// WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60
@@ -578,17 +612,25 @@ type WebDriver interface {
// Input works like SendKeys
Input(text string, options ...ActionOption) error
Clear(packageName string) error
// PressButton Presses the corresponding hardware button on the device
PressButton(devBtn DeviceButton) error
// PressBack Presses the back button
PressBack(options ...ActionOption) error
PressKeyCode(keyCode KeyCode) (err error)
Screenshot() (*bytes.Buffer, error)
// Source Return application elements tree
Source(srcOpt ...SourceOption) (string, error)
LoginNoneUI(packageName, phoneNumber string, captcha string) error
LogoutNoneUI(packageName string) error
TapByText(text string, options ...ActionOption) error
TapByTexts(actions ...TapTextAction) error
@@ -609,4 +651,5 @@ type WebDriver interface {
// triggers the log capture and returns the log entries
StartCaptureLog(identifier ...string) (err error)
StopCaptureLog() (result interface{}, err error)
GetDriverResults() []*DriverResult
}

View File

@@ -276,6 +276,7 @@ type IOSDevice struct {
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
XCTestBundleID string `json:"xctest_bundle_id,omitempty" yaml:"xctest_bundle_id,omitempty"`
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
// switch to iOS springboard before init WDA session
ResetHomeOnStartup bool `json:"reset_home_on_startup,omitempty" yaml:"reset_home_on_startup,omitempty"`
@@ -294,8 +295,8 @@ type IOSDevice struct {
pcapFile string // saved pcap file path
}
func (dev *IOSDevice) System() string {
return "ios"
func (dev *IOSDevice) Init() error {
return nil
}
func (dev *IOSDevice) UUID() string {
@@ -307,7 +308,7 @@ func (dev *IOSDevice) LogEnabled() bool {
}
func (dev *IOSDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt, err error) {
driverOptions := &DriverOptions{}
driverOptions := NewDriverOptions()
for _, option := range options {
option(driverOptions)
}
@@ -338,7 +339,7 @@ func (dev *IOSDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt,
}
}
driverExt, err = newDriverExt(dev, driver, driverOptions.plugin)
driverExt, err = newDriverExt(dev, driver, options...)
if err != nil {
return nil, err
}
@@ -471,6 +472,20 @@ func (dev *IOSDevice) StopPcap() string {
return dev.pcapFile
}
func (dev *IOSDevice) Install(appPath string, opts *InstallOptions) (err error) {
for i := 0; i <= opts.RetryTime; i++ {
err = builtin.RunCommand("go-ios", "install", "--path="+appPath, "--udid="+dev.UDID)
if err == nil {
return nil
}
}
return err
}
func (dev *IOSDevice) Uninstall(bundleId string) error {
return builtin.RunCommand("go-ios", "uninstall", bundleId, "--udid="+dev.UDID)
}
func (dev *IOSDevice) forward(localPort, remotePort int) error {
log.Info().Int("localPort", localPort).Int("remotePort", remotePort).
Str("udid", dev.UDID).Msg("forward tcp port")
@@ -635,7 +650,7 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver
wd := new(wdaDriver)
wd.client = http.DefaultClient
host := "127.0.0.1"
host := "localhost"
if wd.urlPrefix, err = url.Parse(fmt.Sprintf("http://%s:%d", host, localPort)); err != nil {
return nil, errors.Wrap(code.IOSDeviceHTTPDriverError, err.Error())
}

View File

@@ -591,6 +591,14 @@ func (wd *wdaDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe
return
}
func (wd *wdaDriver) SetIme(ime string) error {
return errDriverNotImplemented
}
func (wd *wdaDriver) PressKeyCode(keyCode KeyCode) (err error) {
return errDriverNotImplemented
}
func (wd *wdaDriver) SendKeys(text string, options ...ActionOption) (err error) {
// [[FBRoute POST:@"/wda/keys"] respondWithTarget:self action:@selector(handleKeys:)]
actionOptions := NewActionOptions(options...)
@@ -607,6 +615,10 @@ func (wd *wdaDriver) Input(text string, options ...ActionOption) (err error) {
return wd.SendKeys(text, options...)
}
func (wd *wdaDriver) Clear(packageName string) error {
return errDriverNotImplemented
}
// PressBack simulates a short press on the BACK button.
func (wd *wdaDriver) PressBack(options ...ActionOption) (err error) {
actionOptions := NewActionOptions(options...)
@@ -651,6 +663,14 @@ func (wd *wdaDriver) PressButton(devBtn DeviceButton) (err error) {
return
}
func (wd *wdaDriver) LoginNoneUI(packageName, phoneNumber string, captcha string) error {
return errDriverNotImplemented
}
func (wd *wdaDriver) LogoutNoneUI(packageName string) error {
return errDriverNotImplemented
}
func (wd *wdaDriver) StartCamera() (err error) {
// start camera, alias for app_launch com.apple.camera
return wd.AppLaunch("com.apple.camera")
@@ -877,6 +897,13 @@ func (wd *wdaDriver) StopCaptureLog() (result interface{}, err error) {
return reply.Value, nil
}
func (wd *wdaDriver) GetDriverResults() []*DriverResult {
defer func() {
wd.Driver.driverResults = nil
}()
return wd.Driver.driverResults
}
type rawResponse []byte
func (r rawResponse) checkErr() (err error) {
@@ -939,6 +966,13 @@ func (r rawResponse) valueConvertToJsonRawMessage() (raw builtinJSON.RawMessage,
return
}
func (r rawResponse) valueConvertToJsonObject() (obj map[string]interface{}, err error) {
if err = json.Unmarshal(r, &obj); err != nil {
return nil, err
}
return
}
func (r rawResponse) valueDecodeAsBase64() (raw *bytes.Buffer, err error) {
str, err := r.valueConvertToString()
if err != nil {

View File

@@ -7,6 +7,8 @@ import (
"fmt"
"testing"
"time"
"github.com/rs/zerolog/log"
)
var (
@@ -26,7 +28,7 @@ func setup(t *testing.T) {
if err != nil {
t.Fatal(err)
}
iOSDriverExt, err = newDriverExt(device, driver, nil)
iOSDriverExt, err = newDriverExt(device, driver)
if err != nil {
t.Fatal(err)
}
@@ -37,6 +39,15 @@ func TestViaUSB(t *testing.T) {
t.Log(driver.Status())
}
func TestInstall(t *testing.T) {
setup(t)
err := iOSDriverExt.Install("/Users/bytedance/Downloads/com.yueyou.cyreader_1387717110_7.54.20.ipa", NewInstallOptions(WithRetryTime(5)))
log.Error().Err(err)
if err != nil {
t.Fatal(err)
}
}
func TestNewIOSDevice(t *testing.T) {
device, _ := NewIOSDevice()
if device != nil {

View File

@@ -62,11 +62,11 @@ func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error {
// filter ocr texts with time format
var liveTimeTexts []string
for _, ocrText := range ocrTexts {
if len(ocrText.Text) < 10 || strings.Contains(ocrText.Text, ":") {
if len(ocrText.Text) < 13 || strings.Contains(ocrText.Text, ":") {
continue
}
// exclude digit(s) recognized as letter(s)
_, errParseInt := strconv.ParseInt(ocrText.Text[:10], 10, 64)
_, errParseInt := strconv.ParseInt(ocrText.Text[:13], 10, 64)
if errParseInt != nil {
continue
}
@@ -81,11 +81,6 @@ func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error {
return nil
}
if len(liveTimeText) < 13 {
for (13 - len(liveTimeText)) > 0 {
liveTimeText += "0"
}
}
liveTimeInt, err := strconv.Atoi(liveTimeText)
if err != nil {
liveTimeInt = 0