mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 15:37:35 +08:00
Merge branch 'feature/xucong.053/player' into 'master'
fix: web ui test See merge request iesqa/httprunner!83
This commit is contained in:
@@ -35,6 +35,7 @@ type TConfig struct {
|
|||||||
IOS []*option.IOSDeviceOptions `json:"ios,omitempty" yaml:"ios,omitempty"`
|
IOS []*option.IOSDeviceOptions `json:"ios,omitempty" yaml:"ios,omitempty"`
|
||||||
Android []*option.AndroidDeviceOptions `json:"android,omitempty" yaml:"android,omitempty"`
|
Android []*option.AndroidDeviceOptions `json:"android,omitempty" yaml:"android,omitempty"`
|
||||||
Harmony []*option.HarmonyDeviceOptions `json:"harmony,omitempty" yaml:"harmony,omitempty"`
|
Harmony []*option.HarmonyDeviceOptions `json:"harmony,omitempty" yaml:"harmony,omitempty"`
|
||||||
|
Browser []*option.BrowserDeviceOptions `json:"browser,omitempty" yaml:"browser,omitempty"`
|
||||||
RequestTimeout float32 `json:"request_timeout,omitempty" yaml:"request_timeout,omitempty"` // request timeout in seconds
|
RequestTimeout float32 `json:"request_timeout,omitempty" yaml:"request_timeout,omitempty"` // request timeout in seconds
|
||||||
CaseTimeout float32 `json:"case_timeout,omitempty" yaml:"case_timeout,omitempty"` // testcase timeout in seconds
|
CaseTimeout float32 `json:"case_timeout,omitempty" yaml:"case_timeout,omitempty"` // testcase timeout in seconds
|
||||||
Export []string `json:"export,omitempty" yaml:"export,omitempty"`
|
Export []string `json:"export,omitempty" yaml:"export,omitempty"`
|
||||||
@@ -185,6 +186,24 @@ func (c *TConfig) SetAndroid(opts ...option.AndroidDeviceOption) *TConfig {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *TConfig) SetBrowser(opts ...option.BrowserDeviceOption) *TConfig {
|
||||||
|
browserOptions := option.NewBrowserDeviceOptions(opts...)
|
||||||
|
|
||||||
|
// each device can have its own settings
|
||||||
|
if browserOptions.BrowserID != "" {
|
||||||
|
c.Browser = append(c.Browser, browserOptions)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// device UDID is not specified, settings will be shared
|
||||||
|
if len(c.Browser) == 0 {
|
||||||
|
c.Browser = append(c.Browser, browserOptions)
|
||||||
|
} else {
|
||||||
|
c.Browser[0] = browserOptions
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
// EnablePlugin enables plugin for current testcase.
|
// EnablePlugin enables plugin for current testcase.
|
||||||
// default to disable plugin
|
// default to disable plugin
|
||||||
func (c *TConfig) EnablePlugin() *TConfig {
|
func (c *TConfig) EnablePlugin() *TConfig {
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.40.1-0.20231203135336-87ef8ec48d55 // indirect
|
github.com/quic-go/quic-go v0.40.1-0.20231203135336-87ef8ec48d55 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
|
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
|
||||||
|
|||||||
@@ -237,8 +237,8 @@ github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5
|
|||||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||||
github.com/quic-go/quic-go v0.40.1-0.20231203135336-87ef8ec48d55 h1:I4N3ZRnkZPbDN935Tg8QDf8fRpHp3bZ0U0/L42jBgNE=
|
github.com/quic-go/quic-go v0.40.1-0.20231203135336-87ef8ec48d55 h1:I4N3ZRnkZPbDN935Tg8QDf8fRpHp3bZ0U0/L42jBgNE=
|
||||||
github.com/quic-go/quic-go v0.40.1-0.20231203135336-87ef8ec48d55/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
github.com/quic-go/quic-go v0.40.1-0.20231203135336-87ef8ec48d55/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ=
|
github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v5.0.0-beta-2505071715
|
v5.0.0-beta-2505072245
|
||||||
|
|||||||
@@ -498,6 +498,33 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
|
|||||||
}
|
}
|
||||||
r.uixtDrivers[harmonyDeviceOptions.ConnectKey] = driverExt
|
r.uixtDrivers[harmonyDeviceOptions.ConnectKey] = driverExt
|
||||||
}
|
}
|
||||||
|
// parse browser devices config
|
||||||
|
for _, browserDeviceOptions := range parsedConfig.Browser {
|
||||||
|
err := r.parseDeviceConfig(browserDeviceOptions, parsedConfig.Variables)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(code.InvalidCaseError,
|
||||||
|
fmt.Sprintf("parse browser config failed: %v", err))
|
||||||
|
}
|
||||||
|
device, err := uixt.NewBrowserDevice(browserDeviceOptions.Options()...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "init browser device failed")
|
||||||
|
}
|
||||||
|
if err := device.Setup(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
driver, err := device.NewDriver()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := driver.Setup(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
driverExt, err := uixt.NewXTDriver(driver, aiOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "init browser XTDriver failed")
|
||||||
|
}
|
||||||
|
r.uixtDrivers[browserDeviceOptions.BrowserID] = driverExt
|
||||||
|
}
|
||||||
|
|
||||||
return parsedConfig, nil
|
return parsedConfig, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ func (r *Router) rightClickHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = driver.IDriver.(*uixt.BrowserDriver).
|
err = driver.IDriver.(*uixt.BrowserDriver).
|
||||||
RightClick(rightClickReq.X, rightClickReq.Y)
|
SecondaryClick(rightClickReq.X, rightClickReq.Y)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RenderError(c, err)
|
RenderError(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const (
|
|||||||
StepTypeAndroid StepType = "android"
|
StepTypeAndroid StepType = "android"
|
||||||
StepTypeHarmony StepType = "harmony"
|
StepTypeHarmony StepType = "harmony"
|
||||||
StepTypeIOS StepType = "ios"
|
StepTypeIOS StepType = "ios"
|
||||||
|
stepTypeBrowser StepType = "browser"
|
||||||
StepTypeShell StepType = "shell"
|
StepTypeShell StepType = "shell"
|
||||||
StepTypeFunction StepType = "function"
|
StepTypeFunction StepType = "function"
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ type TStep struct {
|
|||||||
Android *MobileUI `json:"android,omitempty" yaml:"android,omitempty"`
|
Android *MobileUI `json:"android,omitempty" yaml:"android,omitempty"`
|
||||||
Harmony *MobileUI `json:"harmony,omitempty" yaml:"harmony,omitempty"`
|
Harmony *MobileUI `json:"harmony,omitempty" yaml:"harmony,omitempty"`
|
||||||
IOS *MobileUI `json:"ios,omitempty" yaml:"ios,omitempty"`
|
IOS *MobileUI `json:"ios,omitempty" yaml:"ios,omitempty"`
|
||||||
|
Browser *MobileUI `json:"browser,omitempty" yaml:"browser,omitempty"`
|
||||||
Shell *Shell `json:"shell,omitempty" yaml:"shell,omitempty"`
|
Shell *Shell `json:"shell,omitempty" yaml:"shell,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -791,6 +791,17 @@ func (s *StepRequest) Harmony(opts ...option.HarmonyDeviceOption) *StepMobile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Browser creates a new browser step session
|
||||||
|
func (s *StepRequest) Browser(opts ...option.BrowserDeviceOption) *StepMobile {
|
||||||
|
browserOptions := option.NewBrowserDeviceOptions(opts...)
|
||||||
|
return &StepMobile{
|
||||||
|
StepConfig: s.StepConfig,
|
||||||
|
Browser: &MobileUI{
|
||||||
|
Serial: browserOptions.BrowserID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Shell creates a new shell step session
|
// Shell creates a new shell step session
|
||||||
func (s *StepRequest) Shell(content string) *StepShell {
|
func (s *StepRequest) Shell(content string) *StepShell {
|
||||||
return &StepShell{
|
return &StepShell{
|
||||||
|
|||||||
+74
-2
@@ -28,8 +28,8 @@ type StepMobile struct {
|
|||||||
Android *MobileUI `json:"android,omitempty" yaml:"android,omitempty"`
|
Android *MobileUI `json:"android,omitempty" yaml:"android,omitempty"`
|
||||||
Harmony *MobileUI `json:"harmony,omitempty" yaml:"harmony,omitempty"`
|
Harmony *MobileUI `json:"harmony,omitempty" yaml:"harmony,omitempty"`
|
||||||
IOS *MobileUI `json:"ios,omitempty" yaml:"ios,omitempty"`
|
IOS *MobileUI `json:"ios,omitempty" yaml:"ios,omitempty"`
|
||||||
|
Browser *MobileUI `json:"browser,omitempty" yaml:"browser,omitempty"`
|
||||||
cache *MobileUI // used for caching
|
cache *MobileUI // used for caching
|
||||||
}
|
}
|
||||||
|
|
||||||
// uniform interface for all types of mobile systems
|
// uniform interface for all types of mobile systems
|
||||||
@@ -50,6 +50,10 @@ func (s *StepMobile) obj() *MobileUI {
|
|||||||
s.cache = s.Android
|
s.cache = s.Android
|
||||||
s.cache.OSType = string(StepTypeAndroid)
|
s.cache.OSType = string(StepTypeAndroid)
|
||||||
return s.cache
|
return s.cache
|
||||||
|
} else if s.Browser != nil {
|
||||||
|
s.cache = s.Browser
|
||||||
|
s.cache.OSType = string(stepTypeBrowser)
|
||||||
|
return s.cache
|
||||||
} else if s.Mobile != nil {
|
} else if s.Mobile != nil {
|
||||||
s.cache = s.Mobile
|
s.cache = s.Mobile
|
||||||
return s.cache
|
return s.cache
|
||||||
@@ -79,6 +83,14 @@ func (s *StepMobile) InstallApp(path string) *StepMobile {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StepMobile) WebLoginNoneUI(packageName, phoneNumber string, captcha, password string) *StepMobile {
|
||||||
|
s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{
|
||||||
|
Method: uixt.ACTION_WebLoginNoneUI,
|
||||||
|
Params: []string{packageName, phoneNumber, captcha, password},
|
||||||
|
})
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StepMobile) AppLaunch(bundleId string) *StepMobile {
|
func (s *StepMobile) AppLaunch(bundleId string) *StepMobile {
|
||||||
s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{
|
s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{
|
||||||
Method: uixt.ACTION_AppLaunch,
|
Method: uixt.ACTION_AppLaunch,
|
||||||
@@ -286,6 +298,66 @@ func (s *StepMobile) SwipeToTapTexts(texts interface{}, opts ...option.ActionOpt
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StepMobile) SecondaryClick(x, y float64, options ...option.ActionOption) *StepMobile {
|
||||||
|
action := uixt.MobileAction{
|
||||||
|
Method: uixt.ACTION_SecondaryClick,
|
||||||
|
Params: []float64{x, y},
|
||||||
|
Options: option.NewActionOptions(options...),
|
||||||
|
}
|
||||||
|
s.obj().Actions = append(s.obj().Actions, action)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepMobile) SecondaryClickBySelector(selector string, options ...option.ActionOption) *StepMobile {
|
||||||
|
action := uixt.MobileAction{
|
||||||
|
Method: uixt.ACTION_SecondaryClickBySelector,
|
||||||
|
Params: selector,
|
||||||
|
Options: option.NewActionOptions(options...),
|
||||||
|
}
|
||||||
|
s.obj().Actions = append(s.obj().Actions, action)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepMobile) HoverBySelector(selector string, options ...option.ActionOption) *StepMobile {
|
||||||
|
action := uixt.MobileAction{
|
||||||
|
Method: uixt.ACTION_HoverBySelector,
|
||||||
|
Params: selector,
|
||||||
|
Options: option.NewActionOptions(options...),
|
||||||
|
}
|
||||||
|
s.obj().Actions = append(s.obj().Actions, action)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepMobile) TapBySelector(selector string, options ...option.ActionOption) *StepMobile {
|
||||||
|
action := uixt.MobileAction{
|
||||||
|
Method: uixt.ACTION_TapBySelector,
|
||||||
|
Params: selector,
|
||||||
|
Options: option.NewActionOptions(options...),
|
||||||
|
}
|
||||||
|
s.obj().Actions = append(s.obj().Actions, action)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepMobile) WebCloseTab(idx int, options ...option.ActionOption) *StepMobile {
|
||||||
|
action := uixt.MobileAction{
|
||||||
|
Method: uixt.ACTION_WebCloseTab,
|
||||||
|
Params: idx,
|
||||||
|
Options: option.NewActionOptions(options...),
|
||||||
|
}
|
||||||
|
s.obj().Actions = append(s.obj().Actions, action)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepMobile) GetElementTextBySelector(selector string, options ...option.ActionOption) *StepMobile {
|
||||||
|
action := uixt.MobileAction{
|
||||||
|
Method: uixt.ACTION_GetElementTextBySelector,
|
||||||
|
Params: selector,
|
||||||
|
Options: option.NewActionOptions(options...),
|
||||||
|
}
|
||||||
|
s.obj().Actions = append(s.obj().Actions, action)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StepMobile) Input(text string, opts ...option.ActionOption) *StepMobile {
|
func (s *StepMobile) Input(text string, opts ...option.ActionOption) *StepMobile {
|
||||||
action := uixt.MobileAction{
|
action := uixt.MobileAction{
|
||||||
Method: uixt.ACTION_Input,
|
Method: uixt.ACTION_Input,
|
||||||
|
|||||||
@@ -251,6 +251,12 @@ func (tc *TestCaseDef) loadISteps() (*TestCase, error) {
|
|||||||
StepConfig: step.StepConfig,
|
StepConfig: step.StepConfig,
|
||||||
Android: step.Android,
|
Android: step.Android,
|
||||||
})
|
})
|
||||||
|
} else if step.Browser != nil {
|
||||||
|
testCase.TestSteps = append(testCase.TestSteps, &StepMobile{
|
||||||
|
StepConfig: step.StepConfig,
|
||||||
|
Browser: step.Browser,
|
||||||
|
})
|
||||||
|
|
||||||
} else if step.Shell != nil {
|
} else if step.Shell != nil {
|
||||||
testCase.TestSteps = append(testCase.TestSteps, &StepShell{
|
testCase.TestSteps = append(testCase.TestSteps, &StepShell{
|
||||||
StepConfig: step.StepConfig,
|
StepConfig: step.StepConfig,
|
||||||
|
|||||||
@@ -566,15 +566,6 @@ func (ad *ADBDriver) ScreenShot(opts ...option.ActionOption) (raw *bytes.Buffer,
|
|||||||
return raw, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *ADBDriver) TapByHierarchy(text string, opts ...option.ActionOption) error {
|
|
||||||
log.Info().Str("text", text).Msg("ADBDriver.TapByHierarchy")
|
|
||||||
sourceTree, err := ad.sourceTree()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ad.tapByTextUsingHierarchy(sourceTree, text, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ad *ADBDriver) Source(srcOpt ...option.SourceOption) (source string, err error) {
|
func (ad *ADBDriver) Source(srcOpt ...option.SourceOption) (source string, err error) {
|
||||||
_, err = ad.runShellCommand("rm", "-rf", "/sdcard/window_dump.xml")
|
_, err = ad.runShellCommand("rm", "-rf", "/sdcard/window_dump.xml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1123,3 +1114,24 @@ func ConvertPoints(lines []string) (eps []ExportPoint) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ad *ADBDriver) HoverBySelector(selector string, options ...option.ActionOption) (err error) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ad *ADBDriver) TapBySelector(text string, opts ...option.ActionOption) error {
|
||||||
|
log.Info().Str("text", text).Msg("ADBDriver.TapByHierarchy")
|
||||||
|
sourceTree, err := ad.sourceTree()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ad.tapByTextUsingHierarchy(sourceTree, text, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ad *ADBDriver) SecondaryClick(x, y float64) (err error) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ad *ADBDriver) SecondaryClickBySelector(selector string, options ...option.ActionOption) (err error) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@@ -77,12 +78,13 @@ func (ud *UIA2Driver) Setup() error {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// start uiautomator2 server
|
// start uiautomator2 server
|
||||||
// go func() {
|
// Todo: keep-alive
|
||||||
// if err := ud.startUIA2Server(); err != nil {
|
go func() {
|
||||||
// log.Fatal().Err(err).Msg("start UIA2 failed")
|
if err := ud.startUIA2Server(); err != nil {
|
||||||
// }
|
log.Fatal().Err(err).Msg("start UIA2 failed")
|
||||||
// }()
|
}
|
||||||
// time.Sleep(5 * time.Second) // wait for uiautomator2 server start
|
}()
|
||||||
|
time.Sleep(5 * time.Second) // wait for uiautomator2 server start
|
||||||
|
|
||||||
// create new session
|
// create new session
|
||||||
err = ud.InitSession(nil)
|
err = ud.InitSession(nil)
|
||||||
@@ -584,7 +586,7 @@ func (ud *UIA2Driver) Source(srcOpt ...option.SourceOption) (source string, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ud *UIA2Driver) startUIA2Server() error {
|
func (ud *UIA2Driver) startUIA2Server() error {
|
||||||
const maxRetries = 3
|
const maxRetries = 20
|
||||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||||
log.Info().Str("package", ud.Device.Options.UIA2ServerTestPackageName).
|
log.Info().Str("package", ud.Device.Options.UIA2ServerTestPackageName).
|
||||||
Int("attempt", attempt).Msg("start uiautomator server")
|
Int("attempt", attempt).Msg("start uiautomator server")
|
||||||
@@ -594,7 +596,7 @@ func (ud *UIA2Driver) startUIA2Server() error {
|
|||||||
out, err := ud.Device.RunShellCommand("am", "instrument", "-w",
|
out, err := ud.Device.RunShellCommand("am", "instrument", "-w",
|
||||||
ud.Device.Options.UIA2ServerTestPackageName)
|
ud.Device.Options.UIA2ServerTestPackageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "start uiautomator server failed")
|
log.Error().Err(err).Int("retryCount", maxRetries).Msg("start uiautomator server failed, retrying...")
|
||||||
}
|
}
|
||||||
if strings.Contains(out, "Process crashed") {
|
if strings.Contains(out, "Process crashed") {
|
||||||
log.Error().Msg("uiautomator server crashed, retrying...")
|
log.Error().Msg("uiautomator server crashed, retrying...")
|
||||||
|
|||||||
+89
-103
@@ -36,7 +36,7 @@ type CreateBrowserResponse struct {
|
|||||||
|
|
||||||
type BrowserDriver struct {
|
type BrowserDriver struct {
|
||||||
urlPrefix *url.URL
|
urlPrefix *url.URL
|
||||||
sessionId string
|
Session *DriverSession
|
||||||
}
|
}
|
||||||
|
|
||||||
type BrowserInfo struct {
|
type BrowserInfo struct {
|
||||||
@@ -99,44 +99,53 @@ func NewBrowserDriver(device *BrowserDevice) (driver *BrowserDriver, err error)
|
|||||||
driver.urlPrefix = &url.URL{}
|
driver.urlPrefix = &url.URL{}
|
||||||
driver.urlPrefix.Host = BROWSER_LOCAL_ADDRESS
|
driver.urlPrefix.Host = BROWSER_LOCAL_ADDRESS
|
||||||
driver.urlPrefix.Scheme = "http"
|
driver.urlPrefix.Scheme = "http"
|
||||||
driver.sessionId = device.UUID()
|
driver.Session = NewDriverSession()
|
||||||
|
driver.Session.ID = device.UUID()
|
||||||
return driver, nil
|
return driver, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) Drag(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error {
|
func (wd *BrowserDriver) Setup() error {
|
||||||
var err error
|
err := wd.Session.SetupPortForward(8093)
|
||||||
fromX, fromY, toX, toY, err = handlerDrag(wd, fromX, fromY, toX, toY, opts...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
wd.Session.SetBaseURL(BROWSER_LOCAL_ADDRESS)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wd *BrowserDriver) Drag(fromX, fromY, toX, toY float64, options ...option.ActionOption) (err error) {
|
||||||
|
fromX, fromY, toX, toY, err = handlerDrag(wd, fromX, fromY, toX, toY, options...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"from_x": fromX,
|
"from_x": fromX,
|
||||||
"from_y": fromY,
|
"from_y": fromY,
|
||||||
"to_x": toX,
|
"to_x": toX,
|
||||||
"to_y": toY,
|
"to_y": toY,
|
||||||
}
|
}
|
||||||
|
actionOptions := option.NewActionOptions(options...)
|
||||||
|
|
||||||
actionOptions := option.NewActionOptions(opts...)
|
|
||||||
if actionOptions.Duration > 0 {
|
if actionOptions.Duration > 0 {
|
||||||
data["duration"] = actionOptions.Duration
|
data["duration"] = actionOptions.Duration
|
||||||
|
} else {
|
||||||
|
data["duration"] = 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/drag")
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/drag"))
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) AppLaunch(packageName string) (err error) {
|
func (wd *BrowserDriver) AppLaunch(packageName string) (err error) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"url": packageName,
|
"url": packageName,
|
||||||
}
|
}
|
||||||
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/page_launch"))
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/page_launch")
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) DeleteSession() (err error) {
|
func (wd *BrowserDriver) DeleteSession() (err error) {
|
||||||
url := wd.concatURL("context", wd.sessionId)
|
url := wd.concatURL("context", wd.Session.ID)
|
||||||
|
|
||||||
req, err := http.NewRequest("DELETE", url, nil)
|
req, err := http.NewRequest("DELETE", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -170,7 +179,7 @@ func (wd *BrowserDriver) Scroll(delta int) (err error) {
|
|||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"delta": delta,
|
"delta": delta,
|
||||||
}
|
}
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/scroll")
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/scroll"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,16 +193,17 @@ func (wd *BrowserDriver) CreateNetListener() (*websocket.Conn, error) {
|
|||||||
initMessage := fmt.Sprintf(`{
|
initMessage := fmt.Sprintf(`{
|
||||||
"type":"create_net_listener",
|
"type":"create_net_listener",
|
||||||
"context_id":"%v"
|
"context_id":"%v"
|
||||||
}`, wd.sessionId)
|
}`, wd.Session.ID)
|
||||||
err = c.WriteMessage(websocket.TextMessage, []byte(initMessage))
|
err = c.WriteMessage(websocket.TextMessage, []byte(initMessage))
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) ClosePage(pageIndex int) (err error) {
|
func (wd *BrowserDriver) CloseTab(pageIndex int) (err error) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"page_index": pageIndex,
|
"page_index": pageIndex,
|
||||||
}
|
}
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/page_close")
|
|
||||||
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/page_close"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +215,7 @@ func (wd *BrowserDriver) HoverBySelector(selector string, options ...option.Acti
|
|||||||
if actionOptions.Index > 0 {
|
if actionOptions.Index > 0 {
|
||||||
data["element_index"] = actionOptions.Index
|
data["element_index"] = actionOptions.Index
|
||||||
}
|
}
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/hover")
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/hover"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,20 +227,20 @@ func (wd *BrowserDriver) TapBySelector(selector string, options ...option.Action
|
|||||||
if actionOptions.Index > 0 {
|
if actionOptions.Index > 0 {
|
||||||
data["element_index"] = actionOptions.Index
|
data["element_index"] = actionOptions.Index
|
||||||
}
|
}
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/tap")
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/tap"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) RightClick(x, y float64) (err error) {
|
func (wd *BrowserDriver) SecondaryClick(x, y float64) (err error) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"x": x,
|
"x": x,
|
||||||
"y": y,
|
"y": y,
|
||||||
}
|
}
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/right_click")
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/right_click"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) RightClickBySelector(selector string, options ...option.ActionOption) (err error) {
|
func (wd *BrowserDriver) SecondaryClickBySelector(selector string, options ...option.ActionOption) (err error) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"selector": selector,
|
"selector": selector,
|
||||||
}
|
}
|
||||||
@@ -238,13 +248,13 @@ func (wd *BrowserDriver) RightClickBySelector(selector string, options ...option
|
|||||||
if actionOptions.Index > 0 {
|
if actionOptions.Index > 0 {
|
||||||
data["element_index"] = actionOptions.Index
|
data["element_index"] = actionOptions.Index
|
||||||
}
|
}
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/right_click")
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/right_click"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) GetElementTextBySelector(selector string, options ...option.ActionOption) (text string, err error) {
|
func (wd *BrowserDriver) GetElementTextBySelector(selector string, options ...option.ActionOption) (text string, err error) {
|
||||||
actionOptions := option.NewActionOptions(options...)
|
actionOptions := option.NewActionOptions(options...)
|
||||||
baseURL := fmt.Sprintf("http://%s/api/v1/%s/element_text", BROWSER_LOCAL_ADDRESS, wd.sessionId)
|
baseURL := fmt.Sprintf("http://%s/api/v1/%s/element_text", BROWSER_LOCAL_ADDRESS, wd.Session.ID)
|
||||||
|
|
||||||
// 使用 url.Values 构建查询参数
|
// 使用 url.Values 构建查询参数
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
@@ -294,29 +304,49 @@ func (wd *BrowserDriver) GetPageUrl(options ...option.ActionOption) (text string
|
|||||||
if actionOptions.Index > 0 {
|
if actionOptions.Index > 0 {
|
||||||
uri = uri + "?page_index=" + fmt.Sprintf("%v", actionOptions.Index)
|
uri = uri + "?page_index=" + fmt.Sprintf("%v", actionOptions.Index)
|
||||||
}
|
}
|
||||||
resp, err := wd.HttpGet(http.MethodGet, wd.sessionId, uri)
|
resp, err := wd.Session.GET(wd.concatURL(wd.Session.ID, uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
data := resp.Data.(map[string]interface{})
|
data, err := resp.ValueConvertToJsonObject()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
data = data["data"].(map[string]interface{})
|
||||||
return data["url"].(string), nil
|
return data["url"].(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) IsElementExistBySelector(selector string) (bool, error) {
|
func (wd *BrowserDriver) IsElementExistBySelector(selector string) (bool, error) {
|
||||||
resp, err := wd.HttpGet(wd.sessionId, "ui/element_exist", "?selector=", selector)
|
resp, err := wd.Session.GET(wd.concatURL("ui/element_exist", "?selector=", selector))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
data := resp.Data.(map[string]interface{})
|
data, err := resp.ValueConvertToJsonObject()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
data = data["data"].(map[string]interface{})
|
||||||
return data["exist"].(bool), nil
|
return data["exist"].(bool), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wd *BrowserDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (success bool, err error) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"url": packageName,
|
||||||
|
"web_cookie": password,
|
||||||
|
}
|
||||||
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "stub/login"))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) Hover(x, y float64) (err error) {
|
func (wd *BrowserDriver) Hover(x, y float64) (err error) {
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"x": x,
|
"x": x,
|
||||||
"y": y,
|
"y": y,
|
||||||
}
|
}
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/hover")
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/hover"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,86 +354,29 @@ func (wd *BrowserDriver) Input(text string, option ...option.ActionOption) (err
|
|||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"text": text,
|
"text": text,
|
||||||
}
|
}
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/input")
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/input"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source Return application elements tree
|
// Source Return application elements tree
|
||||||
func (wd *BrowserDriver) Source(srcOpt ...option.SourceOption) (string, error) {
|
func (wd *BrowserDriver) Source(srcOpt ...option.SourceOption) (string, error) {
|
||||||
resp, err := wd.HttpGet(http.MethodGet, wd.sessionId, "stub/source")
|
resp, err := wd.Session.GET(wd.concatURL(wd.Session.ID, "stub/source"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonData, err := json.Marshal(resp.Data)
|
return resp.ValueConvertToString()
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(jsonData), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) ScreenShot(options ...option.ActionOption) (*bytes.Buffer, error) {
|
func (wd *BrowserDriver) ScreenShot(options ...option.ActionOption) (*bytes.Buffer, error) {
|
||||||
resp, err := wd.HttpGet(http.MethodGet, wd.sessionId, "screenshot")
|
resp, err := wd.Session.GET(wd.concatURL(wd.Session.ID, "screenshot"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
data := resp.Data.(map[string]interface{})
|
|
||||||
screenshotBase64 := data["screenshot"].(string)
|
|
||||||
screenRaw, err := base64.StdEncoding.DecodeString(screenshotBase64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bytes.NewBuffer(screenRaw), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wd *BrowserDriver) HttpPOST(data interface{}, pathElem ...string) (response *WebAgentResponse, err error) {
|
|
||||||
var bsJSON []byte = nil
|
|
||||||
if data != nil {
|
|
||||||
if bsJSON, err = json.Marshal(data); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return wd.httpRequest(http.MethodPost, wd.concatURL(pathElem...), bsJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wd *BrowserDriver) HttpGet(data interface{}, pathElem ...string) (response *WebAgentResponse, err error) {
|
|
||||||
return wd.httpRequest(http.MethodGet, wd.concatURL(pathElem...), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wd *BrowserDriver) concatURL(elem ...string) string {
|
|
||||||
tmp, _ := url.Parse(wd.urlPrefix.String())
|
|
||||||
commonPath := path.Join(append([]string{wd.urlPrefix.Path}, "api/v1/")...)
|
|
||||||
tmp.Path = path.Join(append([]string{commonPath}, elem...)...)
|
|
||||||
return tmp.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wd *BrowserDriver) httpRequest(method string, rawURL string, rawBody []byte, disableRetry ...bool) (response *WebAgentResponse, err error) {
|
|
||||||
req, err := http.NewRequest(method, rawURL, bytes.NewBuffer(rawBody))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新建http client
|
|
||||||
client := &http.Client{
|
|
||||||
Timeout: 60 * time.Second, // 设置超时时间为5秒
|
|
||||||
}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawResp, _ := io.ReadAll(resp.Body)
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, errors.New(resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将结果解析为 JSON
|
// 将结果解析为 JSON
|
||||||
var result WebAgentResponse
|
var result WebAgentResponse
|
||||||
if err = json.Unmarshal(rawResp, &result); err != nil {
|
if err = json.Unmarshal(resp, &result); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,10 +385,20 @@ func (wd *BrowserDriver) httpRequest(method string, rawURL string, rawBody []byt
|
|||||||
return nil, errors.New(result.Message)
|
return nil, errors.New(result.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data := result.Data.(map[string]interface{})
|
||||||
|
screenshotBase64 := data["screenshot"].(string)
|
||||||
|
screenRaw, err := base64.StdEncoding.DecodeString(screenshotBase64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &result, err
|
return bytes.NewBuffer(screenRaw), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wd *BrowserDriver) concatURL(elem ...string) string {
|
||||||
|
tmp, _ := url.Parse(wd.urlPrefix.String())
|
||||||
|
commonPath := path.Join(append([]string{wd.urlPrefix.Path}, "api/v1/")...)
|
||||||
|
tmp.Path = path.Join(append([]string{commonPath}, elem...)...)
|
||||||
|
return tmp.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) Status() (deviceStatus types.DeviceStatus, err error) {
|
func (wd *BrowserDriver) Status() (deviceStatus types.DeviceStatus, err error) {
|
||||||
@@ -434,11 +417,16 @@ func (wd *BrowserDriver) BatteryInfo() (batteryInfo types.BatteryInfo, err error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) WindowSize() (types.Size, error) {
|
func (wd *BrowserDriver) WindowSize() (types.Size, error) {
|
||||||
resp, err := wd.HttpGet(http.MethodGet, wd.sessionId, "window_size")
|
resp, err := wd.Session.GET(wd.concatURL(wd.Session.ID, "window_size"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Size{}, err
|
return types.Size{}, err
|
||||||
}
|
}
|
||||||
data := resp.Data.(map[string]interface{})
|
|
||||||
|
data, err := resp.ValueConvertToJsonObject()
|
||||||
|
if err != nil {
|
||||||
|
return types.Size{}, err
|
||||||
|
}
|
||||||
|
data = data["data"].(map[string]interface{})
|
||||||
width := data["width"]
|
width := data["width"]
|
||||||
height := data["height"]
|
height := data["height"]
|
||||||
return types.Size{
|
return types.Size{
|
||||||
@@ -540,7 +528,8 @@ func (wd *BrowserDriver) TapFloat(x, y float64, opts ...option.ActionOption) err
|
|||||||
"y": y,
|
"y": y,
|
||||||
"duration": duration,
|
"duration": duration,
|
||||||
}
|
}
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/tap")
|
|
||||||
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/tap"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,7 +544,8 @@ func (wd *BrowserDriver) DoubleTap(x, y float64, options ...option.ActionOption)
|
|||||||
"x": x,
|
"x": x,
|
||||||
"y": y,
|
"y": y,
|
||||||
}
|
}
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/double_tap")
|
|
||||||
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/double_tap"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,7 +556,7 @@ func (wd *BrowserDriver) UploadFile(x, y float64, FileUrl, FileFormat string) (e
|
|||||||
"file_url": FileUrl,
|
"file_url": FileUrl,
|
||||||
"file_format": FileFormat,
|
"file_format": FileFormat,
|
||||||
}
|
}
|
||||||
_, err = wd.HttpPOST(data, wd.sessionId, "ui/upload")
|
_, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/upload"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,10 +592,6 @@ func (wd *BrowserDriver) Clear(packageName string) error {
|
|||||||
return errors.New("not support")
|
return errors.New("not support")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) Setup() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wd *BrowserDriver) GetDevice() IDevice {
|
func (wd *BrowserDriver) GetDevice() IDevice {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -616,7 +602,7 @@ func (wd *BrowserDriver) ForegroundInfo() (app types.AppInfo, err error) {
|
|||||||
|
|
||||||
// PressBack Presses the back button
|
// PressBack Presses the back button
|
||||||
func (wd *BrowserDriver) PressBack(options ...option.ActionOption) error {
|
func (wd *BrowserDriver) PressBack(options ...option.ActionOption) error {
|
||||||
_, err := wd.HttpPOST(map[string]interface{}{}, wd.sessionId, "ui/back")
|
_, err := wd.Session.POST(nil, wd.concatURL(wd.Session.ID, "ui/back"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,7 +670,7 @@ func (wd *BrowserDriver) InitSession(capabilities option.Capabilities) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) GetSession() *DriverSession {
|
func (wd *BrowserDriver) GetSession() *DriverSession {
|
||||||
return nil
|
return wd.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wd *BrowserDriver) ScreenRecord(opts ...option.ActionOption) (videoPath string, err error) {
|
func (wd *BrowserDriver) ScreenRecord(opts ...option.ActionOption) (videoPath string, err error) {
|
||||||
@@ -708,7 +694,7 @@ func (wd *BrowserDriver) TapXY(x, y float64, opts ...option.ActionOption) error
|
|||||||
"x": x,
|
"x": x,
|
||||||
"y": y,
|
"y": y,
|
||||||
}
|
}
|
||||||
_, err := wd.HttpPOST(data, wd.sessionId, "ui/double_tap")
|
_, err := wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/double_tap"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -50,11 +50,17 @@ type IDriver interface {
|
|||||||
Home() error
|
Home() error
|
||||||
Unlock() error
|
Unlock() error
|
||||||
Back() error
|
Back() error
|
||||||
|
// hover
|
||||||
|
HoverBySelector(selector string, opts ...option.ActionOption) error
|
||||||
// tap
|
// tap
|
||||||
TapXY(x, y float64, opts ...option.ActionOption) error // by percentage or absolute coordinate
|
TapXY(x, y float64, opts ...option.ActionOption) error // by percentage or absolute coordinate
|
||||||
TapAbsXY(x, y float64, opts ...option.ActionOption) error // by absolute coordinate
|
TapAbsXY(x, y float64, opts ...option.ActionOption) error // by absolute coordinate
|
||||||
|
TapBySelector(text string, opts ...option.ActionOption) error
|
||||||
DoubleTap(x, y float64, opts ...option.ActionOption) error // by absolute coordinate
|
DoubleTap(x, y float64, opts ...option.ActionOption) error // by absolute coordinate
|
||||||
TouchAndHold(x, y float64, opts ...option.ActionOption) error
|
TouchAndHold(x, y float64, opts ...option.ActionOption) error
|
||||||
|
// secondary click
|
||||||
|
SecondaryClick(x, y float64) error
|
||||||
|
SecondaryClickBySelector(selector string, options ...option.ActionOption) error
|
||||||
// swipe
|
// swipe
|
||||||
Drag(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error
|
Drag(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error
|
||||||
Swipe(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error // by percentage
|
Swipe(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error // by percentage
|
||||||
|
|||||||
+65
-12
@@ -20,6 +20,7 @@ const (
|
|||||||
ACTION_LOG ActionMethod = "log"
|
ACTION_LOG ActionMethod = "log"
|
||||||
ACTION_AppInstall ActionMethod = "install"
|
ACTION_AppInstall ActionMethod = "install"
|
||||||
ACTION_AppUninstall ActionMethod = "uninstall"
|
ACTION_AppUninstall ActionMethod = "uninstall"
|
||||||
|
ACTION_WebLoginNoneUI ActionMethod = "login_none_ui"
|
||||||
ACTION_AppClear ActionMethod = "app_clear"
|
ACTION_AppClear ActionMethod = "app_clear"
|
||||||
ACTION_AppStart ActionMethod = "app_start"
|
ACTION_AppStart ActionMethod = "app_start"
|
||||||
ACTION_AppLaunch ActionMethod = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成
|
ACTION_AppLaunch ActionMethod = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成
|
||||||
@@ -35,18 +36,24 @@ const (
|
|||||||
ACTION_CallFunction ActionMethod = "call_function"
|
ACTION_CallFunction ActionMethod = "call_function"
|
||||||
|
|
||||||
// UI handling
|
// UI handling
|
||||||
ACTION_Home ActionMethod = "home"
|
ACTION_Home ActionMethod = "home"
|
||||||
ACTION_TapXY ActionMethod = "tap_xy"
|
ACTION_TapXY ActionMethod = "tap_xy"
|
||||||
ACTION_TapAbsXY ActionMethod = "tap_abs_xy"
|
ACTION_TapAbsXY ActionMethod = "tap_abs_xy"
|
||||||
ACTION_TapByOCR ActionMethod = "tap_ocr"
|
ACTION_TapByOCR ActionMethod = "tap_ocr"
|
||||||
ACTION_TapByCV ActionMethod = "tap_cv"
|
ACTION_TapByCV ActionMethod = "tap_cv"
|
||||||
ACTION_DoubleTapXY ActionMethod = "double_tap_xy"
|
ACTION_DoubleTapXY ActionMethod = "double_tap_xy"
|
||||||
ACTION_Swipe ActionMethod = "swipe"
|
ACTION_Swipe ActionMethod = "swipe"
|
||||||
ACTION_Drag ActionMethod = "drag"
|
ACTION_Drag ActionMethod = "drag"
|
||||||
ACTION_Input ActionMethod = "input"
|
ACTION_Input ActionMethod = "input"
|
||||||
ACTION_Back ActionMethod = "back"
|
ACTION_Back ActionMethod = "back"
|
||||||
ACTION_KeyCode ActionMethod = "keycode"
|
ACTION_KeyCode ActionMethod = "keycode"
|
||||||
ACTION_AIAction ActionMethod = "ai_action" // action with ai
|
ACTION_AIAction ActionMethod = "ai_action" // action with ai
|
||||||
|
ACTION_TapBySelector ActionMethod = "tap_by_selector"
|
||||||
|
ACTION_HoverBySelector ActionMethod = "hover_by_selector"
|
||||||
|
ACTION_WebCloseTab ActionMethod = "web_close_tab"
|
||||||
|
ACTION_SecondaryClick ActionMethod = "secondary_click"
|
||||||
|
ACTION_SecondaryClickBySelector ActionMethod = "secondary_click_by_selector"
|
||||||
|
ACTION_GetElementTextBySelector ActionMethod = "get_element_text_by_selector"
|
||||||
|
|
||||||
// custom actions
|
// custom actions
|
||||||
ACTION_SwipeToTapApp ActionMethod = "swipe_to_tap_app" // swipe left & right to find app and tap
|
ACTION_SwipeToTapApp ActionMethod = "swipe_to_tap_app" // swipe left & right to find app and tap
|
||||||
@@ -68,6 +75,7 @@ const (
|
|||||||
SelectorImage string = "ui_image"
|
SelectorImage string = "ui_image"
|
||||||
SelectorAI string = "ui_ai" // ui query with ai
|
SelectorAI string = "ui_ai" // ui query with ai
|
||||||
SelectorForegroundApp string = "ui_foreground_app"
|
SelectorForegroundApp string = "ui_foreground_app"
|
||||||
|
SelectorSelector string = "ui_selector"
|
||||||
// assertions
|
// assertions
|
||||||
AssertionEqual string = "equal"
|
AssertionEqual string = "equal"
|
||||||
AssertionNotEqual string = "not_equal"
|
AssertionNotEqual string = "not_equal"
|
||||||
@@ -111,6 +119,17 @@ func (dExt *XTDriver) DoAction(action MobileAction) (err error) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
switch action.Method {
|
switch action.Method {
|
||||||
|
case ACTION_WebLoginNoneUI:
|
||||||
|
if len(action.Params.([]interface{})) == 4 {
|
||||||
|
driver, ok := dExt.IDriver.(*BrowserDriver)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid browser driver")
|
||||||
|
}
|
||||||
|
params := action.Params.([]interface{})
|
||||||
|
_, err = driver.LoginNoneUI(params[0].(string), params[1].(string), params[2].(string), params[3].(string))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid %s params: %v", ACTION_WebLoginNoneUI, action.Params)
|
||||||
case ACTION_AppInstall:
|
case ACTION_AppInstall:
|
||||||
if app, ok := action.Params.(string); ok {
|
if app, ok := action.Params.(string); ok {
|
||||||
if err = dExt.GetDevice().Install(app,
|
if err = dExt.GetDevice().Install(app,
|
||||||
@@ -170,6 +189,40 @@ func (dExt *XTDriver) DoAction(action MobileAction) (err error) {
|
|||||||
return fmt.Errorf("app_terminate params should be bundleId(string), got %v", action.Params)
|
return fmt.Errorf("app_terminate params should be bundleId(string), got %v", action.Params)
|
||||||
case ACTION_Home:
|
case ACTION_Home:
|
||||||
return dExt.Home()
|
return dExt.Home()
|
||||||
|
case ACTION_SecondaryClick:
|
||||||
|
if params, err := builtin.ConvertToFloat64Slice(action.Params); err == nil {
|
||||||
|
if len(params) != 2 {
|
||||||
|
return fmt.Errorf("invalid tap location params: %v", params)
|
||||||
|
}
|
||||||
|
x, y := params[0], params[1]
|
||||||
|
return dExt.SecondaryClick(x, y)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid %s params: %v", ACTION_SecondaryClick, action.Params)
|
||||||
|
case ACTION_HoverBySelector:
|
||||||
|
if selector, ok := action.Params.(string); ok {
|
||||||
|
return dExt.HoverBySelector(selector, action.GetOptions()...)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid %s params: %v", ACTION_HoverBySelector, action.Params)
|
||||||
|
case ACTION_TapBySelector:
|
||||||
|
if selector, ok := action.Params.(string); ok {
|
||||||
|
return dExt.TapBySelector(selector, action.GetOptions()...)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid %s params: %v", ACTION_TapBySelector, action.Params)
|
||||||
|
case ACTION_SecondaryClickBySelector:
|
||||||
|
if selector, ok := action.Params.(string); ok {
|
||||||
|
return dExt.SecondaryClickBySelector(selector, action.GetOptions()...)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid %s params: %v", ACTION_SecondaryClickBySelector, action.Params)
|
||||||
|
case ACTION_WebCloseTab:
|
||||||
|
if param, ok := action.Params.(json.Number); ok {
|
||||||
|
paramInt64, _ := param.Int64()
|
||||||
|
return dExt.IDriver.(*BrowserDriver).CloseTab(int(paramInt64))
|
||||||
|
} else if param, ok := action.Params.(int64); ok {
|
||||||
|
return dExt.IDriver.(*BrowserDriver).CloseTab(int(param))
|
||||||
|
} else {
|
||||||
|
return dExt.IDriver.(*BrowserDriver).CloseTab(action.Params.(int))
|
||||||
|
}
|
||||||
|
// return fmt.Errorf("invalid %s params: %v", ACTION_WebCloseTab, action.Params)
|
||||||
case ACTION_SetIme:
|
case ACTION_SetIme:
|
||||||
if ime, ok := action.Params.(string); ok {
|
if ime, ok := action.Params.(string); ok {
|
||||||
err = dExt.SetIme(ime)
|
err = dExt.SetIme(ime)
|
||||||
|
|||||||
@@ -56,3 +56,15 @@ func (dExt *XTDriver) TapByCV(opts ...option.ActionOption) error {
|
|||||||
|
|
||||||
return dExt.TapAbsXY(point.X, point.Y, opts...)
|
return dExt.TapAbsXY(point.X, point.Y, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dExt *XTDriver) SecondaryClickByOCR(ocrText string, opts ...option.ActionOption) error {
|
||||||
|
actionOptions := option.NewActionOptions(opts...)
|
||||||
|
point, err := dExt.FindScreenText(ocrText, opts...)
|
||||||
|
if err != nil {
|
||||||
|
if actionOptions.IgnoreNotFoundError {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return dExt.SecondaryClick(point.Center().X, point.Center().Y)
|
||||||
|
}
|
||||||
|
|||||||
@@ -270,14 +270,14 @@ func (s *DriverSession) Request(method string, urlStr string, rawBody []byte) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DriverSession) SetupPortForward(localPort int) error {
|
func (s *DriverSession) SetupPortForward(localPort int) error {
|
||||||
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", localPort))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create tcp connection error %v", err)
|
|
||||||
}
|
|
||||||
s.client.Transport = &http.Transport{
|
s.client.Transport = &http.Transport{
|
||||||
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return conn, nil
|
return net.Dial(network, fmt.Sprintf("127.0.0.1:%d", localPort))
|
||||||
},
|
},
|
||||||
|
MaxIdleConns: 10,
|
||||||
|
IdleConnTimeout: 30 * time.Second,
|
||||||
|
DisableKeepAlives: false,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,6 +180,28 @@ func (dExt *XTDriver) assertForegroundApp(appName, assert string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dExt *XTDriver) assertSelector(selector, assert string) error {
|
||||||
|
driver, ok := dExt.IDriver.(*BrowserDriver)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("assert selector only supports browser driver")
|
||||||
|
}
|
||||||
|
switch assert {
|
||||||
|
case AssertionExists:
|
||||||
|
_, err := driver.IsElementExistBySelector(selector)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "assert ocr exists failed")
|
||||||
|
}
|
||||||
|
case AssertionNotExists:
|
||||||
|
_, err := driver.IsElementExistBySelector(selector)
|
||||||
|
if err == nil {
|
||||||
|
return errors.New("assert ocr not exists failed")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected assert method %s", assert)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (dExt *XTDriver) DoValidation(check, assert, expected string, message ...string) (err error) {
|
func (dExt *XTDriver) DoValidation(check, assert, expected string, message ...string) (err error) {
|
||||||
switch check {
|
switch check {
|
||||||
case SelectorOCR:
|
case SelectorOCR:
|
||||||
@@ -188,6 +210,8 @@ func (dExt *XTDriver) DoValidation(check, assert, expected string, message ...st
|
|||||||
err = dExt.AIAssert(assert)
|
err = dExt.AIAssert(assert)
|
||||||
case SelectorForegroundApp:
|
case SelectorForegroundApp:
|
||||||
err = dExt.assertForegroundApp(expected, assert)
|
err = dExt.assertForegroundApp(expected, assert)
|
||||||
|
case SelectorSelector:
|
||||||
|
err = dExt.assertSelector(expected, assert)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("validator %s not implemented", check)
|
return fmt.Errorf("validator %s not implemented", check)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -315,3 +315,19 @@ func (hd *HDCDriver) ClearFiles(paths ...string) error {
|
|||||||
log.Warn().Msg("ClearFiles not implemented in HDCDriver")
|
log.Warn().Msg("ClearFiles not implemented in HDCDriver")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hd *HDCDriver) HoverBySelector(selector string, options ...option.ActionOption) (err error) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hd *HDCDriver) TapBySelector(text string, opts ...option.ActionOption) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hd *HDCDriver) SecondaryClick(x, y float64) (err error) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hd *HDCDriver) SecondaryClickBySelector(selector string, options ...option.ActionOption) (err error) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -1053,3 +1053,19 @@ func (wd *WDADriver) StopCaptureLog() (result interface{}, err error) {
|
|||||||
func (wd *WDADriver) GetSession() *DriverSession {
|
func (wd *WDADriver) GetSession() *DriverSession {
|
||||||
return wd.Session
|
return wd.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wd *WDADriver) HoverBySelector(selector string, options ...option.ActionOption) (err error) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wd *WDADriver) TapBySelector(text string, opts ...option.ActionOption) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wd *WDADriver) SecondaryClick(x, y float64) (err error) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wd *WDADriver) SecondaryClickBySelector(selector string, options ...option.ActionOption) (err error) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/danielpaulus/go-ios/ios"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@@ -50,6 +51,16 @@ func TestDriver_WDA_LazySetup(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIOSDeviceList(t *testing.T) {
|
||||||
|
t.Logf("start test")
|
||||||
|
// get all attached ios devices
|
||||||
|
devices, err := ios.ListDevices()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("%+v", devices)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDevice_IOS_New(t *testing.T) {
|
func TestDevice_IOS_New(t *testing.T) {
|
||||||
device, err := NewIOSDevice(
|
device, err := NewIOSDevice(
|
||||||
option.WithWDAPort(8700),
|
option.WithWDAPort(8700),
|
||||||
|
|||||||
@@ -15,6 +15,19 @@ type BrowserDeviceOptions struct {
|
|||||||
Height int `json:"height,omitempty" yaml:"height,omitempty"`
|
Height int `json:"height,omitempty" yaml:"height,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dev *BrowserDeviceOptions) Options() (deviceOptions []BrowserDeviceOption) {
|
||||||
|
if dev.BrowserID != "" {
|
||||||
|
deviceOptions = append(deviceOptions, WithBrowserID(dev.BrowserID))
|
||||||
|
}
|
||||||
|
if dev.LogOn {
|
||||||
|
deviceOptions = append(deviceOptions, WithBrowserLogOn(true))
|
||||||
|
}
|
||||||
|
if dev.Width > 0 && dev.Height > 0 {
|
||||||
|
deviceOptions = append(deviceOptions, WithBrowserPageSize(dev.Width, dev.Height))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type BrowserDeviceOption func(*BrowserDeviceOptions)
|
type BrowserDeviceOption func(*BrowserDeviceOptions)
|
||||||
|
|
||||||
func WithBrowserID(serial string) BrowserDeviceOption {
|
func WithBrowserID(serial string) BrowserDeviceOption {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package option
|
|||||||
|
|
||||||
type IOSDeviceOptions struct {
|
type IOSDeviceOptions struct {
|
||||||
UDID string `json:"udid,omitempty" yaml:"udid,omitempty"`
|
UDID string `json:"udid,omitempty" yaml:"udid,omitempty"`
|
||||||
|
Wireless bool `json:"wireless,omitempty" yaml:"wireless,omitempty"`
|
||||||
WDAPort int `json:"port,omitempty" yaml:"port,omitempty"` // WDA remote port
|
WDAPort int `json:"port,omitempty" yaml:"port,omitempty"` // WDA remote port
|
||||||
WDAMjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
|
WDAMjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
|
||||||
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
|
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
|
||||||
@@ -20,6 +21,9 @@ func (dev *IOSDeviceOptions) Options() (deviceOptions []IOSDeviceOption) {
|
|||||||
if dev.UDID != "" {
|
if dev.UDID != "" {
|
||||||
deviceOptions = append(deviceOptions, WithUDID(dev.UDID))
|
deviceOptions = append(deviceOptions, WithUDID(dev.UDID))
|
||||||
}
|
}
|
||||||
|
if dev.Wireless {
|
||||||
|
deviceOptions = append(deviceOptions, WithWireless(true))
|
||||||
|
}
|
||||||
if dev.WDAPort != 0 {
|
if dev.WDAPort != 0 {
|
||||||
deviceOptions = append(deviceOptions, WithWDAPort(dev.WDAPort))
|
deviceOptions = append(deviceOptions, WithWDAPort(dev.WDAPort))
|
||||||
}
|
}
|
||||||
@@ -101,6 +105,12 @@ func WithUDID(udid string) IOSDeviceOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithWireless(on bool) IOSDeviceOption {
|
||||||
|
return func(device *IOSDeviceOptions) {
|
||||||
|
device.Wireless = on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithWDAPort(port int) IOSDeviceOption {
|
func WithWDAPort(port int) IOSDeviceOption {
|
||||||
return func(device *IOSDeviceOptions) {
|
return func(device *IOSDeviceOptions) {
|
||||||
device.WDAPort = port
|
device.WDAPort = port
|
||||||
|
|||||||
Reference in New Issue
Block a user