mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-15 20:38:44 +08:00
1204 lines
35 KiB
Go
1204 lines
35 KiB
Go
package uixt
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/electricbubble/gadb"
|
|
)
|
|
|
|
type uiaDriver struct {
|
|
Driver
|
|
|
|
adbDevice gadb.Device
|
|
localPort int
|
|
}
|
|
|
|
func NewUIADriver(capabilities Capabilities, urlPrefix string) (driver *uiaDriver, err error) {
|
|
if capabilities == nil {
|
|
capabilities = NewCapabilities()
|
|
}
|
|
driver = new(uiaDriver)
|
|
if driver.urlPrefix, err = url.Parse(urlPrefix); err != nil {
|
|
return nil, err
|
|
}
|
|
if driver.sessionId, err = driver.NewSession(capabilities); err != nil {
|
|
return nil, err
|
|
}
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) NewSession(capabilities Capabilities) (sessionID string, err error) {
|
|
// register(postHandler, new NewSession("/wd/hub/session"))
|
|
var rawResp rawResponse
|
|
data := map[string]interface{}{"capabilities": capabilities}
|
|
if rawResp, err = d.httpPOST(data, "/session"); err != nil {
|
|
return "", err
|
|
}
|
|
reply := new(struct{ Value struct{ SessionId string } })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return "", err
|
|
}
|
|
sessionID = reply.Value.SessionId
|
|
// d.sessionIdCache[sessionID] = true
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) Quit() (err error) {
|
|
// register(deleteHandler, new DeleteSession("/wd/hub/session/:sessionId"))
|
|
if d.sessionId == "" {
|
|
return nil
|
|
}
|
|
if _, err = d.httpDELETE("/session", d.sessionId); err == nil {
|
|
d.sessionId = ""
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (d *uiaDriver) ActiveSessionID() string {
|
|
return d.sessionId
|
|
}
|
|
|
|
func (d *uiaDriver) SessionIDs() (sessionIDs []string, err error) {
|
|
// register(getHandler, new GetSessions("/wd/hub/sessions"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/sessions"); err != nil {
|
|
return nil, err
|
|
}
|
|
reply := new(struct{ Value []struct{ SessionId string } })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sessionIDs = make([]string, len(reply.Value))
|
|
for i := range reply.Value {
|
|
sessionIDs[i] = reply.Value[i].SessionId
|
|
}
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) SessionDetails() (scrollData map[string]interface{}, err error) {
|
|
// register(getHandler, new GetSessionDetails("/wd/hub/session/:sessionId"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId); err != nil {
|
|
return nil, err
|
|
}
|
|
reply := new(struct{ Value map[string]interface{} })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
scrollData = reply.Value
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) Status() (ready bool, err error) {
|
|
// register(getHandler, new Status("/wd/hub/status"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/status"); err != nil {
|
|
return false, err
|
|
}
|
|
reply := new(struct {
|
|
Value struct {
|
|
// Message string
|
|
Ready bool
|
|
}
|
|
})
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return false, err
|
|
}
|
|
ready = reply.Value.Ready
|
|
return
|
|
}
|
|
|
|
// Screenshot grab device screenshot
|
|
func (d *uiaDriver) Screenshot() (raw *bytes.Buffer, err error) {
|
|
// register(getHandler, new CaptureScreenshot("/wd/hub/session/:sessionId/screenshot"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "screenshot"); err != nil {
|
|
return nil, err
|
|
}
|
|
reply := new(struct{ Value string })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var decodeStr []byte
|
|
if decodeStr, err = base64.StdEncoding.DecodeString(reply.Value); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
raw = bytes.NewBuffer(decodeStr)
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) Orientation() (orientation Orientation, err error) {
|
|
// register(getHandler, new GetOrientation("/wd/hub/session/:sessionId/orientation"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "orientation"); err != nil {
|
|
return "", err
|
|
}
|
|
reply := new(struct{ Value Orientation })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
orientation = reply.Value
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) Rotation() (rotation Rotation, err error) {
|
|
// register(getHandler, new GetRotation("/wd/hub/session/:sessionId/rotation"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "rotation"); err != nil {
|
|
return Rotation{}, err
|
|
}
|
|
reply := new(struct{ Value Rotation })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return Rotation{}, err
|
|
}
|
|
|
|
rotation = reply.Value
|
|
return
|
|
}
|
|
|
|
// DeviceSize get window size of the device
|
|
func (d *uiaDriver) DeviceSize() (deviceSize Size, err error) {
|
|
// register(getHandler, new GetDeviceSize("/wd/hub/session/:sessionId/window/:windowHandle/size"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "window/:windowHandle/size"); err != nil {
|
|
return Size{}, err
|
|
}
|
|
reply := new(struct{ Value Size })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return Size{}, err
|
|
}
|
|
|
|
deviceSize = reply.Value
|
|
return
|
|
}
|
|
|
|
// Source get page source
|
|
func (d *uiaDriver) Source() (sXML string, err error) {
|
|
// register(getHandler, new Source("/wd/hub/session/:sessionId/source"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "source"); err != nil {
|
|
return "", err
|
|
}
|
|
reply := new(struct{ Value string })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
sXML = reply.Value
|
|
return
|
|
}
|
|
|
|
// StatusBarHeight get status bar height of the device
|
|
func (d *uiaDriver) StatusBarHeight() (height int, err error) {
|
|
// register(getHandler, new GetSystemBars("/wd/hub/session/:sessionId/appium/device/system_bars"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "appium/device/system_bars"); err != nil {
|
|
return 0, err
|
|
}
|
|
reply := new(struct{ Value struct{ StatusBar int } })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
height = reply.Value.StatusBar
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) check() error {
|
|
if d.adbDevice.Serial() == "" {
|
|
return errors.New("adb daemon: the device is not ready")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Dispose corresponds to the command:
|
|
// adb -s $serial forward --remove $localPort
|
|
func (d *uiaDriver) Dispose() (err error) {
|
|
if err = d.check(); err != nil {
|
|
return err
|
|
}
|
|
if d.localPort == 0 {
|
|
return nil
|
|
}
|
|
return d.adbDevice.ForwardKill(d.localPort)
|
|
}
|
|
|
|
func (d *uiaDriver) ActiveAppActivity() (appActivity string, err error) {
|
|
if err = d.check(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var sOutput string
|
|
if sOutput, err = d.adbDevice.RunShellCommand("dumpsys activity activities | grep mResumedActivity"); err != nil {
|
|
return "", err
|
|
}
|
|
re := regexp.MustCompile(`\{(.+?)\}`)
|
|
if !re.MatchString(sOutput) {
|
|
return "", fmt.Errorf("active app activity: %s", strings.TrimSpace(sOutput))
|
|
}
|
|
fields := strings.Fields(re.FindStringSubmatch(sOutput)[1])
|
|
appActivity = fields[2]
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) ActiveAppPackageName() (appPackageName string, err error) {
|
|
var activity string
|
|
if activity, err = d.ActiveAppActivity(); err != nil {
|
|
return "", err
|
|
}
|
|
appPackageName = strings.Split(activity, "/")[0]
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) AppLaunch(appPackageName string, waitForComplete ...AndroidBySelector) (err error) {
|
|
if err = d.check(); err != nil {
|
|
return err
|
|
}
|
|
|
|
var sOutput string
|
|
if sOutput, err = d.adbDevice.RunShellCommand("monkey -p", appPackageName, "-c android.intent.category.LAUNCHER 1"); err != nil {
|
|
return err
|
|
}
|
|
if strings.Contains(sOutput, "monkey aborted") {
|
|
return fmt.Errorf("app launch: %s", strings.TrimSpace(sOutput))
|
|
}
|
|
|
|
if len(waitForComplete) != 0 {
|
|
var ce error
|
|
exists := func(d *uiaDriver) (bool, error) {
|
|
for i := range waitForComplete {
|
|
_, ce = d.FindElement(waitForComplete[i])
|
|
if ce == nil {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
if err = d.WaitWithTimeoutAndInterval(exists, 45, 1.5); err != nil {
|
|
return fmt.Errorf("app launch (waitForComplete): %s: %w", err.Error(), ce)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) AppTerminate(appPackageName string) (err error) {
|
|
if err = d.check(); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = d.adbDevice.RunShellCommand("am force-stop", appPackageName)
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) AppInstall(apkPath string, reinstall ...bool) (err error) {
|
|
if err = d.check(); err != nil {
|
|
return err
|
|
}
|
|
|
|
apkName := filepath.Base(apkPath)
|
|
if !strings.HasSuffix(strings.ToLower(apkName), ".apk") {
|
|
return fmt.Errorf("apk file must have an extension of '.apk': %s", apkPath)
|
|
}
|
|
|
|
var apkFile *os.File
|
|
if apkFile, err = os.Open(apkPath); err != nil {
|
|
return fmt.Errorf("apk file: %w", err)
|
|
}
|
|
|
|
remotePath := path.Join(DeviceTempPath, apkName)
|
|
if err = d.adbDevice.PushFile(apkFile, remotePath); err != nil {
|
|
return fmt.Errorf("apk push: %w", err)
|
|
}
|
|
|
|
var shellOutput string
|
|
if len(reinstall) != 0 && reinstall[0] {
|
|
shellOutput, err = d.adbDevice.RunShellCommand("pm install", "-r", remotePath)
|
|
} else {
|
|
shellOutput, err = d.adbDevice.RunShellCommand("pm install", remotePath)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("apk install: %w", err)
|
|
}
|
|
|
|
if !strings.Contains(shellOutput, "Success") {
|
|
return fmt.Errorf("apk installed: %s", shellOutput)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) AppUninstall(appPackageName string, keepDataAndCache ...bool) (err error) {
|
|
if err = d.check(); err != nil {
|
|
return err
|
|
}
|
|
|
|
var shellOutput string
|
|
if len(keepDataAndCache) != 0 && keepDataAndCache[0] {
|
|
shellOutput, err = d.adbDevice.RunShellCommand("pm uninstall", "-k", appPackageName)
|
|
} else {
|
|
shellOutput, err = d.adbDevice.RunShellCommand("pm uninstall", appPackageName)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("apk uninstall: %w", err)
|
|
}
|
|
|
|
if !strings.Contains(shellOutput, "Success") {
|
|
return fmt.Errorf("apk uninstalled: %s", shellOutput)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
type BatteryStatus int
|
|
|
|
const (
|
|
_ = iota
|
|
BatteryStatusUnknown BatteryStatus = iota
|
|
BatteryStatusCharging
|
|
BatteryStatusDischarging
|
|
BatteryStatusNotCharging
|
|
BatteryStatusFull
|
|
)
|
|
|
|
func (bs BatteryStatus) String() string {
|
|
switch bs {
|
|
case BatteryStatusUnknown:
|
|
return "unknown"
|
|
case BatteryStatusCharging:
|
|
return "charging"
|
|
case BatteryStatusDischarging:
|
|
return "discharging"
|
|
case BatteryStatusNotCharging:
|
|
return "not charging"
|
|
case BatteryStatusFull:
|
|
return "full"
|
|
default:
|
|
return fmt.Sprintf("unknown status code (%d)", bs)
|
|
}
|
|
}
|
|
|
|
func (d *uiaDriver) BatteryInfo() (info BatteryInfo, err error) {
|
|
// register(getHandler, new GetBatteryInfo("/wd/hub/session/:sessionId/appium/device/battery_info"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "appium/device/battery_info"); err != nil {
|
|
return BatteryInfo{}, err
|
|
}
|
|
reply := new(struct{ Value BatteryInfo })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return BatteryInfo{}, err
|
|
}
|
|
|
|
info = reply.Value
|
|
if info.Level == -1 || info.Status == -1 {
|
|
return info, errors.New("cannot be retrieved from the system")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) GetAppiumSettings() (settings map[string]interface{}, err error) {
|
|
// register(getHandler, new GetSettings("/wd/hub/session/:sessionId/appium/settings"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "appium/settings"); err != nil {
|
|
return nil, err
|
|
}
|
|
reply := new(struct{ Value map[string]interface{} })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
settings = reply.Value
|
|
return
|
|
}
|
|
|
|
// DeviceScaleRatio get device pixel ratio
|
|
func (d *uiaDriver) DeviceScaleRatio() (scale float64, err error) {
|
|
// register(getHandler, new GetDevicePixelRatio("/wd/hub/session/:sessionId/appium/device/pixel_ratio"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "appium/device/pixel_ratio"); err != nil {
|
|
return 0, err
|
|
}
|
|
reply := new(struct{ Value float64 })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
scale = reply.Value
|
|
return
|
|
}
|
|
|
|
type (
|
|
AndroidDeviceInfo struct {
|
|
// ANDROID_ID A 64-bit number (as a hex string) that is uniquely generated when the user
|
|
// first sets up the device and should remain constant for the lifetime of the user's device. The value
|
|
// may change if a factory reset is performed on the device.
|
|
AndroidID string `json:"androidId"`
|
|
// Build.MANUFACTURER value
|
|
Manufacturer string `json:"manufacturer"`
|
|
// Build.MODEL value
|
|
Model string `json:"model"`
|
|
// Build.BRAND value
|
|
Brand string `json:"brand"`
|
|
// Current running OS's API VERSION
|
|
APIVersion string `json:"apiVersion"`
|
|
// The current version string, for example "1.0" or "3.4b5"
|
|
PlatformVersion string `json:"platformVersion"`
|
|
// the name of the current celluar network carrier
|
|
CarrierName string `json:"carrierName"`
|
|
// the real size of the default display
|
|
RealDisplaySize string `json:"realDisplaySize"`
|
|
// The logical density of the display in Density Independent Pixel units.
|
|
DisplayDensity int `json:"displayDensity"`
|
|
// available networks
|
|
Networks []networkInfo `json:"networks"`
|
|
// current system locale
|
|
Locale string `json:"locale"`
|
|
// current system timezone
|
|
// e.g. "Asia/Tokyo", "America/Caracas", "Asia/Shanghai"
|
|
TimeZone string `json:"timeZone"`
|
|
Bluetooth struct {
|
|
State string `json:"state"`
|
|
} `json:"bluetooth"`
|
|
}
|
|
networkCapabilities struct {
|
|
TransportTypes string `json:"transportTypes"`
|
|
NetworkCapabilities string `json:"networkCapabilities"`
|
|
LinkUpstreamBandwidthKbps int `json:"linkUpstreamBandwidthKbps"`
|
|
LinkDownBandwidthKbps int `json:"linkDownBandwidthKbps"`
|
|
SignalStrength int `json:"signalStrength"`
|
|
SSID string `json:"SSID"`
|
|
}
|
|
networkInfo struct {
|
|
Type int `json:"type"`
|
|
TypeName string `json:"typeName"`
|
|
Subtype int `json:"subtype"`
|
|
SubtypeName string `json:"subtypeName"`
|
|
IsConnected bool `json:"isConnected"`
|
|
DetailedState string `json:"detailedState"`
|
|
State string `json:"state"`
|
|
ExtraInfo string `json:"extraInfo"`
|
|
IsAvailable bool `json:"isAvailable"`
|
|
IsRoaming bool `json:"isRoaming"`
|
|
IsFailover bool `json:"isFailover"`
|
|
Capabilities networkCapabilities `json:"capabilities"`
|
|
}
|
|
)
|
|
|
|
func (d *uiaDriver) DeviceInfo() (info AndroidDeviceInfo, err error) {
|
|
// register(getHandler, new GetDeviceInfo("/wd/hub/session/:sessionId/appium/device/info"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "appium/device/info"); err != nil {
|
|
return AndroidDeviceInfo{}, err
|
|
}
|
|
reply := new(struct{ Value AndroidDeviceInfo })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return AndroidDeviceInfo{}, err
|
|
}
|
|
|
|
info = reply.Value
|
|
return
|
|
}
|
|
|
|
// AlertText get text of the on-screen dialog
|
|
func (d *uiaDriver) AlertText() (text string, err error) {
|
|
// register(getHandler, new GetAlertText("/wd/hub/session/:sessionId/alert/text"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "alert/text"); err != nil {
|
|
return "", err
|
|
}
|
|
reply := new(struct{ Value string })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
text = reply.Value
|
|
return
|
|
}
|
|
|
|
// Tap perform a click at arbitrary coordinates specified
|
|
func (d *uiaDriver) Tap(x, y int) (err error) {
|
|
return d.TapFloat(float64(x), float64(y))
|
|
}
|
|
|
|
func (d *uiaDriver) TapFloat(x, y float64) (err error) {
|
|
// register(postHandler, new Tap("/wd/hub/session/:sessionId/appium/tap"))
|
|
data := map[string]interface{}{
|
|
"x": x,
|
|
"y": y,
|
|
}
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "appium/tap")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) TapPoint(point Point) (err error) {
|
|
return d.Tap(point.X, point.Y)
|
|
}
|
|
|
|
func (d *uiaDriver) TapPointF(point PointF) (err error) {
|
|
return d.TapFloat(point.X, point.Y)
|
|
}
|
|
|
|
func (d *uiaDriver) _swipe(startX, startY, endX, endY interface{}, steps int, elementID ...string) (err error) {
|
|
// register(postHandler, new Swipe("/wd/hub/session/:sessionId/touch/perform"))
|
|
data := map[string]interface{}{
|
|
"startX": startX,
|
|
"startY": startY,
|
|
"endX": endX,
|
|
"endY": endY,
|
|
"steps": steps,
|
|
}
|
|
if len(elementID) != 0 {
|
|
data["elementId"] = elementID[0]
|
|
}
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "touch/perform")
|
|
return
|
|
}
|
|
|
|
// Swipe performs a swipe from one coordinate to another using the number of steps
|
|
// to determine smoothness and speed. Each step execution is throttled to 5ms
|
|
// per step. So for a 100 steps, the swipe will take about 1/2 second to complete.
|
|
// `steps` is the number of move steps sent to the system
|
|
func (d *uiaDriver) Swipe(startX, startY, endX, endY int, steps ...int) (err error) {
|
|
return d.SwipeFloat(float64(startX), float64(startY), float64(endX), float64(endY), steps...)
|
|
}
|
|
|
|
func (d *uiaDriver) SwipeFloat(startX, startY, endX, endY float64, steps ...int) (err error) {
|
|
if len(steps) == 0 {
|
|
steps = []int{12}
|
|
}
|
|
return d._swipe(startX, startY, endX, endY, steps[0])
|
|
}
|
|
|
|
func (d *uiaDriver) SwipePoint(startPoint, endPoint Point, steps ...int) (err error) {
|
|
return d.Swipe(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y, steps...)
|
|
}
|
|
|
|
func (d *uiaDriver) SwipePointF(startPoint, endPoint PointF, steps ...int) (err error) {
|
|
return d.SwipeFloat(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y, steps...)
|
|
}
|
|
|
|
func (d *uiaDriver) _drag(data map[string]interface{}) (err error) {
|
|
// register(postHandler, new Drag("/wd/hub/session/:sessionId/touch/drag"))
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "touch/drag")
|
|
return
|
|
}
|
|
|
|
// Drag performs a swipe from one coordinate to another coordinate. You can control
|
|
// 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 (d *uiaDriver) Drag(startX, startY, endX, endY int, steps ...int) (err error) {
|
|
return d.DragFloat(float64(startX), float64(startY), float64(endX), float64(endY), steps...)
|
|
}
|
|
|
|
func (d *uiaDriver) DragFloat(startX, startY, endX, endY float64, steps ...int) error {
|
|
if len(steps) == 0 {
|
|
steps = []int{12}
|
|
}
|
|
data := map[string]interface{}{
|
|
"startX": startX,
|
|
"startY": startY,
|
|
"endX": endX,
|
|
"endY": endY,
|
|
"steps": steps[0],
|
|
}
|
|
return d._drag(data)
|
|
}
|
|
|
|
func (d *uiaDriver) DragPoint(startPoint Point, endPoint Point, steps ...int) error {
|
|
return d.Drag(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y, steps...)
|
|
}
|
|
|
|
func (d *uiaDriver) DragPointF(startPoint PointF, endPoint PointF, steps ...int) (err error) {
|
|
return d.DragFloat(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y, steps...)
|
|
}
|
|
|
|
func (d *uiaDriver) TouchLongClick(x, y int, duration ...float64) (err error) {
|
|
if len(duration) == 0 {
|
|
duration = []float64{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(duration[0] * 1000),
|
|
},
|
|
}
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "touch/longclick")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) TouchLongClickPoint(point Point, duration ...float64) (err error) {
|
|
return d.TouchLongClick(point.X, point.Y, duration...)
|
|
}
|
|
|
|
func (d *uiaDriver) SendKeys(text string, isReplace ...bool) (err error) {
|
|
if len(isReplace) == 0 {
|
|
isReplace = []bool{true}
|
|
}
|
|
// register(postHandler, new SendKeysToElement("/wd/hub/session/:sessionId/keys"))
|
|
// https://github.com/appium/appium-uiautomator2-server/blob/master/app/src/main/java/io/appium/uiautomator2/handler/SendKeysToElement.java#L76-L85
|
|
data := map[string]interface{}{
|
|
"text": text,
|
|
"replace": isReplace[0],
|
|
}
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "keys")
|
|
return
|
|
}
|
|
|
|
// PressBack simulates a short press on the BACK button.
|
|
func (d *uiaDriver) PressBack() (err error) {
|
|
// register(postHandler, new PressBack("/wd/hub/session/:sessionId/back"))
|
|
_, err = d.httpPOST(nil, "/session", d.sessionId, "back")
|
|
return
|
|
}
|
|
|
|
// public class KeyCodeModel extends BaseModel {
|
|
// @RequiredField
|
|
// public Integer keycode;
|
|
// public Integer metastate;
|
|
// public Integer flags;
|
|
// }
|
|
func (d *uiaDriver) LongPressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) {
|
|
if len(flags) == 0 {
|
|
flags = []KeyFlag{KFFromSystem}
|
|
}
|
|
data := map[string]interface{}{
|
|
"keycode": keyCode,
|
|
"metastate": metaState,
|
|
"flags": flags[0],
|
|
}
|
|
// register(postHandler, new LongPressKeyCode("/wd/hub/session/:sessionId/appium/device/long_press_keycode"))
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "/appium/device/long_press_keycode")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) _pressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) {
|
|
// register(postHandler, new PressKeyCodeAsync("/wd/hub/session/:sessionId/appium/device/press_keycode"))
|
|
data := map[string]interface{}{
|
|
"keycode": keyCode,
|
|
}
|
|
if metaState != KMEmpty {
|
|
data["metastate"] = metaState
|
|
}
|
|
if len(flags) != 0 {
|
|
data["flags"] = flags[0]
|
|
}
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "appium/device/press_keycode")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) {
|
|
if len(flags) == 0 {
|
|
flags = []KeyFlag{KFFromSystem}
|
|
}
|
|
return d._pressKeyCode(keyCode, metaState, KFFromSystem)
|
|
}
|
|
|
|
// PressKeyCodeAsync simulates a short press using a key code.
|
|
func (d *uiaDriver) PressKeyCodeAsync(keyCode KeyCode, metaState ...KeyMeta) (err error) {
|
|
if len(metaState) == 0 {
|
|
metaState = []KeyMeta{KMEmpty}
|
|
}
|
|
return d._pressKeyCode(keyCode, metaState[0])
|
|
}
|
|
|
|
func (d *uiaDriver) TouchDown(x, y int) (err error) {
|
|
// register(postHandler, new TouchDown("/wd/hub/session/:sessionId/touch/down"))
|
|
data := map[string]interface{}{
|
|
"params": map[string]interface{}{
|
|
"x": x,
|
|
"y": y,
|
|
},
|
|
}
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "touch/down")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) TouchDownPoint(point Point) error {
|
|
return d.TouchDown(point.X, point.Y)
|
|
}
|
|
|
|
func (d *uiaDriver) TouchUp(x, y int) (err error) {
|
|
// register(postHandler, new TouchUp("/wd/hub/session/:sessionId/touch/up"))
|
|
data := map[string]interface{}{
|
|
"params": map[string]interface{}{
|
|
"x": x,
|
|
"y": y,
|
|
},
|
|
}
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "touch/up")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) TouchUpPoint(point Point) error {
|
|
return d.TouchUp(point.X, point.Y)
|
|
}
|
|
|
|
func (d *uiaDriver) TouchMove(x, y int) (err error) {
|
|
// register(postHandler, new TouchMove("/wd/hub/session/:sessionId/touch/move"))
|
|
data := map[string]interface{}{
|
|
"params": map[string]interface{}{
|
|
"x": x,
|
|
"y": y,
|
|
},
|
|
}
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "touch/move")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) TouchMovePoint(point Point) error {
|
|
return d.TouchMove(point.X, point.Y)
|
|
}
|
|
|
|
// OpenNotification opens the notification shade.
|
|
func (d *uiaDriver) OpenNotification() (err error) {
|
|
// register(postHandler, new OpenNotification("/wd/hub/session/:sessionId/appium/device/open_notifications"))
|
|
_, err = d.httpPOST(nil, "/session", d.sessionId, "appium/device/open_notifications")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) _flick(data map[string]interface{}) (err error) {
|
|
// register(postHandler, new Flick("/wd/hub/session/:sessionId/touch/flick"))
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "touch/flick")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) Flick(xSpeed, ySpeed int) (err error) {
|
|
data := map[string]interface{}{
|
|
"xspeed": xSpeed,
|
|
"yspeed": ySpeed,
|
|
}
|
|
if xSpeed == 0 && ySpeed == 0 {
|
|
return errors.New("both 'xSpeed' and 'ySpeed' cannot be zero")
|
|
}
|
|
|
|
return d._flick(data)
|
|
}
|
|
|
|
func (d *uiaDriver) _scrollTo(method, selector string, maxSwipes int, elementID ...string) (err error) {
|
|
// register(postHandler, new ScrollTo("/wd/hub/session/:sessionId/touch/scroll"))
|
|
params := map[string]interface{}{
|
|
"strategy": method,
|
|
"selector": selector,
|
|
}
|
|
if maxSwipes > 0 {
|
|
params["maxSwipes"] = maxSwipes
|
|
}
|
|
data := map[string]interface{}{"params": params}
|
|
if len(elementID) != 0 {
|
|
data["origin"] = map[string]string{
|
|
legacyWebElementIdentifier: elementID[0],
|
|
webElementIdentifier: elementID[0],
|
|
}
|
|
}
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "touch/scroll")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) ScrollTo(by AndroidBySelector, maxSwipes ...int) (err error) {
|
|
if len(maxSwipes) == 0 {
|
|
maxSwipes = []int{0}
|
|
}
|
|
method, selector := by.getMethodAndSelector()
|
|
return d._scrollTo(method, selector, maxSwipes[0])
|
|
}
|
|
|
|
type W3CMouseButtonType int
|
|
|
|
const (
|
|
MBTLeft W3CMouseButtonType = 0
|
|
MBTMiddle W3CMouseButtonType = 1
|
|
MBTRight W3CMouseButtonType = 2
|
|
)
|
|
|
|
func (g *W3CGestures) PointerDown(button ...W3CMouseButtonType) *W3CGestures {
|
|
if len(button) == 0 {
|
|
button = []W3CMouseButtonType{MBTLeft}
|
|
}
|
|
*g = append(*g, _newW3CGesture().pointerDown(int(button[0])))
|
|
return g
|
|
}
|
|
|
|
func (g *W3CGestures) PointerUp(button ...W3CMouseButtonType) *W3CGestures {
|
|
if len(button) == 0 {
|
|
button = []W3CMouseButtonType{MBTLeft}
|
|
}
|
|
*g = append(*g, _newW3CGesture().pointerUp(int(button[0])))
|
|
return g
|
|
}
|
|
|
|
type W3CPointerMoveType string
|
|
|
|
const (
|
|
PMTViewport W3CPointerMoveType = "viewport"
|
|
PMTPointer W3CPointerMoveType = "pointer"
|
|
)
|
|
|
|
func (g *W3CGestures) PointerMove(x, y float64, origin interface{}, duration float64, pressure, size float64) *W3CGestures {
|
|
val := ""
|
|
switch v := origin.(type) {
|
|
case string:
|
|
val = v
|
|
case W3CPointerMoveType:
|
|
val = string(v)
|
|
case *uiaElement:
|
|
val = v.id
|
|
default:
|
|
val = string(PMTViewport)
|
|
}
|
|
*g = append(*g, _newW3CGesture().pointerMove(x, y, val, duration, pressure, size))
|
|
return g
|
|
}
|
|
|
|
func (g *W3CGestures) PointerMoveTo(x, y float64, duration ...float64) *W3CGestures {
|
|
if len(duration) == 0 || duration[0] < 0 {
|
|
duration = []float64{0.5}
|
|
}
|
|
*g = append(*g, _newW3CGesture().pointerMove(x, y, string(PMTViewport), duration[0]*1000))
|
|
return g
|
|
}
|
|
|
|
func (g *W3CGestures) PointerMoveRelative(x, y float64, duration ...float64) *W3CGestures {
|
|
if len(duration) == 0 || duration[0] < 0 {
|
|
duration = []float64{0.5}
|
|
}
|
|
*g = append(*g, _newW3CGesture().pointerMove(x, y, string(PMTPointer), duration[0]*1000))
|
|
return g
|
|
}
|
|
|
|
func (g *W3CGestures) PointerMouseOver(x, y float64, element *uiaElement, duration ...float64) *W3CGestures {
|
|
if len(duration) == 0 || duration[0] < 0 {
|
|
duration = []float64{0.5}
|
|
}
|
|
*g = append(*g, _newW3CGesture().pointerMove(x, y, element.id, duration[0]*1000))
|
|
return g
|
|
}
|
|
|
|
type W3CAction map[string]interface{}
|
|
|
|
type W3CActionType string
|
|
|
|
const (
|
|
_ W3CActionType = "none"
|
|
ATKey W3CActionType = "key"
|
|
ATPointer W3CActionType = "pointer"
|
|
)
|
|
|
|
type W3CPointerType string
|
|
|
|
const (
|
|
PTMouse W3CPointerType = "mouse"
|
|
PTPen W3CPointerType = "pen"
|
|
PTTouch W3CPointerType = "touch"
|
|
)
|
|
|
|
func NewW3CAction(actionType W3CActionType, gestures *W3CGestures, pointerType ...W3CPointerType) W3CAction {
|
|
w3cAction := make(W3CAction)
|
|
w3cAction["type"] = actionType
|
|
w3cAction["actions"] = gestures
|
|
if actionType != ATPointer {
|
|
return w3cAction
|
|
}
|
|
|
|
if len(pointerType) == 0 {
|
|
pointerType = []W3CPointerType{PTTouch}
|
|
}
|
|
type W3CItemParameters struct {
|
|
PointerType W3CPointerType `json:"pointerType"`
|
|
}
|
|
w3cAction["parameters"] = W3CItemParameters{PointerType: pointerType[0]}
|
|
return w3cAction
|
|
}
|
|
|
|
func (d *uiaDriver) PerformW3CActions(action W3CAction, acts ...W3CAction) (err error) {
|
|
var actionId uint64 = 1
|
|
acts = append([]W3CAction{action}, acts...)
|
|
for i := range acts {
|
|
item := acts[i]
|
|
item["id"] = strconv.FormatUint(actionId, 10)
|
|
actionId++
|
|
acts[i] = item
|
|
}
|
|
data := map[string]interface{}{
|
|
"actions": acts,
|
|
}
|
|
// register(postHandler, new W3CActions("/wd/hub/session/:sessionId/actions"))
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "/actions")
|
|
return
|
|
}
|
|
|
|
type ClipDataType string
|
|
|
|
const ClipDataTypePlaintext ClipDataType = "PLAINTEXT"
|
|
|
|
func (d *uiaDriver) GetClipboard(contentType ...ClipDataType) (content string, err error) {
|
|
if len(contentType) == 0 {
|
|
contentType = []ClipDataType{ClipDataTypePlaintext}
|
|
}
|
|
// register(postHandler, new GetClipboard("/wd/hub/session/:sessionId/appium/device/get_clipboard"))
|
|
data := map[string]interface{}{
|
|
"contentType": contentType[0],
|
|
}
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpPOST(data, "/session", d.sessionId, "appium/device/get_clipboard"); err != nil {
|
|
return "", err
|
|
}
|
|
reply := new(struct{ Value string })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
content = reply.Value
|
|
if data, err := base64.StdEncoding.DecodeString(content); err != nil {
|
|
return content, err
|
|
} else {
|
|
content = string(data)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) SetClipboard(contentType ClipDataType, content string, label ...string) (err error) {
|
|
lbl := content
|
|
if len(label) != 0 {
|
|
lbl = label[0]
|
|
}
|
|
const defaultLabelLen = 10
|
|
if len(lbl) > defaultLabelLen {
|
|
lbl = lbl[:defaultLabelLen]
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"contentType": contentType,
|
|
"label": lbl,
|
|
"content": base64.StdEncoding.EncodeToString([]byte(content)),
|
|
}
|
|
// register(postHandler, new SetClipboard("/wd/hub/session/:sessionId/appium/device/set_clipboard"))
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "appium/device/set_clipboard")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) AlertAccept(buttonLabel ...string) (err error) {
|
|
data := map[string]interface{}{
|
|
"buttonLabel": nil,
|
|
}
|
|
if len(buttonLabel) != 0 {
|
|
data["buttonLabel"] = buttonLabel[0]
|
|
}
|
|
// register(postHandler, new AcceptAlert("/wd/hub/session/:sessionId/alert/accept"))
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "alert/accept")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) AlertDismiss(buttonLabel ...string) (err error) {
|
|
data := map[string]interface{}{
|
|
"buttonLabel": nil,
|
|
}
|
|
if len(buttonLabel) != 0 {
|
|
data["buttonLabel"] = buttonLabel[0]
|
|
}
|
|
// register(postHandler, new DismissAlert("/wd/hub/session/:sessionId/alert/dismiss"))
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "alert/dismiss")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) SetAppiumSettings(settings map[string]interface{}) (err error) {
|
|
data := map[string]interface{}{
|
|
"settings": settings,
|
|
}
|
|
// register(postHandler, new UpdateSettings("/wd/hub/session/:sessionId/appium/settings"))
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "appium/settings")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) SetOrientation(orientation Orientation) (err error) {
|
|
data := map[string]interface{}{
|
|
"orientation": orientation,
|
|
}
|
|
// register(postHandler, new SetOrientation("/wd/hub/session/:sessionId/orientation"))
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "orientation")
|
|
return
|
|
}
|
|
|
|
// SetRotation
|
|
// `x` and `y` are ignored. We only care about `z`
|
|
// 0/90/180/270
|
|
func (d *uiaDriver) SetRotation(rotation Rotation) (err error) {
|
|
data := map[string]interface{}{
|
|
"z": rotation.Z,
|
|
}
|
|
// register(postHandler, new SetRotation("/wd/hub/session/:sessionId/rotation"))
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "rotation")
|
|
return
|
|
}
|
|
|
|
type NetworkType int
|
|
|
|
const (
|
|
NetworkTypeWifi NetworkType = 2
|
|
|
|
// NetworkTypeNone NetworkType = iota
|
|
// NetworkTypeAirplane
|
|
// NetworkTypeWifi
|
|
// _
|
|
// NetworkTypeData
|
|
// _
|
|
// NetworkTypeAll
|
|
)
|
|
|
|
// NetworkConnection always turn on
|
|
func (d *uiaDriver) NetworkConnection(networkType NetworkType) (err error) {
|
|
// register(postHandler, new NetworkConnection("/wd/hub/session/:sessionId/network_connection"))
|
|
data := map[string]interface{}{
|
|
"type": networkType,
|
|
}
|
|
_, err = d.httpPOST(data, "/session", d.sessionId, "network_connection")
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) _findElements(method, selector string, elementID ...string) (elements []*uiaElement, err error) {
|
|
// register(postHandler, new FindElements("/wd/hub/session/:sessionId/elements"))
|
|
data := map[string]interface{}{
|
|
"strategy": method,
|
|
"selector": selector,
|
|
}
|
|
if len(elementID) != 0 {
|
|
data["context"] = elementID[0]
|
|
}
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpPOST(data, "/session", d.sessionId, "/elements"); err != nil {
|
|
return nil, err
|
|
}
|
|
reply := new(struct{ Value []map[string]string })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(reply.Value) == 0 {
|
|
return nil, fmt.Errorf("no such element: unable to find an element using '%s', value '%s'", method, selector)
|
|
}
|
|
elements = make([]*uiaElement, len(reply.Value))
|
|
for i, elem := range reply.Value {
|
|
var id string
|
|
if id = elementIDFromValue(elem); id == "" {
|
|
return nil, fmt.Errorf("invalid element returned: %+v", reply)
|
|
}
|
|
elements[i] = &uiaElement{parent: d, id: id}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) _findElement(method, selector string, elementID ...string) (elem *uiaElement, err error) {
|
|
// register(postHandler, new FindElement("/wd/hub/session/:sessionId/element"))
|
|
data := map[string]interface{}{
|
|
"strategy": method,
|
|
"selector": selector,
|
|
}
|
|
if len(elementID) != 0 {
|
|
data["context"] = elementID[0]
|
|
}
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpPOST(data, "/session", d.sessionId, "/element"); err != nil {
|
|
return nil, err
|
|
}
|
|
reply := new(struct{ Value map[string]string })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(reply.Value) == 0 {
|
|
return nil, fmt.Errorf("no such element: unable to find an element using '%s', value '%s'", method, selector)
|
|
}
|
|
var id string
|
|
if id = elementIDFromValue(reply.Value); id == "" {
|
|
return nil, fmt.Errorf("invalid element returned: %+v", reply)
|
|
}
|
|
elem = &uiaElement{parent: d, id: id}
|
|
return
|
|
}
|
|
|
|
func (d *uiaDriver) FindElements(by AndroidBySelector) (elements []*uiaElement, err error) {
|
|
return d._findElements(by.getMethodAndSelector())
|
|
}
|
|
|
|
func (d *uiaDriver) FindElement(by AndroidBySelector) (elem *uiaElement, err error) {
|
|
return d._findElement(by.getMethodAndSelector())
|
|
}
|
|
|
|
func (d *uiaDriver) ActiveElement() (elem *uiaElement, err error) {
|
|
// register(getHandler, new ActiveElement("/wd/hub/session/:sessionId/element/active"))
|
|
var rawResp rawResponse
|
|
if rawResp, err = d.httpGET("/session", d.sessionId, "/element/active"); err != nil {
|
|
return nil, err
|
|
}
|
|
reply := new(struct{ Value map[string]string })
|
|
if err = json.Unmarshal(rawResp, reply); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(reply.Value) == 0 {
|
|
return nil, errors.New("no such element")
|
|
}
|
|
var id string
|
|
if id = elementIDFromValue(reply.Value); id == "" {
|
|
return nil, fmt.Errorf("invalid element returned: %+v", reply)
|
|
}
|
|
elem = &uiaElement{parent: d, id: id}
|
|
return
|
|
}
|
|
|
|
type AndroidCondition func(d *uiaDriver) (bool, error)
|
|
|
|
func (d *uiaDriver) _waitWithTimeoutAndInterval(condition AndroidCondition, timeout, interval time.Duration) (err error) {
|
|
startTime := time.Now()
|
|
for {
|
|
done, err := condition(d)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if done {
|
|
return nil
|
|
}
|
|
|
|
if elapsed := time.Since(startTime); elapsed > timeout {
|
|
return fmt.Errorf("timeout after %v", elapsed)
|
|
}
|
|
time.Sleep(interval)
|
|
}
|
|
}
|
|
|
|
// WaitWithTimeoutAndInterval waits for the condition to evaluate to true.
|
|
func (d *uiaDriver) WaitWithTimeoutAndInterval(condition AndroidCondition, timeout, interval float64) (err error) {
|
|
dTimeout := time.Millisecond * time.Duration(timeout*1000)
|
|
dInterval := time.Millisecond * time.Duration(interval*1000)
|
|
return d._waitWithTimeoutAndInterval(condition, dTimeout, dInterval)
|
|
}
|
|
|
|
// WaitWithTimeout works like WaitWithTimeoutAndInterval, but with default polling interval.
|
|
func (d *uiaDriver) WaitWithTimeout(condition AndroidCondition, timeout float64) error {
|
|
dTimeout := time.Millisecond * time.Duration(timeout*1000)
|
|
return d._waitWithTimeoutAndInterval(condition, dTimeout, DefaultWaitInterval)
|
|
}
|
|
|
|
// Wait works like WaitWithTimeoutAndInterval, but using the default timeout and polling interval.
|
|
func (d *uiaDriver) Wait(condition AndroidCondition) error {
|
|
return d._waitWithTimeoutAndInterval(condition, DefaultWaitTimeout, DefaultWaitInterval)
|
|
}
|