mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-05 15:59:33 +08:00
refactor ios: replace gidevice with go-ios
This commit is contained in:
@@ -15,24 +15,25 @@ import (
|
||||
type ActionMethod string
|
||||
|
||||
const (
|
||||
ACTION_LOG ActionMethod = "log"
|
||||
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"
|
||||
ACTION_AppStop ActionMethod = "app_stop"
|
||||
ACTION_ScreenShot ActionMethod = "screenshot"
|
||||
ACTION_Sleep ActionMethod = "sleep"
|
||||
ACTION_SleepMS ActionMethod = "sleep_ms"
|
||||
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"
|
||||
ACTION_LOG ActionMethod = "log"
|
||||
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"
|
||||
ACTION_AppStop ActionMethod = "app_stop"
|
||||
ACTION_ScreenShot ActionMethod = "screenshot"
|
||||
ACTION_Sleep ActionMethod = "sleep"
|
||||
ACTION_SleepMS ActionMethod = "sleep_ms"
|
||||
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"
|
||||
ACTION_GetForegroundApp ActionMethod = "get_foreground_app"
|
||||
|
||||
// UI handling
|
||||
ACTION_Home ActionMethod = "home"
|
||||
@@ -46,6 +47,7 @@ const (
|
||||
ACTION_Swipe ActionMethod = "swipe"
|
||||
ACTION_Input ActionMethod = "input"
|
||||
ACTION_Back ActionMethod = "back"
|
||||
ACTION_KeyCode ActionMethod = "keycode"
|
||||
|
||||
// custom actions
|
||||
ACTION_SwipeToTapApp ActionMethod = "swipe_to_tap_app" // swipe left & right to find app and tap
|
||||
@@ -109,7 +111,8 @@ type ActionOptions struct {
|
||||
MaxRetryTimes int `json:"max_retry_times,omitempty" yaml:"max_retry_times,omitempty"` // max retry times
|
||||
IgnoreNotFoundError bool `json:"ignore_NotFoundError,omitempty" yaml:"ignore_NotFoundError,omitempty"` // ignore error if target element not found
|
||||
Interval float64 `json:"interval,omitempty" yaml:"interval,omitempty"` // interval between retries in seconds
|
||||
PressDuration float64 `json:"duration,omitempty" yaml:"duration,omitempty"` // used to set duration of ios swipe action
|
||||
Duration float64 `json:"duration,omitempty" yaml:"duration,omitempty"` // used to set duration of ios swipe action
|
||||
PressDuration float64 `json:"press_duration,omitempty" yaml:"press_duration,omitempty"` // used to set duration of ios swipe action
|
||||
Steps int `json:"steps,omitempty" yaml:"steps,omitempty"` // used to set steps of android swipe action
|
||||
Direction interface{} `json:"direction,omitempty" yaml:"direction,omitempty"` // used by swipe to tap text or app
|
||||
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty"` // TODO: wait timeout in seconds for mobile action
|
||||
@@ -159,6 +162,9 @@ func (o *ActionOptions) Options() []ActionOption {
|
||||
if o.Interval != 0 {
|
||||
options = append(options, WithInterval(o.Interval))
|
||||
}
|
||||
if o.Duration != 0 {
|
||||
options = append(options, WithDuration(o.Duration))
|
||||
}
|
||||
if o.PressDuration != 0 {
|
||||
options = append(options, WithPressDuration(o.PressDuration))
|
||||
}
|
||||
@@ -309,8 +315,8 @@ func (o *ActionOptions) updateData(data map[string]interface{}) {
|
||||
data["steps"] = 12 // default steps
|
||||
}
|
||||
|
||||
if o.PressDuration > 0 {
|
||||
data["duration"] = o.PressDuration
|
||||
if o.Duration > 0 {
|
||||
data["duration"] = o.Duration
|
||||
}
|
||||
if _, ok := data["duration"]; !ok {
|
||||
data["duration"] = 0 // default duration
|
||||
@@ -380,9 +386,15 @@ func WithInterval(sec float64) ActionOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithPressDuration(duration float64) ActionOption {
|
||||
func WithDuration(duration float64) ActionOption {
|
||||
return func(o *ActionOptions) {
|
||||
o.PressDuration = duration
|
||||
o.Duration = duration
|
||||
}
|
||||
}
|
||||
|
||||
func WithPressDuration(pressDuration float64) ActionOption {
|
||||
return func(o *ActionOptions) {
|
||||
o.PressDuration = pressDuration
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,15 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/httprunner/funplugin/myexec"
|
||||
@@ -245,6 +248,34 @@ func (ad *adbDriver) Unlock() (err error) {
|
||||
return ad.PressKeyCodes(KCMenu, KMEmpty)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Backspace(count int, options ...ActionOption) (err error) {
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
if count == 1 {
|
||||
return ad.PressKeyCode(67)
|
||||
}
|
||||
keyArray := make([]KeyCode, count)
|
||||
|
||||
for i := range keyArray {
|
||||
keyArray[i] = KeyCode(67)
|
||||
}
|
||||
return ad.combinationKey(keyArray)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) combinationKey(keyCodes []KeyCode) (err error) {
|
||||
if len(keyCodes) == 1 {
|
||||
return ad.PressKeyCode(keyCodes[0])
|
||||
}
|
||||
strKeyCodes := make([]string, len(keyCodes))
|
||||
for i, keycode := range keyCodes {
|
||||
strKeyCodes[i] = fmt.Sprintf("%d", keycode)
|
||||
}
|
||||
_, err = ad.adbClient.RunShellCommand(
|
||||
"input", append([]string{"keycombination"}, strKeyCodes...)...)
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) PressKeyCode(keyCode KeyCode) (err error) {
|
||||
return ad.PressKeyCodes(keyCode, KMEmpty)
|
||||
}
|
||||
@@ -309,15 +340,11 @@ func (ad *adbDriver) TapFloat(x, y float64, options ...ActionOption) (err error)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) DoubleTap(x, y int, options ...ActionOption) error {
|
||||
return ad.DoubleTapFloat(float64(x), float64(y), options...)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) DoubleTapFloat(x, y float64, options ...ActionOption) (err error) {
|
||||
func (ad *adbDriver) DoubleTap(x, y float64, options ...ActionOption) error {
|
||||
// adb shell input tap x y
|
||||
xStr := fmt.Sprintf("%.1f", x)
|
||||
yStr := fmt.Sprintf("%.1f", y)
|
||||
_, err = ad.adbClient.RunShellCommand(
|
||||
_, err := ad.adbClient.RunShellCommand(
|
||||
"input", "tap", xStr, yStr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("tap <%s, %s> failed", xStr, yStr))
|
||||
@@ -331,22 +358,64 @@ func (ad *adbDriver) DoubleTapFloat(x, y float64, options ...ActionOption) (err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) TouchAndHold(x, y int, second ...float64) (err error) {
|
||||
return ad.TouchAndHoldFloat(float64(x), float64(y), second...)
|
||||
func (ad *adbDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) {
|
||||
actionOptions := NewActionOptions(options...)
|
||||
|
||||
if len(actionOptions.Offset) == 2 {
|
||||
x += float64(actionOptions.Offset[0])
|
||||
y += float64(actionOptions.Offset[1])
|
||||
}
|
||||
x += actionOptions.getRandomOffset()
|
||||
y += actionOptions.getRandomOffset()
|
||||
duration := 1000.0
|
||||
if actionOptions.Duration > 0 {
|
||||
duration = actionOptions.Duration * 1000
|
||||
}
|
||||
// adb shell input swipe fromX fromY toX toY
|
||||
_, err = ad.adbClient.RunShellCommand(
|
||||
"input", "swipe",
|
||||
fmt.Sprintf("%.1f", x), fmt.Sprintf("%.1f", y),
|
||||
fmt.Sprintf("%.1f", x), fmt.Sprintf("%.1f", y),
|
||||
fmt.Sprintf("%d", int(duration)),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "long press failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) TouchAndHoldFloat(x, y float64, second ...float64) (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
func (ad *adbDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
|
||||
actionOptions := NewActionOptions(options...)
|
||||
|
||||
func (ad *adbDriver) Drag(fromX, fromY, toX, toY int, options ...ActionOption) error {
|
||||
return ad.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) DragFloat(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
if len(actionOptions.Offset) == 4 {
|
||||
fromX += float64(actionOptions.Offset[0])
|
||||
fromY += float64(actionOptions.Offset[1])
|
||||
toX += float64(actionOptions.Offset[2])
|
||||
toY += float64(actionOptions.Offset[3])
|
||||
}
|
||||
fromX += actionOptions.getRandomOffset()
|
||||
fromY += actionOptions.getRandomOffset()
|
||||
toX += actionOptions.getRandomOffset()
|
||||
toY += actionOptions.getRandomOffset()
|
||||
duration := 200.0
|
||||
if actionOptions.Duration > 0 {
|
||||
duration = actionOptions.Duration * 1000
|
||||
}
|
||||
command := "swipe"
|
||||
if actionOptions.PressDuration > 0 {
|
||||
command = "draganddrop"
|
||||
}
|
||||
// adb shell input swipe fromX fromY toX toY
|
||||
_, err = ad.adbClient.RunShellCommand(
|
||||
"input", command,
|
||||
fmt.Sprintf("%.1f", fromX), fmt.Sprintf("%.1f", fromY),
|
||||
fmt.Sprintf("%.1f", toX), fmt.Sprintf("%.1f", toY),
|
||||
fmt.Sprintf("%d", int(duration)),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "drag failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Swipe(fromX, fromY, toX, toY int, options ...ActionOption) error {
|
||||
@@ -557,8 +626,8 @@ 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) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
|
||||
return info, errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (ad *adbDriver) LogoutNoneUI(packageName string) error {
|
||||
@@ -750,6 +819,10 @@ func (ad *adbDriver) GetSession() *DriverSession {
|
||||
return &ad.Driver.session
|
||||
}
|
||||
|
||||
func (ad *adbDriver) GetDriverResults() []*DriverResult {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) {
|
||||
packageInfo, err := ad.adbClient.RunShellCommand(
|
||||
"CLASSPATH=/data/local/tmp/evalite", "app_process", "/",
|
||||
@@ -916,3 +989,63 @@ var androidActivities = map[string]map[string][]string{
|
||||
},
|
||||
// TODO: SPH, XHS
|
||||
}
|
||||
|
||||
func (ad *adbDriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
|
||||
// 获取当前时间戳
|
||||
timestamp := time.Now().Format("20060102_150405") + fmt.Sprintf("_%03d", time.Now().UnixNano()/1e6%1000)
|
||||
// 创建文件名
|
||||
fileName := fmt.Sprintf("%s/%s.mp4", folderPath, timestamp)
|
||||
err = os.MkdirAll(folderPath, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error creating directory")
|
||||
}
|
||||
|
||||
// 创建一个文件
|
||||
file, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
log.Error().Err(err)
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
// scrcpy -s 7d21bb91 --record=file.mp4 -N
|
||||
cmd := exec.Command(
|
||||
"scrcpy",
|
||||
"-s", ad.adbClient.Serial(),
|
||||
fmt.Sprintf("--record=%s", fileName),
|
||||
"-N",
|
||||
)
|
||||
cmd.Stdout = io.Discard
|
||||
cmd.Stderr = io.Discard
|
||||
// 启动命令
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Error().Err(err)
|
||||
return "", err
|
||||
}
|
||||
timer := time.After(duration)
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
// 等待 ffmpeg 命令执行完毕
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
select {
|
||||
case <-timer:
|
||||
// 超时,停止 scrcpy 进程
|
||||
if err := cmd.Process.Signal(syscall.SIGINT); err != nil {
|
||||
log.Error().Err(err)
|
||||
}
|
||||
case err := <-done:
|
||||
// ffmpeg 正常结束
|
||||
if err != nil {
|
||||
log.Error().Err(err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return filepath.Abs(fileName)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) TearDown() {
|
||||
}
|
||||
|
||||
@@ -24,6 +24,12 @@ type stubAndroidDriver struct {
|
||||
|
||||
const StubSocketName = "com.bytest.device"
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
// newStubAndroidDriver
|
||||
// 创建stub Driver address为forward后的端口格式127.0.0.1:${port}
|
||||
func newStubAndroidDriver(address string, urlPrefix string, readTimeout ...time.Duration) (*stubAndroidDriver, error) {
|
||||
@@ -174,52 +180,37 @@ func (sad *stubAndroidDriver) Source(srcOpt ...SourceOption) (source string, 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
func (sad *stubAndroidDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
|
||||
params := map[string]interface{}{
|
||||
"phone": phoneNumber,
|
||||
"code": captcha,
|
||||
}
|
||||
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 err
|
||||
return info, err
|
||||
}
|
||||
res, err := resp.valueConvertToJsonObject()
|
||||
if err != nil {
|
||||
return err
|
||||
return info, err
|
||||
}
|
||||
log.Info().Msgf("%v", res)
|
||||
if res["isSuccess"] != true {
|
||||
err = fmt.Errorf("failed to login %s", res["data"])
|
||||
err = fmt.Errorf("falied to login %s", res["data"])
|
||||
log.Err(err).Msgf("%v", res)
|
||||
return err
|
||||
return info, err
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
login, err := sad.isLogin(packageName)
|
||||
if err != nil {
|
||||
return 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)
|
||||
}
|
||||
if !login {
|
||||
return fmt.Errorf("failed to login")
|
||||
}
|
||||
return nil
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) LogoutNoneUI(packageName string) error {
|
||||
@@ -233,11 +224,15 @@ func (sad *stubAndroidDriver) LogoutNoneUI(packageName string) error {
|
||||
}
|
||||
log.Info().Msgf("%v", res)
|
||||
if res["isSuccess"] != true {
|
||||
err = fmt.Errorf("failed to logout %s", res["data"])
|
||||
err = fmt.Errorf("falied to logout %s", res["data"])
|
||||
log.Err(err).Msgf("%v", res)
|
||||
return err
|
||||
}
|
||||
log.Info().Interface("resp", resp).Msg("logout success")
|
||||
fmt.Printf("%v", resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -256,21 +251,44 @@ func (sad *stubAndroidDriver) LoginNoneUIDynamic(packageName, phoneNumber string
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) isLogin(packageName string) (login bool, err error) {
|
||||
resp, err := sad.httpGET("/host", "/login", "/check")
|
||||
func (sad *stubAndroidDriver) SetHDTStatus(status bool) error {
|
||||
_, err := sad.adbClient.RunShellCommand("settings", "put", "global", "feedbacker_sso_bypass_token", "default_sso_bypass_token")
|
||||
if err != nil {
|
||||
return false, err
|
||||
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 false, err
|
||||
return info, err
|
||||
}
|
||||
log.Info().Msgf("%v", res)
|
||||
if res["isSuccess"] != true {
|
||||
err = fmt.Errorf("failed to check login %s", res["data"])
|
||||
err = fmt.Errorf("falied to get app info %s", res["data"])
|
||||
log.Err(err).Msgf("%v", res)
|
||||
return false, err
|
||||
return info, err
|
||||
}
|
||||
log.Info().Interface("resp", resp).Msg("check login success")
|
||||
return true, nil
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package uixt
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var androidStubDriver *stubAndroidDriver
|
||||
|
||||
@@ -30,21 +34,13 @@ func TestSource(t *testing.T) {
|
||||
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")
|
||||
info, err := androidStubDriver.LoginNoneUI("com.ss.android.ugc.aweme", "12342316231", "8517", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(info)
|
||||
}
|
||||
|
||||
func TestLogout(t *testing.T) {
|
||||
@@ -54,3 +50,92 @@ func TestLogout(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwipe(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
err := androidStubDriver.Swipe(878, 2375, 672, 2375)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTap(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
err := androidStubDriver.Tap(900, 400)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoubleTap(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
err := androidStubDriver.DoubleTap(500, 500)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLongPress(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
err := androidStubDriver.Swipe(1036, 1076, 1036, 1076, WithDuration(3))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInput(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
err := androidStubDriver.Input("\"哈哈\"")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
raw, err := androidStubDriver.Screenshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
source, err := androidStubDriver.Source()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
step := 14
|
||||
file, err := os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/0/%d.jpg", step))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file.Write(raw.Bytes())
|
||||
|
||||
file, err = os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/0/%d.json", step))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file.Write([]byte(source))
|
||||
}
|
||||
|
||||
func TestAppLaunch(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
err := androidStubDriver.AppLaunch("com.ss.android.ugc.aweme")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppTerminal(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
_, err := androidStubDriver.AppTerminate("com.ss.android.ugc.aweme")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppInfo(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
info, err := androidStubDriver.getLoginAppInfo("com.ss.android.ugc.aweme")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(info)
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ func TestDriver_Drag(t *testing.T) {
|
||||
}
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
|
||||
err = driver.DragFloat(400, 501.5, 400, 261.5)
|
||||
err = driver.Drag(400, 501.5, 400, 261.5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -502,3 +502,21 @@ func TestTapTexts(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordVideo(t *testing.T) {
|
||||
setupAndroidAdbDriver(t)
|
||||
path, err := driverExt.Driver.(*adbDriver).RecordScreen("", 5*time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
println(path)
|
||||
}
|
||||
|
||||
func Test_Android_Backspace(t *testing.T) {
|
||||
setupAndroidAdbDriver(t)
|
||||
|
||||
err := driverExt.Driver.Backspace(1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,8 +294,8 @@ func (ud *uiaDriver) Orientation() (orientation Orientation, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) DoubleTap(x, y int, options ...ActionOption) error {
|
||||
return ud.DoubleFloatTap(float64(x), float64(y))
|
||||
func (ud *uiaDriver) DoubleTap(x, y float64, options ...ActionOption) error {
|
||||
return ud.DoubleFloatTap(x, y)
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) DoubleFloatTap(x, y float64) error {
|
||||
@@ -362,20 +362,18 @@ func (ud *uiaDriver) TapFloat(x, y float64, options ...ActionOption) (err error)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) TouchAndHold(x, y int, second ...float64) (err error) {
|
||||
return ud.TouchAndHoldFloat(float64(x), float64(y), second...)
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) TouchAndHoldFloat(x, y float64, second ...float64) (err error) {
|
||||
if len(second) == 0 {
|
||||
second = []float64{1.0}
|
||||
func (ud *uiaDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) {
|
||||
opts := NewActionOptions(options...)
|
||||
duration := opts.Duration
|
||||
if duration == 0 {
|
||||
duration = 1.0
|
||||
}
|
||||
// register(postHandler, new TouchLongClick("/wd/hub/session/:sessionId/touch/longclick"))
|
||||
data := map[string]interface{}{
|
||||
"params": map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
"duration": int(second[0] * 1000),
|
||||
"duration": int(duration * 1000),
|
||||
},
|
||||
}
|
||||
_, err = ud.httpPOST(data, "/session", ud.session.ID, "touch/longclick")
|
||||
@@ -386,11 +384,7 @@ func (ud *uiaDriver) TouchAndHoldFloat(x, y float64, second ...float64) (err err
|
||||
// the smoothness and speed of the swipe by specifying the number of steps.
|
||||
// Each step execution is throttled to 5 milliseconds per step, so for a 100
|
||||
// steps, the swipe will take around 0.5 seconds to complete.
|
||||
func (ud *uiaDriver) Drag(fromX, fromY, toX, toY int, options ...ActionOption) error {
|
||||
return ud.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) DragFloat(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
|
||||
func (ud *uiaDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
|
||||
actionOptions := NewActionOptions(options...)
|
||||
if len(actionOptions.Offset) == 4 {
|
||||
fromX += float64(actionOptions.Offset[0])
|
||||
@@ -662,3 +656,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
|
||||
}
|
||||
|
||||
@@ -62,8 +62,9 @@ type Driver struct {
|
||||
client *http.Client
|
||||
|
||||
// cache to avoid repeated query
|
||||
scale float64
|
||||
windowSize *Size
|
||||
scale float64
|
||||
windowSize *Size
|
||||
driverResults []*DriverResult
|
||||
|
||||
// cache session data
|
||||
session DriverSession
|
||||
|
||||
@@ -1,27 +1,42 @@
|
||||
package uixt
|
||||
|
||||
func (dExt *DriverExt) Drag(pathname string, toX, toY int, pressForDuration ...float64) (err error) {
|
||||
return dExt.DragFloat(pathname, float64(toX), float64(toY), pressForDuration...)
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (dExt *DriverExt) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
|
||||
return dExt.Driver.Drag(fromX, fromY, toX, toY, options...)
|
||||
}
|
||||
|
||||
func (dExt *DriverExt) DragFloat(pathname string, toX, toY float64, pressForDuration ...float64) (err error) {
|
||||
return dExt.DragOffsetFloat(pathname, toX, toY, 0, 0, pressForDuration...)
|
||||
}
|
||||
|
||||
func (dExt *DriverExt) DragOffset(pathname string, toX, toY int, xOffset, yOffset float64, pressForDuration ...float64) (err error) {
|
||||
return dExt.DragOffsetFloat(pathname, float64(toX), float64(toY), xOffset, yOffset, pressForDuration...)
|
||||
}
|
||||
|
||||
func (dExt *DriverExt) DragOffsetFloat(pathname string, toX, toY, xOffset, yOffset float64, pressForDuration ...float64) (err error) {
|
||||
if len(pressForDuration) == 0 {
|
||||
pressForDuration = []float64{1.0}
|
||||
}
|
||||
|
||||
point, err := dExt.FindUIRectInUIKit(pathname)
|
||||
func (dExt *DriverExt) DragRelative(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
|
||||
width := dExt.windowSize.Width
|
||||
height := dExt.windowSize.Height
|
||||
orientation, err := dExt.Driver.Orientation()
|
||||
if err != nil {
|
||||
return err
|
||||
log.Warn().Err(err).Msgf("drag from (%v, %v) to (%v, %v) get orientation failed, use default orientation",
|
||||
fromX, fromY, toX, toY)
|
||||
orientation = OrientationPortrait
|
||||
}
|
||||
|
||||
return dExt.Driver.DragFloat(point.X+xOffset, point.Y+yOffset, toX, toY,
|
||||
WithPressDuration(pressForDuration[0]))
|
||||
if !assertRelative(fromX) || !assertRelative(fromY) ||
|
||||
!assertRelative(toX) || !assertRelative(toY) {
|
||||
return fmt.Errorf("fromX(%f), fromY(%f), toX(%f), toY(%f) must be less than 1",
|
||||
fromX, fromY, toX, toY)
|
||||
}
|
||||
// 左转和右转都是"LANDSCAPE"
|
||||
if orientation == OrientationPortrait {
|
||||
fromX = float64(width) * fromX
|
||||
fromY = float64(height) * fromY
|
||||
toX = float64(width) * toX
|
||||
toY = float64(height) * toY
|
||||
} else {
|
||||
fromX = float64(height) * fromX
|
||||
fromY = float64(width) * fromY
|
||||
toX = float64(height) * toX
|
||||
toY = float64(width) * toY
|
||||
}
|
||||
|
||||
return dExt.Driver.Drag(fromX, fromY, toX, toY, options...)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type DriverExt struct {
|
||||
Driver IWebDriver
|
||||
ImageService IImageService // used to extract image data
|
||||
|
||||
windowSize Size
|
||||
// funplugin
|
||||
plugin funplugin.IPlugin
|
||||
}
|
||||
|
||||
@@ -145,22 +145,6 @@ func (dev *HarmonyDevice) NewUSBDriver(options ...DriverOption) (driver IWebDriv
|
||||
return harmonyDriver, nil
|
||||
}
|
||||
|
||||
func (dev *HarmonyDevice) StartPerf() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dev *HarmonyDevice) StopPerf() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (dev *HarmonyDevice) StartPcap() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dev *HarmonyDevice) StopPcap() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (dev *HarmonyDevice) Install(appPath string, options ...InstallOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -175,27 +175,15 @@ func (hd *hdcDriver) TapFloat(x, y float64, options ...ActionOption) error {
|
||||
return hd.uiDriver.InjectGesture(ghdc.NewGesture().Start(ghdc.Point{X: int(x), Y: int(y)}).Pause(100))
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) DoubleTap(x, y int, options ...ActionOption) error {
|
||||
func (hd *hdcDriver) DoubleTap(x, y float64, options ...ActionOption) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) DoubleTapFloat(x, y float64, options ...ActionOption) error {
|
||||
func (hd *hdcDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) TouchAndHold(x, y int, second ...float64) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) TouchAndHoldFloat(x, y float64, second ...float64) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) Drag(fromX, fromY, toX, toY int, options ...ActionOption) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) DragFloat(fromX, fromY, toX, toY float64, options ...ActionOption) error {
|
||||
func (hd *hdcDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
@@ -260,6 +248,10 @@ func (hd *hdcDriver) PressBack(options ...ActionOption) error {
|
||||
return hd.uiDriver.PressBack()
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) Backspace(count int, options ...ActionOption) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) PressKeyCode(keyCode KeyCode) (err error) {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
@@ -292,8 +284,9 @@ func (hd *hdcDriver) Source(srcOpt ...SourceOption) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) LoginNoneUI(packageName, phoneNumber string, captcha string) error {
|
||||
return errDriverNotImplemented
|
||||
func (hd *hdcDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) LogoutNoneUI(packageName string) error {
|
||||
@@ -336,3 +329,14 @@ func (hd *hdcDriver) StopCaptureLog() (result interface{}, err error) {
|
||||
// defer clear(hd.points)
|
||||
return hd.points, nil
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) GetDriverResults() []*DriverResult {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (hd *hdcDriver) TearDown() {
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestHarmonyTap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwipe(t *testing.T) {
|
||||
func TestHarmonySwipe(t *testing.T) {
|
||||
setupHarmonyDevice(t)
|
||||
err := harmonyDriverExt.SwipeLeft()
|
||||
if err != nil {
|
||||
@@ -49,7 +49,7 @@ func TestSwipe(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInput(t *testing.T) {
|
||||
func TestHarmonyInput(t *testing.T) {
|
||||
setupHarmonyDevice(t)
|
||||
err := harmonyDriverExt.Input("test")
|
||||
if err != nil {
|
||||
|
||||
@@ -446,12 +446,14 @@ type DriverOptions struct {
|
||||
plugin funplugin.IPlugin
|
||||
withImageService bool
|
||||
withResultFolder bool
|
||||
withUIAction bool
|
||||
}
|
||||
|
||||
func NewDriverOptions() *DriverOptions {
|
||||
return &DriverOptions{
|
||||
withImageService: true,
|
||||
withResultFolder: true,
|
||||
withUIAction: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,6 +477,12 @@ func WithDriverResultFolder(withResultFolder bool) DriverOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithUIAction(withUIAction bool) DriverOption {
|
||||
return func(options *DriverOptions) {
|
||||
options.withUIAction = withUIAction
|
||||
}
|
||||
}
|
||||
|
||||
func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption {
|
||||
return func(options *DriverOptions) {
|
||||
options.plugin = plugin
|
||||
@@ -490,17 +498,13 @@ type IDevice interface {
|
||||
// TODO: add ctx to NewDriver
|
||||
NewDriver(...DriverOption) (driverExt *DriverExt, err error)
|
||||
|
||||
StartPerf() error
|
||||
StopPerf() string
|
||||
|
||||
StartPcap() error
|
||||
StopPcap() string
|
||||
|
||||
Install(appPath string, options ...InstallOption) error
|
||||
Uninstall(packageName string) error
|
||||
|
||||
GetPackageInfo(packageName string) (AppInfo, error)
|
||||
GetCurrentWindow() (windowInfo WindowInfo, err error)
|
||||
|
||||
// Teardown() error
|
||||
}
|
||||
|
||||
type ForegroundApp struct {
|
||||
@@ -577,18 +581,15 @@ type IWebDriver interface {
|
||||
TapFloat(x, y float64, options ...ActionOption) error
|
||||
|
||||
// DoubleTap Sends a double tap event at the coordinate.
|
||||
DoubleTap(x, y int, options ...ActionOption) error
|
||||
DoubleTapFloat(x, y float64, options ...ActionOption) error
|
||||
DoubleTap(x, y float64, options ...ActionOption) error
|
||||
|
||||
// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration.
|
||||
// second: The default value is 1
|
||||
TouchAndHold(x, y int, second ...float64) error
|
||||
TouchAndHoldFloat(x, y float64, second ...float64) error
|
||||
TouchAndHold(x, y float64, options ...ActionOption) error
|
||||
|
||||
// Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate.
|
||||
// WithPressDurationOption option can be used to set pressForDuration (default to 1 second).
|
||||
Drag(fromX, fromY, toX, toY int, options ...ActionOption) error
|
||||
DragFloat(fromX, fromY, toX, toY float64, options ...ActionOption) error
|
||||
Drag(fromX, fromY, toX, toY float64, options ...ActionOption) error
|
||||
|
||||
// Swipe works like Drag, but `pressForDuration` value is 0
|
||||
Swipe(fromX, fromY, toX, toY int, options ...ActionOption) error
|
||||
@@ -620,12 +621,14 @@ type IWebDriver interface {
|
||||
|
||||
PressKeyCode(keyCode KeyCode) (err error)
|
||||
|
||||
Backspace(count int, options ...ActionOption) (err error)
|
||||
|
||||
Screenshot() (*bytes.Buffer, error)
|
||||
|
||||
// Source Return application elements tree
|
||||
Source(srcOpt ...SourceOption) (string, error)
|
||||
|
||||
LoginNoneUI(packageName, phoneNumber string, captcha string) error
|
||||
LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error)
|
||||
LogoutNoneUI(packageName string) error
|
||||
|
||||
TapByText(text string, options ...ActionOption) error
|
||||
@@ -647,4 +650,9 @@ type IWebDriver interface {
|
||||
// triggers the log capture and returns the log entries
|
||||
StartCaptureLog(identifier ...string) (err error)
|
||||
StopCaptureLog() (result interface{}, err error)
|
||||
|
||||
GetDriverResults() []*DriverResult
|
||||
RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error)
|
||||
|
||||
TearDown()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,17 @@ import (
|
||||
"encoding/base64"
|
||||
builtinJSON "encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -18,35 +24,32 @@ import (
|
||||
"github.com/httprunner/httprunner/v4/hrp/code"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gidevice"
|
||||
)
|
||||
|
||||
type wdaDriver struct {
|
||||
Driver
|
||||
|
||||
// default port
|
||||
defaultConn gidevice.InnerConn
|
||||
|
||||
// mjpeg port
|
||||
mjpegUSBConn gidevice.InnerConn // via USB
|
||||
mjpegHTTPConn net.Conn // via HTTP
|
||||
udid string
|
||||
device *IOSDevice
|
||||
mjpegHTTPConn net.Conn // via HTTP
|
||||
mjpegClient *http.Client
|
||||
mjpegUrl string
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) resetSession() error {
|
||||
capabilities := NewCapabilities()
|
||||
capabilities.WithDefaultAlertAction(AlertActionAccept)
|
||||
|
||||
_, err := wd.NewSession(capabilities)
|
||||
sessionInfo, err := wd.NewSession(capabilities)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wd.session.ID = sessionInfo.SessionId
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) httpRequest(method string, rawURL string, rawBody []byte, disableRetry ...bool) (rawResp rawResponse, err error) {
|
||||
disableRetryBool := len(disableRetry) > 0 && disableRetry[0]
|
||||
for retryCount := 1; retryCount <= 5; retryCount++ {
|
||||
for retryCount := 1; retryCount <= 2; retryCount++ {
|
||||
rawResp, err = wd.Driver.httpRequest(method, rawURL, rawBody)
|
||||
if err == nil || disableRetryBool {
|
||||
return
|
||||
@@ -108,19 +111,10 @@ func (wd *wdaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionI
|
||||
if sessionInfo, err = rawResp.valueConvertToSessionInfo(); err != nil {
|
||||
return SessionInfo{}, err
|
||||
}
|
||||
wd.Driver.session.Reset()
|
||||
wd.Driver.session.ID = sessionInfo.SessionId
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) DeleteSession() (err error) {
|
||||
if wd.defaultConn != nil {
|
||||
wd.defaultConn.Close()
|
||||
}
|
||||
if wd.mjpegUSBConn != nil {
|
||||
wd.mjpegUSBConn.Close()
|
||||
}
|
||||
|
||||
if wd.mjpegClient != nil {
|
||||
wd.mjpegClient.CloseIdleConnections()
|
||||
}
|
||||
@@ -399,6 +393,9 @@ func (wd *wdaDriver) AppLaunch(bundleId string) (err error) {
|
||||
// [[FBRoute POST:@"/wda/apps/launch"] respondWithTarget:self action:@selector(handleSessionAppLaunch:)]
|
||||
data := make(map[string]interface{})
|
||||
data["bundleId"] = bundleId
|
||||
data["environment"] = map[string]interface{}{
|
||||
"SHOW_EXPLORER": "NO",
|
||||
}
|
||||
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/apps/launch")
|
||||
if err != nil {
|
||||
return errors.Wrap(code.MobileUILaunchAppError,
|
||||
@@ -448,20 +445,30 @@ func (wd *wdaDriver) AppDeactivate(second float64) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) GetForegroundApp() (app AppInfo, err error) {
|
||||
// appInfo, err := wd.ActiveAppInfo()
|
||||
// if err != nil {
|
||||
// return AppInfo{}, err
|
||||
// }
|
||||
|
||||
// app = AppInfo{
|
||||
// AppBaseInfo: AppBaseInfo{
|
||||
// PackageName: appInfo.BundleId,
|
||||
// Activity: "",
|
||||
// },
|
||||
// }
|
||||
return AppInfo{}, errors.Wrap(errDriverNotImplemented,
|
||||
"GetForegroundApp not implemented for ios")
|
||||
func (wd *wdaDriver) GetForegroundApp() (appInfo AppInfo, err error) {
|
||||
activeAppInfo, err := wd.ActiveAppInfo()
|
||||
appInfo.BundleId = activeAppInfo.BundleId
|
||||
if err != nil {
|
||||
return appInfo, err
|
||||
}
|
||||
apps, err := wd.device.ListApps(ApplicationTypeAny)
|
||||
if err != nil {
|
||||
return appInfo, err
|
||||
}
|
||||
for _, app := range apps {
|
||||
if app.CFBundleIdentifier == activeAppInfo.BundleId {
|
||||
appInfo.BundleId = app.CFBundleIdentifier
|
||||
appInfo.AppName = app.CFBundleName
|
||||
appInfo.VersionName = app.CFBundleShortVersionString
|
||||
appInfo.PackageName = app.CFBundleIdentifier
|
||||
versionCode, err := strconv.Atoi(app.CFBundleVersion)
|
||||
if err == nil {
|
||||
appInfo.VersionCode = versionCode
|
||||
}
|
||||
return appInfo, err
|
||||
}
|
||||
}
|
||||
return appInfo, err
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) AssertForegroundApp(bundleId string, viewControllerType ...string) error {
|
||||
@@ -499,43 +506,35 @@ func (wd *wdaDriver) TapFloat(x, y float64, options ...ActionOption) (err error)
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) DoubleTap(x, y int, options ...ActionOption) error {
|
||||
return wd.DoubleTapFloat(float64(x), float64(y), options...)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) DoubleTapFloat(x, y float64, options ...ActionOption) (err error) {
|
||||
func (wd *wdaDriver) DoubleTap(x, y float64, options ...ActionOption) (err error) {
|
||||
// [[FBRoute POST:@"/wda/doubleTap"] respondWithTarget:self action:@selector(handleDoubleTapCoordinate:)]
|
||||
actionOptions := NewActionOptions(options...)
|
||||
x = wd.toScale(x)
|
||||
y = wd.toScale(y)
|
||||
if len(actionOptions.Offset) == 2 {
|
||||
x += float64(actionOptions.Offset[0])
|
||||
y += float64(actionOptions.Offset[1])
|
||||
}
|
||||
x += actionOptions.getRandomOffset()
|
||||
y += actionOptions.getRandomOffset()
|
||||
|
||||
data := map[string]interface{}{
|
||||
"x": wd.toScale(x),
|
||||
"y": wd.toScale(y),
|
||||
"x": x,
|
||||
"y": y,
|
||||
}
|
||||
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/doubleTap")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) TouchAndHold(x, y int, second ...float64) error {
|
||||
return wd.TouchAndHoldFloat(float64(x), float64(y), second...)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) TouchAndHoldFloat(x, y float64, second ...float64) (err error) {
|
||||
// [[FBRoute POST:@"/wda/touchAndHold"] respondWithTarget:self action:@selector(handleTouchAndHoldCoordinate:)]
|
||||
data := map[string]interface{}{
|
||||
"x": wd.toScale(x),
|
||||
"y": wd.toScale(y),
|
||||
func (wd *wdaDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) {
|
||||
actionOptions := NewActionOptions(options...)
|
||||
if actionOptions.Duration == 0 {
|
||||
options = append(options, WithDuration(1))
|
||||
}
|
||||
if len(second) == 0 || second[0] <= 0 {
|
||||
second = []float64{1.0}
|
||||
}
|
||||
data["duration"] = second[0]
|
||||
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/touchAndHold")
|
||||
return
|
||||
return wd.TapFloat(x, y, options...)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) Drag(fromX, fromY, toX, toY int, options ...ActionOption) error {
|
||||
return wd.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) DragFloat(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
|
||||
func (wd *wdaDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
|
||||
// [[FBRoute POST:@"/wda/dragfromtoforduration"] respondWithTarget:self action:@selector(handleDragCoordinate:)]
|
||||
actionOptions := NewActionOptions(options...)
|
||||
|
||||
@@ -560,11 +559,15 @@ func (wd *wdaDriver) DragFloat(fromX, fromY, toX, toY float64, options ...Action
|
||||
"toX": toX,
|
||||
"toY": toY,
|
||||
}
|
||||
if actionOptions.PressDuration > 0 {
|
||||
data["pressDuration"] = actionOptions.PressDuration
|
||||
}
|
||||
|
||||
// update data options in post data for extra WDA configurations
|
||||
actionOptions.updateData(data)
|
||||
// wda 43 version
|
||||
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/dragfromtoforduration")
|
||||
// _, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/drag")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -573,7 +576,7 @@ func (wd *wdaDriver) Swipe(fromX, fromY, toX, toY int, options ...ActionOption)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...ActionOption) error {
|
||||
return wd.DragFloat(fromX, fromY, toX, toY, options...)
|
||||
return wd.Drag(fromX, fromY, toX, toY, options...)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) SetPasteboard(contentType PasteboardType, content string) (err error) {
|
||||
@@ -619,6 +622,20 @@ func (wd *wdaDriver) SendKeys(text string, options ...ActionOption) (err error)
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) Backspace(count int, options ...ActionOption) (err error) {
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
actionOptions := NewActionOptions(options...)
|
||||
data := map[string]interface{}{"count": count}
|
||||
|
||||
// new data options in post data for extra WDA configurations
|
||||
actionOptions.updateData(data)
|
||||
|
||||
_, err = wd.httpPOST(data, "/gtf/interaction/input/backspace")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) Input(text string, options ...ActionOption) (err error) {
|
||||
return wd.SendKeys(text, options...)
|
||||
}
|
||||
@@ -671,8 +688,8 @@ func (wd *wdaDriver) PressButton(devBtn DeviceButton) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) LoginNoneUI(packageName, phoneNumber string, captcha string) error {
|
||||
return errDriverNotImplemented
|
||||
func (wd *wdaDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
|
||||
return info, errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) LogoutNoneUI(packageName string) error {
|
||||
@@ -742,11 +759,13 @@ func (wd *wdaDriver) Screenshot() (raw *bytes.Buffer, err error) {
|
||||
// [[FBRoute GET:@"/screenshot"].withoutSession respondWithTarget:self action:@selector(handleGetScreenshot:)]
|
||||
var rawResp rawResponse
|
||||
if rawResp, err = wd.httpGET("/session", wd.session.ID, "/screenshot"); err != nil {
|
||||
return nil, errors.Wrap(err, "get WDA screenshot data failed")
|
||||
return nil, errors.Wrap(code.DeviceScreenShotError,
|
||||
fmt.Sprintf("get WDA screenshot data failed: %v", err))
|
||||
}
|
||||
|
||||
if raw, err = rawResp.valueDecodeAsBase64(); err != nil {
|
||||
return nil, errors.Wrap(err, "decode WDA screenshot data failed")
|
||||
return nil, errors.Wrap(code.DeviceScreenShotError,
|
||||
fmt.Sprintf("decode WDA screenshot data failed: %v", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -864,6 +883,70 @@ func (wd *wdaDriver) triggerWDALog(data map[string]interface{}) (rawResp []byte,
|
||||
return wd.httpPOST(data, "/gtf/automation/log")
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
|
||||
// 获取当前时间戳
|
||||
timestamp := time.Now().Format("20060102_150405") + fmt.Sprintf("_%03d", time.Now().UnixNano()/1e6%1000)
|
||||
// 创建文件名
|
||||
fileName := fmt.Sprintf("%s/%s.mp4", folderPath, timestamp)
|
||||
err = os.MkdirAll(folderPath, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error creating directory")
|
||||
}
|
||||
// 创建一个文件
|
||||
file, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating file:", err)
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
// 确保文件在程序结束时被删除
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
// ffmpeg 命令
|
||||
cmd := exec.Command(
|
||||
"ffmpeg",
|
||||
"-use_wallclock_as_timestamps", "1",
|
||||
"-f", "mjpeg",
|
||||
"-y",
|
||||
"-r", "10",
|
||||
"-i", "http://"+wd.mjpegUrl,
|
||||
"-c:v", "libx264",
|
||||
"-vf", "pad=width=ceil(iw/2)*2:height=ceil(ih/2)*2",
|
||||
fileName,
|
||||
)
|
||||
cmd.Stdout = io.Discard
|
||||
cmd.Stderr = io.Discard
|
||||
// 启动命令
|
||||
if err := cmd.Start(); err != nil {
|
||||
fmt.Println("Error starting ffmpeg command:", err)
|
||||
return "", err
|
||||
}
|
||||
timer := time.After(duration)
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
// 等待 ffmpeg 命令执行完毕
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
select {
|
||||
case <-timer:
|
||||
// 超时,停止 ffmpeg 进程
|
||||
fmt.Println("Time is up, stopping ffmpeg command...")
|
||||
if err := cmd.Process.Signal(syscall.SIGINT); err != nil {
|
||||
fmt.Println("Error killing ffmpeg process:", err)
|
||||
}
|
||||
case err := <-done:
|
||||
// ffmpeg 正常结束
|
||||
if err != nil {
|
||||
fmt.Println("FFmpeg finished with error:", err)
|
||||
} else {
|
||||
fmt.Println("FFmpeg finished successfully")
|
||||
}
|
||||
}
|
||||
return filepath.Abs(fileName)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) StartCaptureLog(identifier ...string) error {
|
||||
log.Info().Msg("start WDA log recording")
|
||||
if identifier == nil {
|
||||
@@ -903,8 +986,20 @@ func (wd *wdaDriver) StopCaptureLog() (result interface{}, err error) {
|
||||
return reply.Value, nil
|
||||
}
|
||||
|
||||
func (ud *wdaDriver) GetSession() *DriverSession {
|
||||
return &ud.Driver.session
|
||||
func (wd *wdaDriver) GetSession() *DriverSession {
|
||||
return &wd.Driver.session
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) GetDriverResults() []*DriverResult {
|
||||
defer func() {
|
||||
wd.Driver.driverResults = nil
|
||||
}()
|
||||
return wd.Driver.driverResults
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) TearDown() {
|
||||
wd.mjpegClient.CloseIdleConnections()
|
||||
wd.client.CloseIdleConnections()
|
||||
}
|
||||
|
||||
type rawResponse []byte
|
||||
|
||||
569
hrp/pkg/uixt/ios_stub_driver.go
Normal file
569
hrp/pkg/uixt/ios_stub_driver.go
Normal file
@@ -0,0 +1,569 @@
|
||||
package uixt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type stubIOSDriver struct {
|
||||
bightInsightPrefix string
|
||||
serverPrefix string
|
||||
timeout time.Duration
|
||||
Driver
|
||||
*wdaDriver
|
||||
device *IOSDevice
|
||||
}
|
||||
|
||||
func newStubIOSDriver(bightInsightAddr, serverAddr string, dev *IOSDevice, readTimeout ...time.Duration) (*stubIOSDriver, error) {
|
||||
timeout := 10 * time.Second
|
||||
if len(readTimeout) > 0 {
|
||||
timeout = readTimeout[0]
|
||||
}
|
||||
driver := new(stubIOSDriver)
|
||||
driver.device = dev
|
||||
driver.bightInsightPrefix = bightInsightAddr
|
||||
driver.serverPrefix = serverAddr
|
||||
driver.timeout = timeout
|
||||
driver.Driver.client = &http.Client{
|
||||
Timeout: time.Second * 20, // 设置超时时间为 10 秒
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) setUpWda() (err error) {
|
||||
if s.wdaDriver == nil {
|
||||
capabilities := NewCapabilities()
|
||||
capabilities.WithDefaultAlertAction(AlertActionAccept)
|
||||
driver, err := s.device.NewHTTPDriver(capabilities)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("stub driver failed to init wda driver")
|
||||
return err
|
||||
}
|
||||
s.wdaDriver = driver.(*wdaDriver)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSession starts a new session and returns the SessionInfo.
|
||||
func (s *stubIOSDriver) NewSession(capabilities Capabilities) (SessionInfo, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return SessionInfo{}, err
|
||||
}
|
||||
return s.wdaDriver.NewSession(capabilities)
|
||||
}
|
||||
|
||||
// DeleteSession Kills application associated with that session and removes session
|
||||
// 1. alertsMonitor disable
|
||||
// 2. testedApplicationBundleId terminate
|
||||
func (s *stubIOSDriver) DeleteSession() error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.DeleteSession()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) Status() (DeviceStatus, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return DeviceStatus{}, err
|
||||
}
|
||||
return s.wdaDriver.Status()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) DeviceInfo() (DeviceInfo, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return DeviceInfo{}, err
|
||||
}
|
||||
return s.wdaDriver.DeviceInfo()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) Location() (Location, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return Location{}, err
|
||||
}
|
||||
return s.wdaDriver.Location()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) BatteryInfo() (BatteryInfo, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return BatteryInfo{}, err
|
||||
}
|
||||
return s.wdaDriver.BatteryInfo()
|
||||
}
|
||||
|
||||
// WindowSize Return the width and height in portrait mode.
|
||||
// when getting the window size in wda/ui2/adb, if the device is in landscape mode,
|
||||
// the width and height will be reversed.
|
||||
func (s *stubIOSDriver) WindowSize() (Size, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return Size{}, err
|
||||
}
|
||||
return s.wdaDriver.WindowSize()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) Screen() (Screen, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return Screen{}, err
|
||||
}
|
||||
return s.wdaDriver.Screen()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) Scale() (float64, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return s.wdaDriver.Scale()
|
||||
}
|
||||
|
||||
// GetTimestamp returns the timestamp of the mobile device
|
||||
func (s *stubIOSDriver) GetTimestamp() (timestamp int64, err error) {
|
||||
err = s.setUpWda()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return s.wdaDriver.GetTimestamp()
|
||||
}
|
||||
|
||||
// Homescreen Forces the device under test to switch to the home screen
|
||||
func (s *stubIOSDriver) Homescreen() error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.Homescreen()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) Unlock() (err error) {
|
||||
err = s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.Unlock()
|
||||
}
|
||||
|
||||
// AppLaunch Launch an application with given bundle identifier in scope of current session.
|
||||
// !This method is only available since Xcode9 SDK
|
||||
func (s *stubIOSDriver) AppLaunch(packageName string) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.AppLaunch(packageName)
|
||||
}
|
||||
|
||||
// AppTerminate Terminate an application with the given package name.
|
||||
// Either `true` if the app has been successfully terminated or `false` if it was not running
|
||||
func (s *stubIOSDriver) AppTerminate(packageName string) (bool, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return s.wdaDriver.AppTerminate(packageName)
|
||||
}
|
||||
|
||||
// GetForegroundApp returns current foreground app package name and activity name
|
||||
func (s *stubIOSDriver) GetForegroundApp() (app AppInfo, err error) {
|
||||
err = s.setUpWda()
|
||||
if err != nil {
|
||||
return AppInfo{}, err
|
||||
}
|
||||
return s.wdaDriver.GetForegroundApp()
|
||||
}
|
||||
|
||||
// AssertForegroundApp returns nil if the given package and activity are in foreground
|
||||
func (s *stubIOSDriver) AssertForegroundApp(packageName string, activityType ...string) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.AssertForegroundApp(packageName, activityType...)
|
||||
}
|
||||
|
||||
// StartCamera Starts a new camera for recording
|
||||
func (s *stubIOSDriver) StartCamera() error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.StartCamera()
|
||||
}
|
||||
|
||||
// StopCamera Stops the camera for recording
|
||||
func (s *stubIOSDriver) StopCamera() error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.StopCamera()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) Orientation() (orientation Orientation, err error) {
|
||||
err = s.setUpWda()
|
||||
if err != nil {
|
||||
return OrientationPortrait, err
|
||||
}
|
||||
return s.wdaDriver.Orientation()
|
||||
}
|
||||
|
||||
// Tap Sends a tap event at the coordinate.
|
||||
func (s *stubIOSDriver) Tap(x, y int, options ...ActionOption) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.Tap(x, y, options...)
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) TapFloat(x, y float64, options ...ActionOption) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.TapFloat(x, y, options...)
|
||||
}
|
||||
|
||||
// DoubleTap Sends a double tap event at the coordinate.
|
||||
func (s *stubIOSDriver) DoubleTap(x, y float64, options ...ActionOption) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.DoubleTap(x, y, options...)
|
||||
}
|
||||
|
||||
// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration.
|
||||
//
|
||||
// second: The default value is 1
|
||||
func (s *stubIOSDriver) TouchAndHold(x, y float64, options ...ActionOption) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.TouchAndHold(x, y, options...)
|
||||
}
|
||||
|
||||
// Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate.
|
||||
// WithPressDurationOption option can be used to set pressForDuration (default to 1 second).
|
||||
func (s *stubIOSDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.Drag(fromX, fromY, toX, toY, options...)
|
||||
}
|
||||
|
||||
// Swipe works like Drag, but `pressForDuration` value is 0
|
||||
func (s *stubIOSDriver) Swipe(fromX, fromY, toX, toY int, options ...ActionOption) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.Swipe(fromX, fromY, toX, toY, options...)
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...ActionOption) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.SwipeFloat(fromX, fromY, toX, toY, options...)
|
||||
}
|
||||
|
||||
// SetPasteboard Sets data to the general pasteboard
|
||||
func (s *stubIOSDriver) SetPasteboard(contentType PasteboardType, content string) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.SetPasteboard(contentType, content)
|
||||
}
|
||||
|
||||
// GetPasteboard Gets the data contained in the general pasteboard.
|
||||
//
|
||||
// It worked when `WDA` was foreground. https://github.com/appium/WebDriverAgent/issues/330
|
||||
func (s *stubIOSDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error) {
|
||||
err = s.setUpWda()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.wdaDriver.GetPasteboard(contentType)
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) SetIme(ime string) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.SetIme(ime)
|
||||
}
|
||||
|
||||
// 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
|
||||
func (s *stubIOSDriver) SendKeys(text string, options ...ActionOption) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.SendKeys(text, options...)
|
||||
}
|
||||
|
||||
// Input works like SendKeys
|
||||
func (s *stubIOSDriver) Input(text string, options ...ActionOption) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.Input(text, options...)
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) Clear(packageName string) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.Clear(packageName)
|
||||
}
|
||||
|
||||
// PressButton Presses the corresponding hardware button on the device
|
||||
func (s *stubIOSDriver) PressButton(devBtn DeviceButton) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.PressButton(devBtn)
|
||||
}
|
||||
|
||||
// PressBack Presses the back button
|
||||
func (s *stubIOSDriver) PressBack(options ...ActionOption) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.PressBack(options...)
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) PressKeyCode(keyCode KeyCode) (err error) {
|
||||
err = s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.PressKeyCode(keyCode)
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) Screenshot() (*bytes.Buffer, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.wdaDriver.Screenshot()
|
||||
//screenshotService, err := instruments.NewScreenshotService(s.device.d)
|
||||
//if err != nil {
|
||||
// log.Error().Err(err).Msg("Starting screenshot service failed")
|
||||
// return nil, err
|
||||
//}
|
||||
//defer screenshotService.Close()
|
||||
//
|
||||
//imageBytes, err := screenshotService.TakeScreenshot()
|
||||
//if err != nil {
|
||||
// log.Error().Err(err).Msg("failed to task screenshot")
|
||||
// return nil, err
|
||||
//}
|
||||
//return bytes.NewBuffer(imageBytes), nil
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) TapByText(text string, options ...ActionOption) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.TapByText(text, options...)
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) TapByTexts(actions ...TapTextAction) error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.TapByTexts(actions...)
|
||||
}
|
||||
|
||||
// AccessibleSource Return application elements accessibility tree
|
||||
func (s *stubIOSDriver) AccessibleSource() (string, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.wdaDriver.AccessibleSource()
|
||||
}
|
||||
|
||||
// HealthCheck Health check might modify simulator state so it should only be called in-between testing sessions
|
||||
//
|
||||
// Checks health of XCTest by:
|
||||
// 1) Querying application for some elements,
|
||||
// 2) Triggering some device events.
|
||||
func (s *stubIOSDriver) HealthCheck() error {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.HealthCheck()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) GetAppiumSettings() (map[string]interface{}, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.wdaDriver.GetAppiumSettings()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) SetAppiumSettings(settings map[string]interface{}) (map[string]interface{}, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.wdaDriver.SetAppiumSettings(settings)
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) IsHealthy() (bool, error) {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return s.wdaDriver.IsHealthy()
|
||||
}
|
||||
|
||||
// triggers the log capture and returns the log entries
|
||||
func (s *stubIOSDriver) StartCaptureLog(identifier ...string) (err error) {
|
||||
err = s.setUpWda()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.wdaDriver.StartCaptureLog(identifier...)
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) StopCaptureLog() (result interface{}, err error) {
|
||||
err = s.setUpWda()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.wdaDriver.StopCaptureLog()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) GetDriverResults() []*DriverResult {
|
||||
err := s.setUpWda()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return s.wdaDriver.GetDriverResults()
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) Source(srcOpt ...SourceOption) (string, error) {
|
||||
resp, err := s.Driver.httpRequest(http.MethodGet, fmt.Sprintf("%s/source?format=json&onlyWeb=false", s.bightInsightPrefix), []byte{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(resp), nil
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) 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")
|
||||
}
|
||||
bsJSON, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
resp, err := s.Driver.httpRequest(http.MethodPost, fmt.Sprintf("%s/host/login/account/", s.serverPrefix), bsJSON)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
res, err := resp.valueConvertToJsonObject()
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
log.Info().Msgf("%v", res)
|
||||
// {'isSuccess': True, 'data': '登录成功', 'code': 0}
|
||||
if res["isSuccess"] != true {
|
||||
err = fmt.Errorf("falied to logout %s", res["data"])
|
||||
log.Err(err).Msgf("%v", res)
|
||||
return info, err
|
||||
}
|
||||
time.Sleep(20 * time.Second)
|
||||
info, err = s.getLoginAppInfo(packageName)
|
||||
if err != nil || !info.IsLogin {
|
||||
return info, fmt.Errorf("falied to login %v", info)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) LogoutNoneUI(packageName string) error {
|
||||
resp, err := s.Driver.httpRequest(http.MethodGet, fmt.Sprintf("%s/host/loginout/", s.serverPrefix), []byte{})
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) TearDown() {
|
||||
s.Driver.client.CloseIdleConnections()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) getLoginAppInfo(packageName string) (info AppLoginInfo, err error) {
|
||||
resp, err := s.Driver.httpRequest(http.MethodGet, fmt.Sprintf("%s/host/app/info/", s.serverPrefix), []byte{})
|
||||
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 get is login %s", res["data"])
|
||||
log.Err(err).Msgf("%v", res)
|
||||
return info, err
|
||||
}
|
||||
err = json.Unmarshal([]byte(res["data"].(string)), &info)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (s *stubIOSDriver) GetSession() *DriverSession {
|
||||
return &s.Driver.session
|
||||
}
|
||||
105
hrp/pkg/uixt/ios_stub_driver_test.go
Normal file
105
hrp/pkg/uixt/ios_stub_driver_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package uixt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
var (
|
||||
iOSStubDriver IWebDriver
|
||||
iOSDevice *IOSDevice
|
||||
)
|
||||
|
||||
func setupiOSStubDriver(t *testing.T) {
|
||||
var err error
|
||||
iOSDevice, err = NewIOSDevice(WithWDAPort(8700), WithWDAMjpegPort(8800), WithIOSStub(false))
|
||||
checkErr(t, err)
|
||||
iOSStubDriver, err = iOSDevice.NewStubDriver()
|
||||
checkErr(t, err)
|
||||
}
|
||||
|
||||
func TestIOSLogin(t *testing.T) {
|
||||
setupiOSStubDriver(t)
|
||||
info, err := iOSStubDriver.LoginNoneUI("", "12342316231", "8517", "")
|
||||
checkErr(t, err)
|
||||
t.Log(info)
|
||||
}
|
||||
|
||||
func TestIOSLogout(t *testing.T) {
|
||||
setupiOSStubDriver(t)
|
||||
err := iOSStubDriver.LogoutNoneUI("")
|
||||
checkErr(t, err)
|
||||
}
|
||||
|
||||
func TestIOSIsLogin(t *testing.T) {
|
||||
setupiOSStubDriver(t)
|
||||
err := iOSStubDriver.LogoutNoneUI("")
|
||||
checkErr(t, err)
|
||||
}
|
||||
|
||||
func TestIOSSource(t *testing.T) {
|
||||
setupiOSStubDriver(t)
|
||||
source, err := iOSStubDriver.Source()
|
||||
checkErr(t, err)
|
||||
t.Log(source)
|
||||
}
|
||||
|
||||
func TestIOSForeground(t *testing.T) {
|
||||
setupiOSStubDriver(t)
|
||||
app, err := iOSStubDriver.GetForegroundApp()
|
||||
checkErr(t, err)
|
||||
t.Log(app)
|
||||
}
|
||||
|
||||
func TestIOSSwipe(t *testing.T) {
|
||||
setupiOSStubDriver(t)
|
||||
iOSStubDriver.Swipe(540, 0, 540, 1000)
|
||||
}
|
||||
|
||||
func TestIOSSave(t *testing.T) {
|
||||
setupiOSStubDriver(t)
|
||||
raw, err := iOSStubDriver.Screenshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
source, err := iOSStubDriver.Source()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
step := 7
|
||||
file, err := os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/ios/4159417_cvcn02okg4g0/%d.jpg", step))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file.Write(raw.Bytes())
|
||||
|
||||
file, err = os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/ios/4159417_cvcn02okg4g0/%d.json", step))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file.Write([]byte(source))
|
||||
}
|
||||
|
||||
func TestListen(t *testing.T) {
|
||||
setupiOSStubDriver(t)
|
||||
localPort, err := builtin.GetFreePort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = iOSDevice.forward(localPort, 8800)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
addr := fmt.Sprintf("0.0.0.0:%d", localPort)
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err == nil {
|
||||
l.Close() // 端口成功绑定后立即释放,返回该端口号
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func setup(t *testing.T) {
|
||||
}
|
||||
capabilities := NewCapabilities()
|
||||
capabilities.WithDefaultAlertAction(AlertActionAccept)
|
||||
driver, err = device.NewUSBDriver(capabilities)
|
||||
driver, err = device.NewHTTPDriver(capabilities)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func TestInstall(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewIOSDevice(t *testing.T) {
|
||||
device, _ := NewIOSDevice()
|
||||
device, _ := NewIOSDevice(WithWDAPort(8700), WithWDAMjpegPort(8800))
|
||||
if device != nil {
|
||||
t.Log(device)
|
||||
}
|
||||
@@ -70,8 +70,16 @@ func TestNewIOSDevice(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIOSDevice_GetPackageInfo(t *testing.T) {
|
||||
device, err := NewIOSDevice(WithWDAPort(8700))
|
||||
checkErr(t, err)
|
||||
appInfo, err := device.GetPackageInfo("com.apple.Preferences")
|
||||
checkErr(t, err)
|
||||
t.Log(appInfo)
|
||||
}
|
||||
|
||||
func TestNewWDAHTTPDriver(t *testing.T) {
|
||||
device, _ := NewIOSDevice(WithWDAPort(8700), WithWDAMjpegPort(8800))
|
||||
device, _ := NewIOSDevice()
|
||||
var err error
|
||||
_, err = device.NewHTTPDriver(nil)
|
||||
if err != nil {
|
||||
@@ -85,14 +93,6 @@ func TestNewUSBDriver(t *testing.T) {
|
||||
// t.Log(driver.IsWdaHealthy())
|
||||
}
|
||||
|
||||
func TestIOSDevice_GetPackageInfo(t *testing.T) {
|
||||
device, err := NewIOSDevice(WithWDAPort(8700))
|
||||
checkErr(t, err)
|
||||
appInfo, err := device.GetPackageInfo("com.apple.Preferences")
|
||||
checkErr(t, err)
|
||||
t.Log(appInfo)
|
||||
}
|
||||
|
||||
func TestDriver_DeviceScaleRatio(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
@@ -276,7 +276,7 @@ func Test_remoteWD_TouchAndHold(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
// err := driver.TouchAndHold(200, 300)
|
||||
err := driver.TouchAndHold(200, 300, -1)
|
||||
err := driver.TouchAndHold(200, 300)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -286,7 +286,7 @@ func Test_remoteWD_Drag(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
// err := driver.Drag(200, 300, 200, 500, WithDataPressDuration(0.5))
|
||||
err := driver.Swipe(200, 300, 200, 500)
|
||||
err := driver.Drag(200, 300, 200, 500, WithPressDuration(2), WithDuration(3))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -308,7 +308,7 @@ func Test_remoteWD_SetPasteboard(t *testing.T) {
|
||||
// err := driver.SetPasteboard(PasteboardTypePlaintext, "gwda")
|
||||
err := driver.SetPasteboard(PasteboardTypeUrl, "Clock-stopwatch://")
|
||||
// userHomeDir, _ := os.UserHomeDir()
|
||||
// bytesImg, _ := os.ReadFile(userHomeDir + "/Pictures/IMG_0806.jpg")
|
||||
// bytesImg, _ := ioutil.ReadFile(userHomeDir + "/Pictures/IMG_0806.jpg")
|
||||
// err := driver.SetPasteboard(PasteboardTypeImage, string(bytesImg))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -333,21 +333,21 @@ func Test_remoteWD_GetPasteboard(t *testing.T) {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// userHomeDir, _ := os.UserHomeDir()
|
||||
// if err = os.WriteFile(userHomeDir+"/Desktop/p1.png", buffer.Bytes(), 0600); err != nil {
|
||||
// if err = ioutil.WriteFile(userHomeDir+"/Desktop/p1.png", buffer.Bytes(), 0600); err != nil {
|
||||
// t.Error(err)
|
||||
// }
|
||||
}
|
||||
|
||||
func Test_remoteWD_SendKeys(t *testing.T) {
|
||||
setup(t)
|
||||
driver.StartCaptureLog("hrp_wda_log")
|
||||
err := driver.SendKeys("", WithIdentifier("test"))
|
||||
result, _ := driver.StopCaptureLog()
|
||||
// driver.StartCaptureLog("hrp_wda_log")
|
||||
err := driver.SendKeys("test", WithIdentifier("test"))
|
||||
// result, _ := driver.StopCaptureLog()
|
||||
// err := driver.SendKeys("App Store", WithFrequency(3))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(result)
|
||||
// t.Log(result)
|
||||
}
|
||||
|
||||
func Test_remoteWD_PressButton(t *testing.T) {
|
||||
@@ -445,3 +445,21 @@ func Test_remoteWD_AccessibleSource(t *testing.T) {
|
||||
_ = source
|
||||
fmt.Println(source)
|
||||
}
|
||||
|
||||
func TestRecord(t *testing.T) {
|
||||
setup(t)
|
||||
path, err := driver.(*wdaDriver).RecordScreen("", 5*time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
println(path)
|
||||
}
|
||||
|
||||
// func Test_Backspace(t *testing.T) {
|
||||
// setup(t)
|
||||
|
||||
// err := driver.Backspace(3)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -93,7 +93,7 @@ func (dExt *DriverExt) DoubleTapXY(x, y float64, options ...ActionOption) error
|
||||
}
|
||||
x = x * float64(windowSize.Width)
|
||||
y = y * float64(windowSize.Height)
|
||||
err = dExt.Driver.DoubleTapFloat(x, y, options...)
|
||||
err = dExt.Driver.DoubleTap(x, y, options...)
|
||||
if err != nil {
|
||||
return errors.Wrap(code.MobileUITapError, err.Error())
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func (dExt *DriverExt) DoubleTapOffset(param string, xOffset, yOffset float64, o
|
||||
return err
|
||||
}
|
||||
|
||||
err = dExt.Driver.DoubleTapFloat(point.X+xOffset, point.Y+yOffset, options...)
|
||||
err = dExt.Driver.DoubleTap(point.X+xOffset, point.Y+yOffset, options...)
|
||||
if err != nil {
|
||||
return errors.Wrap(code.MobileUITapError, err.Error())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user