refactor: move guia2 into uixt

This commit is contained in:
debugtalk 2022-09-25 00:47:12 +08:00
parent 736f1d6c7b
commit 66b5d5cc49
18 changed files with 4557 additions and 155 deletions

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.18
require (
github.com/andybalholm/brotli v1.0.4
github.com/denisbrodbeck/machineid v1.0.1
github.com/electricbubble/gadb v0.0.7
github.com/electricbubble/gidevice v0.6.2
github.com/electricbubble/opencv-helper v0.0.3
github.com/fatih/color v1.13.0

2
go.sum
View File

@ -98,6 +98,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/electricbubble/gadb v0.0.7 h1:fxvVLVNs3IFKuYAEXDF2tDZUjT9jNCltoTSirjM5dgo=
github.com/electricbubble/gadb v0.0.7/go.mod h1:3293YJ6OWHv/Q6NA5dwSbK43MbmYm8+Vz2d7h5J3IA8=
github.com/electricbubble/gidevice v0.6.2 h1:eIeCHH7Xn5fTwnUv3qL8c7L4anKIHtjlTBkgr1LDVTc=
github.com/electricbubble/gidevice v0.6.2/go.mod h1:bRHL2M9qgeEKju8KRvKMZUVEg7t5zMnTiG3SJ3QDH5o=
github.com/electricbubble/opencv-helper v0.0.3 h1:p0sHTUPPPm8GqzVUtYH+wQbJoguzotUXVRAS7Ibk7nI=

View File

@ -38,13 +38,13 @@ This uixt module is initially forked from the following repos and made a lot of
- [electricbubble/gwda-ext-opencv]
- [electricbubble/gwda]
- [electricbubble/guia]
- [electricbubble/guia2]
[electricbubble/gwda-ext-opencv]: https://github.com/electricbubble/gwda-ext-opencv
[appium/WebDriverAgent]: https://github.com/appium/WebDriverAgent
[electricbubble/gwda]: https://github.com/electricbubble/gwda
[electricbubble/guia]: https://github.com/electricbubble/guia2
[electricbubble/guia2]: https://github.com/electricbubble/guia2
[OpenCV 4]: https://opencv.org/
[hybridgroup/gocv]: https://github.com/hybridgroup/gocv
[volcengine]: https://www.volcengine.com/product/text-recognition

View File

@ -1 +1,158 @@
package uixt
import "strings"
type touchGesture struct {
Touch PointF `json:"touch"`
Time float64 `json:"time"`
}
type TouchAction []touchGesture
func NewTouchAction(cap ...int) *TouchAction {
if len(cap) == 0 || cap[0] <= 0 {
cap = []int{8}
}
tmp := make(TouchAction, 0, cap[0])
return &tmp
}
func (ta *TouchAction) Add(x, y int, startTime ...float64) *TouchAction {
return ta.AddFloat(float64(x), float64(y), startTime...)
}
func (ta *TouchAction) AddFloat(x, y float64, startTime ...float64) *TouchAction {
if len(startTime) == 0 {
var tmp float64 = 0
if len(*ta) != 0 {
g := (*ta)[len(*ta)-1]
tmp = g.Time + 0.05
}
startTime = []float64{tmp}
}
*ta = append(*ta, touchGesture{Touch: PointF{x, y}, Time: startTime[0]})
return ta
}
func (ta *TouchAction) AddPoint(point Point, startTime ...float64) *TouchAction {
return ta.AddFloat(float64(point.X), float64(point.Y), startTime...)
}
func (ta *TouchAction) AddPointF(point PointF, startTime ...float64) *TouchAction {
return ta.AddFloat(point.X, point.Y, startTime...)
}
func (d *uiaDriver) MultiPointerGesture(gesture1 *TouchAction, gesture2 *TouchAction, tas ...*TouchAction) (err error) {
// Must provide coordinates for at least 2 pointers
actions := make([]*TouchAction, 0)
actions = append(actions, gesture1, gesture2)
if len(tas) != 0 {
actions = append(actions, tas...)
}
data := map[string]interface{}{
"actions": actions,
}
// register(postHandler, new MultiPointerGesture("/wd/hub/session/:sessionId/touch/multi/perform"))
_, err = d.httpPOST(data, "/session", d.sessionId, "/touch/multi/perform")
return
}
type w3cGesture map[string]interface{}
func _newW3CGesture() w3cGesture {
return make(w3cGesture)
}
func (g w3cGesture) _set(key string, value interface{}) w3cGesture {
g[key] = value
return g
}
func (g w3cGesture) pause(duration float64) w3cGesture {
return g._set("type", "pause").
_set("duration", duration)
}
func (g w3cGesture) keyDown(value string) w3cGesture {
return g._set("type", "keyDown").
_set("value", value)
}
func (g w3cGesture) keyUp(value string) w3cGesture {
return g._set("type", "keyUp").
_set("value", value)
}
func (g w3cGesture) pointerDown(button int) w3cGesture {
return g._set("type", "pointerDown")._set("button", button)
}
func (g w3cGesture) pointerUp(button int) w3cGesture {
return g._set("type", "pointerUp")._set("button", button)
}
func (g w3cGesture) pointerMove(x, y float64, origin string, duration float64, pressureAndSize ...float64) w3cGesture {
switch len(pressureAndSize) {
case 1:
g._set("pressure", pressureAndSize[0])
case 2:
g._set("pressure", pressureAndSize[0])
g._set("size", pressureAndSize[1])
}
return g._set("type", "pointerMove").
_set("duration", duration).
_set("origin", origin).
_set("x", x).
_set("y", y)
}
func (g w3cGesture) size(size ...float64) w3cGesture {
if len(size) == 0 {
size = []float64{1.0}
}
return g._set("size", size[0])
}
func (g w3cGesture) pressure(pressure ...float64) w3cGesture {
if len(pressure) == 0 {
pressure = []float64{1.0}
}
return g._set("pressure", pressure[0])
}
type W3CGestures []w3cGesture
func NewW3CGestures(cap ...int) *W3CGestures {
if len(cap) == 0 || cap[0] <= 0 {
cap = []int{8}
}
tmp := make(W3CGestures, 0, cap[0])
return &tmp
}
func (g *W3CGestures) Pause(duration ...float64) *W3CGestures {
if len(duration) == 0 || duration[0] < 0 {
duration = []float64{0.5}
}
*g = append(*g, _newW3CGesture().pause(duration[0]*1000))
return g
}
func (g *W3CGestures) KeyDown(value string) *W3CGestures {
*g = append(*g, _newW3CGesture().keyDown(value))
return g
}
func (g *W3CGestures) KeyUp(value string) *W3CGestures {
*g = append(*g, _newW3CGesture().keyUp(value))
return g
}
func (g *W3CGestures) SendKeys(text string) *W3CGestures {
ss := strings.Split(text, "")
for i := range ss {
g.KeyDown(ss[i])
g.KeyUp(ss[i])
}
return g
}

View File

@ -1,7 +1,117 @@
package uixt
import (
"bytes"
"fmt"
"net"
"reflect"
"github.com/electricbubble/gadb"
"github.com/pkg/errors"
)
var (
AdbServerHost = "localhost"
AdbServerPort = gadb.AdbServerPort // 5037
UIA2ServerPort = 6790
DeviceTempPath = "/data/local/tmp"
)
const forwardToPrefix = "forward-to-"
func InitUIAClient(device *AndroidDevice) (*DriverExt, error) {
var deviceOptions []AndroidDeviceOption
if device.SerialNumber != "" {
deviceOptions = append(deviceOptions, WithSerialNumber(device.SerialNumber))
}
if device.IP != "" {
deviceOptions = append(deviceOptions, WithAdbIP(device.IP))
}
if device.Port != 0 {
deviceOptions = append(deviceOptions, WithAdbPort(device.Port))
}
// init uia device
androidDevice, err := NewAndroidDevice(deviceOptions...)
if err != nil {
return nil, err
}
driver, err := androidDevice.NewUSBDriver(nil)
if err != nil {
return nil, errors.Wrap(err, "failed to init UIA driver")
}
fmt.Println(driver)
var driverExt *DriverExt
// TODO
// driverExt, err = Extend(driver)
// if err != nil {
// return nil, errors.Wrap(err, "failed to extend UIA Driver")
// }
return driverExt, nil
}
type AndroidDeviceOption func(*AndroidDevice)
func WithSerialNumber(serial string) AndroidDeviceOption {
return func(device *AndroidDevice) {
device.SerialNumber = serial
}
}
func WithAdbIP(ip string) AndroidDeviceOption {
return func(device *AndroidDevice) {
device.IP = ip
}
}
func WithAdbPort(port int) AndroidDeviceOption {
return func(device *AndroidDevice) {
device.Port = port
}
}
func WithAdbLogOn(logOn bool) AndroidDeviceOption {
return func(device *AndroidDevice) {
device.LogOn = logOn
}
}
func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, err error) {
deviceList, err := DeviceList()
if err != nil {
return nil, fmt.Errorf("get attached devices failed: %v", err)
}
device = &AndroidDevice{
Port: UIA2ServerPort,
IP: AdbServerHost,
}
for _, option := range options {
option(device)
}
serialNumber := device.SerialNumber
for _, dev := range deviceList {
// find device by serial number if specified
if serialNumber != "" && dev.Serial() != serialNumber {
continue
}
device.SerialNumber = dev.Serial()
device.Device = dev
return device, nil
}
return nil, fmt.Errorf("device %s not found", device.SerialNumber)
}
type AndroidDevice struct {
gadb.Device
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
IP string `json:"ip,omitempty" yaml:"ip,omitempty"`
Port int `json:"port,omitempty" yaml:"port,omitempty"`
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
}
@ -10,6 +120,436 @@ func (o AndroidDevice) UUID() string {
return o.SerialNumber
}
func InitUIAClient(device *AndroidDevice) (*DriverExt, error) {
return nil, nil
func DeviceList() (devices []gadb.Device, err error) {
var adbClient gadb.Client
if adbClient, err = gadb.NewClientWith(AdbServerHost, AdbServerPort); err != nil {
return nil, err
}
return adbClient.DeviceList()
}
// NewUSBDriver creates new client via USB connected device, this will also start a new session.
// TODO: replace uiaDriver with WebDriver
func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver *uiaDriver, err error) {
var localPort int
if localPort, err = getFreePort(); err != nil {
return nil, err
}
if err = dev.Forward(localPort, UIA2ServerPort); err != nil {
return nil, err
}
rawURL := fmt.Sprintf("http://%s%d:6790/wd/hub", forwardToPrefix, localPort)
driver, err = NewUIADriver(capabilities, rawURL)
if err != nil {
_ = dev.ForwardKill(localPort)
return nil, err
}
driver.adbDevice = dev.Device
driver.localPort = localPort
conn, err := net.Dial("tcp", fmt.Sprintf(":%d", localPort))
if err != nil {
return nil, fmt.Errorf("adb forward: %w", err)
}
driver.client = convertToHTTPClient(conn)
return driver, nil
}
// NewHTTPDriver creates new remote HTTP client, this will also start a new session.
// TODO: replace uiaDriver with WebDriver
func (dev *AndroidDevice) NewHTTPDriver(capabilities Capabilities) (driver *uiaDriver, err error) {
rawURL := fmt.Sprintf("http://%s:%d/wd/hub", dev.IP, dev.Port)
if driver, err = NewUIADriver(capabilities, rawURL); err != nil {
return nil, err
}
driver.adbDevice = dev.Device
return driver, nil
}
func getFreePort() (int, error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, fmt.Errorf("free port: %w", err)
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, fmt.Errorf("free port: %w", err)
}
defer func() { _ = l.Close() }()
return l.Addr().(*net.TCPAddr).Port, nil
}
type UiSelectorHelper struct {
value *bytes.Buffer
}
func NewUiSelectorHelper() UiSelectorHelper {
return UiSelectorHelper{value: bytes.NewBufferString("new UiSelector()")}
}
func (s UiSelectorHelper) String() string {
return s.value.String() + ";"
}
// Text Set the search criteria to match the visible text displayed
// in a widget (for example, the text label to launch an app).
//
// The text for the element must match exactly with the string in your input
// argument. Matching is case-sensitive.
func (s UiSelectorHelper) Text(text string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.text("%s")`, text))
return s
}
// TextMatches Set the search criteria to match the visible text displayed in a layout
// element, using a regular expression.
//
// The text in the widget must match exactly with the string in your
// input argument.
func (s UiSelectorHelper) TextMatches(regex string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.textMatches("%s")`, regex))
return s
}
// TextStartsWith Set the search criteria to match visible text in a widget that is
// prefixed by the text parameter.
//
// The matching is case-insensitive.
func (s UiSelectorHelper) TextStartsWith(text string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.textStartsWith("%s")`, text))
return s
}
// TextContains Set the search criteria to match the visible text in a widget
// where the visible text must contain the string in your input argument.
//
// The matching is case-sensitive.
func (s UiSelectorHelper) TextContains(text string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.textContains("%s")`, text))
return s
}
// ClassName Set the search criteria to match the class property
// for a widget (for example, "android.widget.Button").
func (s UiSelectorHelper) ClassName(className string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.className("%s")`, className))
return s
}
// ClassNameMatches Set the search criteria to match the class property
// for a widget, using a regular expression.
func (s UiSelectorHelper) ClassNameMatches(regex string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.classNameMatches("%s")`, regex))
return s
}
// Description Set the search criteria to match the content-description
// property for a widget.
//
// The content-description is typically used
// by the Android Accessibility framework to
// provide an audio prompt for the widget when
// the widget is selected. The content-description
// for the widget must match exactly
// with the string in your input argument.
//
// Matching is case-sensitive.
func (s UiSelectorHelper) Description(desc string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.description("%s")`, desc))
return s
}
// DescriptionMatches Set the search criteria to match the content-description
// property for a widget.
//
// The content-description is typically used
// by the Android Accessibility framework to
// provide an audio prompt for the widget when
// the widget is selected. The content-description
// for the widget must match exactly
// with the string in your input argument.
func (s UiSelectorHelper) DescriptionMatches(regex string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.descriptionMatches("%s")`, regex))
return s
}
// DescriptionStartsWith Set the search criteria to match the content-description
// property for a widget.
//
// The content-description is typically used
// by the Android Accessibility framework to
// provide an audio prompt for the widget when
// the widget is selected. The content-description
// for the widget must start
// with the string in your input argument.
//
// Matching is case-insensitive.
func (s UiSelectorHelper) DescriptionStartsWith(desc string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.descriptionStartsWith("%s")`, desc))
return s
}
// DescriptionContains Set the search criteria to match the content-description
// property for a widget.
//
// The content-description is typically used
// by the Android Accessibility framework to
// provide an audio prompt for the widget when
// the widget is selected. The content-description
// for the widget must contain
// the string in your input argument.
//
// Matching is case-insensitive.
func (s UiSelectorHelper) DescriptionContains(desc string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.descriptionContains("%s")`, desc))
return s
}
// ResourceId Set the search criteria to match the given resource ID.
func (s UiSelectorHelper) ResourceId(id string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.resourceId("%s")`, id))
return s
}
// ResourceIdMatches Set the search criteria to match the resource ID
// of the widget, using a regular expression.
func (s UiSelectorHelper) ResourceIdMatches(regex string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.resourceIdMatches("%s")`, regex))
return s
}
// Index Set the search criteria to match the widget by its node
// index in the layout hierarchy.
//
// The index value must be 0 or greater.
//
// Using the index can be unreliable and should only
// be used as a last resort for matching. Instead,
// consider using the `Instance(int)` method.
func (s UiSelectorHelper) Index(index int) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.index(%d)`, index))
return s
}
// Instance Set the search criteria to match the
// widget by its instance number.
//
// The instance value must be 0 or greater, where
// the first instance is 0.
//
// For example, to simulate a user click on
// the third image that is enabled in a UI screen, you
// could specify a a search criteria where the instance is
// 2, the `className(String)` matches the image
// widget class, and `enabled(boolean)` is true.
// The code would look like this:
// `new UiSelector().className("android.widget.ImageView")
// .enabled(true).instance(2);`
func (s UiSelectorHelper) Instance(instance int) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.instance(%d)`, instance))
return s
}
// Enabled Set the search criteria to match widgets that are enabled.
//
// Typically, using this search criteria alone is not useful.
// You should also include additional criteria, such as text,
// content-description, or the class name for a widget.
//
// If no other search criteria is specified, and there is more
// than one matching widget, the first widget in the tree
// is selected.
func (s UiSelectorHelper) Enabled(b bool) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.enabled(%t)`, b))
return s
}
// Focused Set the search criteria to match widgets that have focus.
//
// Typically, using this search criteria alone is not useful.
// You should also include additional criteria, such as text,
// content-description, or the class name for a widget.
//
// If no other search criteria is specified, and there is more
// than one matching widget, the first widget in the tree
// is selected.
func (s UiSelectorHelper) Focused(b bool) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.focused(%t)`, b))
return s
}
// Focusable Set the search criteria to match widgets that are focusable.
//
// Typically, using this search criteria alone is not useful.
// You should also include additional criteria, such as text,
// content-description, or the class name for a widget.
//
// If no other search criteria is specified, and there is more
// than one matching widget, the first widget in the tree
// is selected.
func (s UiSelectorHelper) Focusable(b bool) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.focusable(%t)`, b))
return s
}
// Scrollable Set the search criteria to match widgets that are scrollable.
//
// Typically, using this search criteria alone is not useful.
// You should also include additional criteria, such as text,
// content-description, or the class name for a widget.
//
// If no other search criteria is specified, and there is more
// than one matching widget, the first widget in the tree
// is selected.
func (s UiSelectorHelper) Scrollable(b bool) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.scrollable(%t)`, b))
return s
}
// Selected Set the search criteria to match widgets that
// are currently selected.
//
// Typically, using this search criteria alone is not useful.
// You should also include additional criteria, such as text,
// content-description, or the class name for a widget.
//
// If no other search criteria is specified, and there is more
// than one matching widget, the first widget in the tree
// is selected.
func (s UiSelectorHelper) Selected(b bool) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.selected(%t)`, b))
return s
}
// Checked Set the search criteria to match widgets that
// are currently checked (usually for checkboxes).
//
// Typically, using this search criteria alone is not useful.
// You should also include additional criteria, such as text,
// content-description, or the class name for a widget.
//
// If no other search criteria is specified, and there is more
// than one matching widget, the first widget in the tree
// is selected.
func (s UiSelectorHelper) Checked(b bool) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.checked(%t)`, b))
return s
}
// Checkable Set the search criteria to match widgets that are checkable.
//
// Typically, using this search criteria alone is not useful.
// You should also include additional criteria, such as text,
// content-description, or the class name for a widget.
//
// If no other search criteria is specified, and there is more
// than one matching widget, the first widget in the tree
// is selected.
func (s UiSelectorHelper) Checkable(b bool) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.checkable(%t)`, b))
return s
}
// Clickable Set the search criteria to match widgets that are clickable.
//
// Typically, using this search criteria alone is not useful.
// You should also include additional criteria, such as text,
// content-description, or the class name for a widget.
//
// If no other search criteria is specified, and there is more
// than one matching widget, the first widget in the tree
// is selected.
func (s UiSelectorHelper) Clickable(b bool) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.clickable(%t)`, b))
return s
}
// LongClickable Set the search criteria to match widgets that are long-clickable.
//
// Typically, using this search criteria alone is not useful.
// You should also include additional criteria, such as text,
// content-description, or the class name for a widget.
//
// If no other search criteria is specified, and there is more
// than one matching widget, the first widget in the tree
// is selected.
func (s UiSelectorHelper) LongClickable(b bool) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.longClickable(%t)`, b))
return s
}
// packageName Set the search criteria to match the package name
// of the application that contains the widget.
func (s UiSelectorHelper) packageName(name string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.packageName(%s)`, name))
return s
}
// PackageNameMatches Set the search criteria to match the package name
// of the application that contains the widget.
func (s UiSelectorHelper) PackageNameMatches(regex string) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.packageNameMatches(%s)`, regex))
return s
}
// ChildSelector Adds a child UiSelector criteria to this selector.
//
// Use this selector to narrow the search scope to
// child widgets under a specific parent widget.
func (s UiSelectorHelper) ChildSelector(selector UiSelectorHelper) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.childSelector(%s)`, selector.value.String()))
return s
}
func (s UiSelectorHelper) PatternSelector(selector UiSelectorHelper) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.patternSelector(%s)`, selector.value.String()))
return s
}
func (s UiSelectorHelper) ContainerSelector(selector UiSelectorHelper) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.containerSelector(%s)`, selector.value.String()))
return s
}
// FromParent Adds a child UiSelector criteria to this selector which is used to
// start search from the parent widget.
//
// Use this selector to narrow the search scope to
// sibling widgets as well all child widgets under a parent.
func (s UiSelectorHelper) FromParent(selector UiSelectorHelper) UiSelectorHelper {
s.value.WriteString(fmt.Sprintf(`.fromParent(%s)`, selector.value.String()))
return s
}
type AndroidBySelector struct {
// Set the search criteria to match the given resource ResourceIdID.
ResourceIdID string `json:"id"`
// Set the search criteria to match the content-description property for a widget.
ContentDescription string `json:"accessibility id"`
XPath string `json:"xpath"`
// Set the search criteria to match the class property for a widget (for example, "android.widget.Button").
ClassName string `json:"class name"`
UiAutomator string `json:"-android uiautomator"`
}
func (by AndroidBySelector) getMethodAndSelector() (method, selector string) {
vBy := reflect.ValueOf(by)
tBy := reflect.TypeOf(by)
for i := 0; i < vBy.NumField(); i++ {
vi := vBy.Field(i).Interface()
// switch vi := vi.(type) {
// case string:
// selector = vi
// }
selector = vi.(string)
if selector != "" && selector != "UNKNOWN" {
method = tBy.Field(i).Tag.Get("json")
return
}
}
return
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,238 @@
package uixt
import (
"bytes"
"encoding/base64"
"encoding/json"
)
type uiaElement struct {
parent *uiaDriver
id string
}
func (e *uiaElement) Text() (text string, err error) {
// register(getHandler, new GetText("/wd/hub/session/:sessionId/element/:id/text"))
var rawResp rawResponse
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/text"); err != nil {
return "", err
}
reply := new(struct{ Value string })
if err = json.Unmarshal(rawResp, reply); err != nil {
return "", err
}
text = reply.Value
return
}
func (e *uiaElement) GetAttribute(name string) (attribute string, err error) {
// register(getHandler, new GetElementAttribute("/wd/hub/session/:sessionId/element/:id/attribute/:name"))
var rawResp rawResponse
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/attribute", name); err != nil {
return "", err
}
reply := new(struct{ Value string })
if err = json.Unmarshal(rawResp, reply); err != nil {
return "", err
}
attribute = reply.Value
return
}
func (e *uiaElement) ContentDescription() (name string, err error) {
// register(getHandler, new GetName("/wd/hub/session/:sessionId/element/:id/name"))
var rawResp rawResponse
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/name"); err != nil {
return "", err
}
reply := new(struct{ Value string })
if err = json.Unmarshal(rawResp, reply); err != nil {
return "", err
}
name = reply.Value
return
}
func (e *uiaElement) Size() (size Size, err error) {
// register(getHandler, new GetSize("/wd/hub/session/:sessionId/element/:id/size"))
var rawResp rawResponse
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/size"); err != nil {
return Size{-1, -1}, err
}
reply := new(struct{ Value Size })
if err = json.Unmarshal(rawResp, reply); err != nil {
return Size{-1, -1}, err
}
size = reply.Value
return
}
func (e *uiaElement) Rect() (rect Rect, err error) {
// register(getHandler, new GetRect("/wd/hub/session/:sessionId/element/:id/rect"))
var rawResp rawResponse
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/rect"); err != nil {
return Rect{}, err
}
reply := new(struct{ Value Rect })
if err = json.Unmarshal(rawResp, reply); err != nil {
return Rect{}, err
}
rect = reply.Value
return
}
func (e *uiaElement) Screenshot() (raw *bytes.Buffer, err error) {
// W3C endpoint
// register(getHandler, new GetElementScreenshot("/wd/hub/session/:sessionId/element/:id/screenshot"))
// JSONWP endpoint
// register(getHandler, new GetElementScreenshot("/wd/hub/session/:sessionId/screenshot/:id"))
var rawResp rawResponse
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/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 (e *uiaElement) Location() (point Point, err error) {
// register(getHandler, new Location("/wd/hub/session/:sessionId/element/:id/location"))
var rawResp rawResponse
if rawResp, err = e.parent.httpGET("/session", e.parent.sessionId, "/element", e.id, "/location"); err != nil {
return Point{-1, -1}, err
}
reply := new(struct{ Value Point })
if err = json.Unmarshal(rawResp, reply); err != nil {
return Point{-1, -1}, err
}
point = reply.Value
return
}
func (e *uiaElement) Click() (err error) {
// register(postHandler, new Click("/wd/hub/session/:sessionId/element/:id/click"))
_, err = e.parent.httpPOST(nil, "/session", e.parent.sessionId, "/element", e.id, "/click")
return
}
func (e *uiaElement) Clear() (err error) {
// register(postHandler, new Clear("/wd/hub/session/:sessionId/element/:id/clear"))
_, err = e.parent.httpPOST(nil, "/session", e.parent.sessionId, "/element", e.id, "/clear")
return
}
func (e *uiaElement) SendKeys(text string, isReplace ...bool) (err error) {
if len(isReplace) == 0 {
isReplace = []bool{true}
}
// register(postHandler, new SendKeysToElement("/wd/hub/session/:sessionId/element/:id/value"))
// 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 = e.parent.httpPOST(data, "/session", e.parent.sessionId, "/element", e.id, "/value")
return
}
func (e *uiaElement) FindElements(by AndroidBySelector) (elements []*uiaElement, err error) {
method, selector := by.getMethodAndSelector()
return e.parent._findElements(method, selector, e.id)
}
func (e *uiaElement) FindElement(by AndroidBySelector) (elem *uiaElement, err error) {
method, selector := by.getMethodAndSelector()
return e.parent._findElement(method, selector, e.id)
}
func (e *uiaElement) Swipe(startX, startY, endX, endY int, steps ...int) (err error) {
return e.SwipeFloat(float64(startX), float64(startY), float64(endX), float64(endY), steps...)
}
func (e *uiaElement) SwipeFloat(startX, startY, endX, endY float64, steps ...int) (err error) {
if len(steps) == 0 {
steps = []int{12}
}
return e.parent._swipe(startX, startY, endX, endY, steps[0], e.id)
}
func (e *uiaElement) SwipePoint(startPoint, endPoint Point, steps ...int) (err error) {
return e.Swipe(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y, steps...)
}
func (e *uiaElement) SwipePointF(startPoint, endPoint PointF, steps ...int) (err error) {
return e.SwipeFloat(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y, steps...)
}
func (e *uiaElement) Drag(endX, endY int, steps ...int) (err error) {
return e.DragFloat(float64(endX), float64(endY), steps...)
}
func (e *uiaElement) DragFloat(endX, endY float64, steps ...int) error {
if len(steps) == 0 {
steps = []int{12 * 10}
} else {
steps[0] = 12 * 10
}
data := map[string]interface{}{
"elementId": e.id,
"endX": endX,
"endY": endY,
"steps": steps[0],
}
return e.parent._drag(data)
}
func (e *uiaElement) DragPoint(endPoint Point, steps ...int) error {
return e.Drag(endPoint.X, endPoint.Y, steps...)
}
func (e *uiaElement) DragPointF(endPoint PointF, steps ...int) (err error) {
return e.DragFloat(endPoint.X, endPoint.Y, steps...)
}
func (e *uiaElement) DragTo(destElem *uiaElement, steps ...int) error {
if len(steps) == 0 {
steps = []int{12}
}
data := map[string]interface{}{
"elementId": e.id,
"destElId": destElem.id,
"steps": steps[0],
}
return e.parent._drag(data)
}
func (e *uiaElement) Flick(xOffset, yOffset, speed int) (err error) {
data := map[string]interface{}{
legacyWebElementIdentifier: e.id,
webElementIdentifier: e.id,
"xoffset": xOffset,
"yoffset": yOffset,
"speed": speed,
}
return e.parent._flick(data)
}
func (e *uiaElement) ScrollTo(by AndroidBySelector, maxSwipes ...int) (err error) {
if len(maxSwipes) == 0 {
maxSwipes = []int{0}
}
method, selector := by.getMethodAndSelector()
return e.parent._scrollTo(method, selector, maxSwipes[0], e.id)
}
func (e *uiaElement) ScrollToElement(element *uiaElement) (err error) {
// register(postHandler, new ScrollToElement("/wd/hub/session/:sessionId/appium/element/:id/scroll_to/:id2"))
_, err = e.parent.httpPOST(nil, "/session", e.parent.sessionId, "/appium/element", e.id, "/scroll_to", element.id)
return
}

View File

@ -0,0 +1,879 @@
package uixt
type KeyMeta int
const (
KMEmpty KeyMeta = 0 // As a `null`
KMCapLocked KeyMeta = 0x100 // SHIFT key locked in CAPS mode.
KMAltLocked KeyMeta = 0x200 // ALT key locked.
KMSymLocked KeyMeta = 0x400 // SYM key locked.
KMSelecting KeyMeta = 0x800 // Text is in selection mode.
// KMAltOn KeyMeta = 0x02 // This mask is used to check whether one of the ALT meta keys is pressed.
// KMAltLeftOn KeyMeta = 0x10 // This mask is used to check whether the left ALT meta key is pressed.
// KMAltRightOn KeyMeta = 0x20 // This mask is used to check whether the right the ALT meta key is pressed.
// KMShiftOn KeyMeta = 0x1 // This mask is used to check whether one of the SHIFT meta keys is pressed.
// KMShiftLeftOn KeyMeta = 0x40 // This mask is used to check whether the left SHIFT meta key is pressed.
// KMShiftRightOn KeyMeta = 0x80 // This mask is used to check whether the right SHIFT meta key is pressed.
// KMSymOn KeyMeta = 0x4 // This mask is used to check whether the SYM meta key is pressed.
// KMFunctionOn KeyMeta = 0x8 // This mask is used to check whether the FUNCTION meta key is pressed.
// KMCtrlOn KeyMeta = 0x1000 // This mask is used to check whether one of the CTRL meta keys is pressed.
// KMCtrlLeftOn KeyMeta = 0x2000 // This mask is used to check whether the left CTRL meta key is pressed.
// KMCtrlRightOn KeyMeta = 0x4000 // This mask is used to check whether the right CTRL meta key is pressed.
// KMMetaOn KeyMeta = 0x10000 // This mask is used to check whether one of the META meta keys is pressed.
// KMMetaLeftOn KeyMeta = 0x20000 // This mask is used to check whether the left META meta key is pressed.
// KMMetaRightOn KeyMeta = 0x40000 // This mask is used to check whether the right META meta key is pressed.
// KMCapsLockOn KeyMeta = 0x100000 // This mask is used to check whether the CAPS LOCK meta key is on.
// KMNumLockOn KeyMeta = 0x200000 // This mask is used to check whether the NUM LOCK meta key is on.
// KMScrollLockOn KeyMeta = 0x400000 // This mask is used to check whether the SCROLL LOCK meta key is on.
// KMShiftMask = KMShiftOn | KMShiftLeftOn | KMShiftRightOn
// KMAltMask = KMAltOn | KMAltLeftOn | KMAltRightOn
// KMCtrlMask = KMCtrlOn | KMCtrlLeftOn | KMCtrlRightOn
// KMMetaMask = KMMetaOn | KMMetaLeftOn | KMMetaRightOn
)
type KeyFlag int
const (
// KFWokeHere This mask is set if the device woke because of this key event.
// Deprecated
KFWokeHere KeyFlag = 0x1
// KFSoftKeyboard This mask is set if the key event was generated by a software keyboard.
KFSoftKeyboard KeyFlag = 0x2
// KFKeepTouchMode This mask is set if we don't want the key event to cause us to leave touch mode.
KFKeepTouchMode KeyFlag = 0x4
// KFFromSystem This mask is set if an event was known to come from a trusted part
// of the system. That is, the event is known to come from the user,
// and could not have been spoofed by a third party component.
KFFromSystem KeyFlag = 0x8
// KFEditorAction This mask is used for compatibility, to identify enter keys that are
// coming from an IME whose enter key has been auto-labelled "next" or
// "done". This allows TextView to dispatch these as normal enter keys
// for old applications, but still do the appropriate action when receiving them.
KFEditorAction KeyFlag = 0x10
// KFCanceled When associated with up key events, this indicates that the key press
// has been canceled. Typically this is used with virtual touch screen
// keys, where the user can slide from the virtual key area on to the
// display: in that case, the application will receive a canceled up
// event and should not perform the action normally associated with the
// key. Note that for this to work, the application can not perform an
// action for a key until it receives an up or the long press timeout has expired.
KFCanceled KeyFlag = 0x20
// KFVirtualHardKey This key event was generated by a virtual (on-screen) hard key area.
// Typically this is an area of the touchscreen, outside of the regular
// display, dedicated to "hardware" buttons.
KFVirtualHardKey KeyFlag = 0x40
// KFLongPress This flag is set for the first key repeat that occurs after the long press timeout.
KFLongPress KeyFlag = 0x80
// KFCanceledLongPress Set when a key event has `KFCanceled` set because a long
// press action was executed while it was down.
KFCanceledLongPress KeyFlag = 0x100
// KFTracking Set for `ACTION_UP` when this event's key code is still being
// tracked from its initial down. That is, somebody requested that tracking
// started on the key down and a long press has not caused
// the tracking to be canceled.
KFTracking KeyFlag = 0x200
// KFFallback Set when a key event has been synthesized to implement default behavior
// for an event that the application did not handle.
// Fallback key events are generated by unhandled trackball motions
// (to emulate a directional keypad) and by certain unhandled key presses
// that are declared in the key map (such as special function numeric keypad
// keys when numlock is off).
KFFallback KeyFlag = 0x400
// KFPredispatch Signifies that the key is being predispatched.
// KFPredispatch KeyFlag = 0x20000000
// KFStartTracking Private control to determine when an app is tracking a key sequence.
// KFStartTracking KeyFlag = 0x40000000
// KFTainted Private flag that indicates when the system has detected that this key event
// may be inconsistent with respect to the sequence of previously delivered key events,
// such as when a key up event is sent but the key was not down.
// KFTainted KeyFlag = 0x80000000
)
type KeyCode int
const (
_ KeyCode = 0 // Unknown key code.
// KCSoftLeft Soft Left key
// Usually situated below the display on phones and used as a multi-function
// feature key for selecting a software defined function shown on the bottom left
// of the display.
KCSoftLeft KeyCode = 1
// KCSoftRight Soft Right key.
// Usually situated below the display on phones and used as a multi-function
// feature key for selecting a software defined function shown on the bottom right
// of the display.
KCSoftRight KeyCode = 2
// KCHome Home key.
// This key is handled by the framework and is never delivered to applications.
KCHome KeyCode = 3
KCBack KeyCode = 4 // Back key
KCCall KeyCode = 5 // Call key
KCEndCall KeyCode = 6 // End Call key
KC0 KeyCode = 7 // '0' key
KC1 KeyCode = 8 // '1' key
KC2 KeyCode = 9 // '2' key
KC3 KeyCode = 10 // '3' key
KC4 KeyCode = 11 // '4' key
KC5 KeyCode = 12 // '5' key
KC6 KeyCode = 13 // '6' key
KC7 KeyCode = 14 // '7' key
KC8 KeyCode = 15 // '8' key
KC9 KeyCode = 16 // '9' key
KCStar KeyCode = 17 // '*' key
KCPound KeyCode = 18 // '#' key
// KCDPadUp KeycodeDPadUp Directional Pad Up key.
// May also be synthesized from trackball motions.
KCDPadUp KeyCode = 19
// KCDPadDown Directional Pad Down key.
// May also be synthesized from trackball motions.
KCDPadDown KeyCode = 20
// KCDPadLeft Directional Pad Left key.
// May also be synthesized from trackball motions.
KCDPadLeft KeyCode = 21
// KCDPadRight Directional Pad Right key.
// May also be synthesized from trackball motions.
KCDPadRight KeyCode = 22
// KCDPadCenter Directional Pad Center key.
// May also be synthesized from trackball motions.
KCDPadCenter KeyCode = 23
// KCVolumeUp Volume Up key.
// Adjusts the speaker volume up.
KCVolumeUp KeyCode = 24
// KCVolumeDown Volume Down key.
// Adjusts the speaker volume down.
KCVolumeDown KeyCode = 25
// KCPower Power key.
KCPower KeyCode = 26
// KCCamera Camera key.
// Used to launch a camera application or take pictures.
KCCamera KeyCode = 27
KCClear KeyCode = 28 // Clear key
KCa KeyCode = 29 // 'a' key
KCb KeyCode = 30 // 'b' key
KCc KeyCode = 31 // 'c' key
KCd KeyCode = 32 // 'd' key
KCe KeyCode = 33 // 'e' key
KCf KeyCode = 34 // 'f' key
KCg KeyCode = 35 // 'g' key
KCh KeyCode = 36 // 'h' key
KCi KeyCode = 37 // 'i' key
KCj KeyCode = 38 // 'j' key
KCk KeyCode = 39 // 'k' key
KCl KeyCode = 40 // 'l' key
KCm KeyCode = 41 // 'm' key
KCn KeyCode = 42 // 'n' key
KCo KeyCode = 43 // 'o' key
KCp KeyCode = 44 // 'p' key
KCq KeyCode = 45 // 'q' key
KCr KeyCode = 46 // 'r' key
KCs KeyCode = 47 // 's' key
KCt KeyCode = 48 // 't' key
KCu KeyCode = 49 // 'u' key
KCv KeyCode = 50 // 'v' key
KCw KeyCode = 51 // 'w' key
KCx KeyCode = 52 // 'x' key
KCy KeyCode = 53 // 'y' key
KCz KeyCode = 54 // 'z' key
KCComma KeyCode = 55 // ',' key
KCPeriod KeyCode = 56 // '.' key
KCAltLeft KeyCode = 57 // Left Alt modifier key
KCAltRight KeyCode = 58 // Right Alt modifier key
KCShiftLeft KeyCode = 59 // Left Shift modifier key
KCShiftRight KeyCode = 60 // Right Shift modifier key
KCTab KeyCode = 61 // Tab key
KCSpace KeyCode = 62 // Space key
// KCSym Symbol modifier key.
// Used to enter alternate symbols.
KCSym KeyCode = 63
// KCExplorer Explorer special function key.
// Used to launch a browser application.
KCExplorer KeyCode = 64
// KCEnvelope Envelope special function key.
// Used to launch a mail application.
KCEnvelope KeyCode = 65
// KCEnter Enter key.
KCEnter KeyCode = 66
// KCDel Backspace key.
// Deletes characters before the insertion point, unlike `KCForwardDel`.
KCDel KeyCode = 67
KCGrave KeyCode = 68 // '`' (backtick) key
KCMinus KeyCode = 69 // '-'
KCEquals KeyCode = 70 // '=' key
KCLeftBracket KeyCode = 71 // '[' key
KCRightBracket KeyCode = 72 // ']' key
KCBackslash KeyCode = 73 // '\' key
KCSemicolon KeyCode = 74 // '' key
KCApostrophe KeyCode = 75 // ''' (apostrophe) key
KCSlash KeyCode = 76 // '/' key
KCAt KeyCode = 77 // '@' key
// KCNum Number modifier key.
// Used to enter numeric symbols.
// This key is not Num Lock; it is more like `KCAltLeft` and is
// interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}.
KCNum KeyCode = 78
// KCHeadsetHook Headset Hook key.
// Used to hang up calls and stop media.
KCHeadsetHook KeyCode = 79
// KCFocus Camera Focus key.
// Used to focus the camera.
// *Camera* focus
KCFocus KeyCode = 80
KCPlus KeyCode = 81 // '+' key.
KCMenu KeyCode = 82 // Menu key.
KCNotification KeyCode = 83 // Notification key.
KCSearch KeyCode = 84 // Search key.
KCMediaPlayPause KeyCode = 85 // Play/Pause media key.
KCMediaStop KeyCode = 86 // Stop media key.
KCMediaNext KeyCode = 87 // Play Next media key.
KCMediaPrevious KeyCode = 88 // Play Previous media key.
KCMediaRewind KeyCode = 89 // Rewind media key.
KCMediaFastForward KeyCode = 90 // Fast Forward media key.
// KCMute Mute key.
// Mutes the microphone, unlike `KCVolumeMute`
KCMute KeyCode = 91
// KCPageUp Page Up key.
KCPageUp KeyCode = 92
// KCPageDown Page Down key.
KCPageDown KeyCode = 93
// KCPictSymbols Picture Symbols modifier key.
// Used to switch symbol sets (Emoji, Kao-moji).
// switch symbol-sets (Emoji,Kao-moji)
KCPictSymbols KeyCode = 94
// KCSwitchCharset Switch Charset modifier key.
// Used to switch character sets (Kanji, Katakana).
// switch char-sets (Kanji,Katakana)
KCSwitchCharset KeyCode = 95
// KCButtonA A Button key.
// On a game controller, the A button should be either the button labeled A
// or the first button on the bottom row of controller buttons.
KCButtonA KeyCode = 96
// KCButtonB B Button key.
// On a game controller, the B button should be either the button labeled B
// or the second button on the bottom row of controller buttons.
KCButtonB KeyCode = 97
// KCButtonC C Button key.
// On a game controller, the C button should be either the button labeled C
// or the third button on the bottom row of controller buttons.
KCButtonC KeyCode = 98
// KCButtonX X Button key.
// On a game controller, the X button should be either the button labeled X
// or the first button on the upper row of controller buttons.
KCButtonX KeyCode = 99
// KCButtonY Y Button key.
// On a game controller, the Y button should be either the button labeled Y
// or the second button on the upper row of controller buttons.
KCButtonY KeyCode = 100
// KCButtonZ Z Button key.
// On a game controller, the Z button should be either the button labeled Z
// or the third button on the upper row of controller buttons.
KCButtonZ KeyCode = 101
// KCButtonL1 L1 Button key.
// On a game controller, the L1 button should be either the button labeled L1 (or L)
// or the top left trigger button.
KCButtonL1 KeyCode = 102
// KCButtonR1 R1 Button key.
// On a game controller, the R1 button should be either the button labeled R1 (or R)
// or the top right trigger button.
KCButtonR1 KeyCode = 103
// KCButtonL2 L2 Button key.
// On a game controller, the L2 button should be either the button labeled L2
// or the bottom left trigger button.
KCButtonL2 KeyCode = 104
// KCButtonR2 R2 Button key.
// On a game controller, the R2 button should be either the button labeled R2
// or the bottom right trigger button.
KCButtonR2 KeyCode = 105
// KCButtonTHUMBL Left Thumb Button key.
// On a game controller, the left thumb button indicates that the left (or only)
// joystick is pressed.
KCButtonTHUMBL KeyCode = 106
// KCButtonTHUMBR Right Thumb Button key.
// On a game controller, the right thumb button indicates that the right
// joystick is pressed.
KCButtonTHUMBR KeyCode = 107
// KCButtonStart Start Button key.
// On a game controller, the button labeled Start.
KCButtonStart KeyCode = 108
// KCButtonSelect Select Button key.
// On a game controller, the button labeled Select.
KCButtonSelect KeyCode = 109
// KCButtonMode Mode Button key.
// On a game controller, the button labeled Mode.
KCButtonMode KeyCode = 110
// KCEscape Escape key.
KCEscape KeyCode = 111
// KCForwardDel Forward Delete key.
// Deletes characters ahead of the insertion point, unlike `KCDel`.
KCForwardDel KeyCode = 112
KCCtrlLeft KeyCode = 113 // Left Control modifier key
KCCtrlRight KeyCode = 114 // Right Control modifier key
KCCapsLock KeyCode = 115 // Caps Lock key
KCScrollLock KeyCode = 116 // Scroll Lock key
KCMetaLeft KeyCode = 117 // Left Meta modifier key
KCMetaRight KeyCode = 118 // Right Meta modifier key
KCFunction KeyCode = 119 // Function modifier key
KCSysRq KeyCode = 120 // System Request / Print Screen key
KCBreak KeyCode = 121 // Break / Pause key
// KCMoveHome Home Movement key.
// Used for scrolling or moving the cursor around to the start of a line
// or to the top of a list.
KCMoveHome KeyCode = 122
// KCMoveEnd End Movement key.
// Used for scrolling or moving the cursor around to the end of a line
// or to the bottom of a list.
KCMoveEnd KeyCode = 123
// KCInsert Insert key.
// Toggles insert / overwrite edit mode.
KCInsert KeyCode = 124
// KCForward Forward key.
// Navigates forward in the history stack. Complement of `KCBack`.
KCForward KeyCode = 125
// KCMediaPlay Play media key.
KCMediaPlay KeyCode = 126
// KCMediaPause Pause media key.
KCMediaPause KeyCode = 127
// KCMediaClose Close media key.
// May be used to close a CD tray, for example.
KCMediaClose KeyCode = 128
// KCMediaEject Eject media key.
// May be used to eject a CD tray, for example.
KCMediaEject KeyCode = 129
// KCMediaRecord Record media key.
KCMediaRecord KeyCode = 130
KCF1 KeyCode = 131 // F1 key.
KCF2 KeyCode = 132 // F2 key.
KCF3 KeyCode = 133 // F3 key.
KCF4 KeyCode = 134 // F4 key.
KCF5 KeyCode = 135 // F5 key.
KCF6 KeyCode = 136 // F6 key.
KCF7 KeyCode = 137 // F7 key.
KCF8 KeyCode = 138 // F8 key.
KCF9 KeyCode = 139 // F9 key.
KCF10 KeyCode = 140 // F10 key.
KCF11 KeyCode = 141 // F11 key.
KCF12 KeyCode = 142 // F12 key.
// KCNumLock Num Lock key.
// This is the Num Lock key; it is different from `KCNum`.
// This key alters the behavior of other keys on the numeric keypad.
KCNumLock KeyCode = 143
KCNumpad0 KeyCode = 144 // Numeric keypad '0' key
KCNumpad1 KeyCode = 145 // Numeric keypad '1' key
KCNumpad2 KeyCode = 146 // Numeric keypad '2' key
KCNumpad3 KeyCode = 147 // Numeric keypad '3' key
KCNumpad4 KeyCode = 148 // Numeric keypad '4' key
KCNumpad5 KeyCode = 149 // Numeric keypad '5' key
KCNumpad6 KeyCode = 150 // Numeric keypad '6' key
KCNumpad7 KeyCode = 151 // Numeric keypad '7' key
KCNumpad8 KeyCode = 152 // Numeric keypad '8' key
KCNumpad9 KeyCode = 153 // Numeric keypad '9' key
KCNumpadDivide KeyCode = 154 // Numeric keypad '/' key (for division)
KCNumpadMultiply KeyCode = 155 // Numeric keypad '*' key (for multiplication)
KCNumpadSubtract KeyCode = 156 // Numeric keypad '-' key (for subtraction)
KCNumpadAdd KeyCode = 157 // Numeric keypad '+' key (for addition)
KCNumpadDot KeyCode = 158 // Numeric keypad '.' key (for decimals or digit grouping)
KCNumpadComma KeyCode = 159 // Numeric keypad ',' key (for decimals or digit grouping)
KCNumpadEnter KeyCode = 160 // Numeric keypad Enter key
KCNumpadEquals KeyCode = 161 // Numeric keypad 'KeyCode =' key
KCNumpadLeftParen KeyCode = 162 // Numeric keypad '(' key
KCNumpadRightParen KeyCode = 163 // Numeric keypad ')' key
// KCVolumeMute Volume Mute key.
// Mutes the speaker, unlike `KCMute`.
// This key should normally be implemented as a toggle such that the first press
// mutes the speaker and the second press restores the original volume.
KCVolumeMute KeyCode = 164
// KCInfo Info key.
// Common on TV remotes to show additional information related to what is
// currently being viewed.
KCInfo KeyCode = 165
// KCChannelUp Channel up key.
// On TV remotes, increments the television channel.
KCChannelUp KeyCode = 166
// KCChannelDown Channel down key.
// On TV remotes, decrements the television channel.
KCChannelDown KeyCode = 167
// KCZoomIn Zoom in key.
KCZoomIn KeyCode = 168
// KCZoomOut Zoom out key.
KCZoomOut KeyCode = 169
// KCTv TV key.
// On TV remotes, switches to viewing live TV.
KCTv KeyCode = 170
// KCWindow Window key.
// On TV remotes, toggles picture-in-picture mode or other windowing functions.
// On Android Wear devices, triggers a display offset.
KCWindow KeyCode = 171
// KCGuide Guide key.
// On TV remotes, shows a programming guide.
KCGuide KeyCode = 172
// KCDvr DVR key.
// On some TV remotes, switches to a DVR mode for recorded shows.
KCDvr KeyCode = 173
// KCBookmark Bookmark key.
// On some TV remotes, bookmarks content or web pages.
KCBookmark KeyCode = 174
// KCCaptions Toggle captions key.
// Switches the mode for closed-captioning text, for example during television shows.
KCCaptions KeyCode = 175
// KCSettings Settings key.
// Starts the system settings activity.
KCSettings KeyCode = 176
// KCTvPower TV power key.
// On TV remotes, toggles the power on a television screen.
KCTvPower KeyCode = 177
// KCTvInput TV input key.
// On TV remotes, switches the input on a television screen.
KCTvInput KeyCode = 178
// KCStbPower Set-top-box power key.
// On TV remotes, toggles the power on an external Set-top-box.
KCStbPower KeyCode = 179
// KCStbInput Set-top-box input key.
// On TV remotes, switches the input mode on an external Set-top-box.
KCStbInput KeyCode = 180
// KCAvrPower A/V Receiver power key.
// On TV remotes, toggles the power on an external A/V Receiver.
KCAvrPower KeyCode = 181
// KCAvrInput A/V Receiver input key.
// On TV remotes, switches the input mode on an external A/V Receiver.
KCAvrInput KeyCode = 182
// KCProgRed Red "programmable" key.
// On TV remotes, acts as a contextual/programmable key.
KCProgRed KeyCode = 183
// KCProgGreen Green "programmable" key.
// On TV remotes, actsas a contextual/programmable key.
KCProgGreen KeyCode = 184
// KCProgYellow Yellow "programmable" key.
// On TV remotes, acts as a contextual/programmable key.
KCProgYellow KeyCode = 185
// KCProgBlue Blue "programmable" key.
// On TV remotes, acts as a contextual/programmable key.
KCProgBlue KeyCode = 186
// KCAppSwitch App switch key.
// Should bring up the application switcher dialog.
KCAppSwitch KeyCode = 187
KCButton1 KeyCode = 188 // Generic Game Pad Button #1
KCButton2 KeyCode = 189 // Generic Game Pad Button #2
KCButton3 KeyCode = 190 // Generic Game Pad Button #3
KCButton4 KeyCode = 191 // Generic Game Pad Button #4
KCButton5 KeyCode = 192 // Generic Game Pad Button #5
KCButton6 KeyCode = 193 // Generic Game Pad Button #6
KCButton7 KeyCode = 194 // Generic Game Pad Button #7
KCButton8 KeyCode = 195 // Generic Game Pad Button #8
KCButton9 KeyCode = 196 // Generic Game Pad Button #9
KCButton10 KeyCode = 197 // Generic Game Pad Button #10
KCButton11 KeyCode = 198 // Generic Game Pad Button #11
KCButton12 KeyCode = 199 // Generic Game Pad Button #12
KCButton13 KeyCode = 200 // Generic Game Pad Button #13
KCButton14 KeyCode = 201 // Generic Game Pad Button #14
KCButton15 KeyCode = 202 // Generic Game Pad Button #15
KCButton16 KeyCode = 203 // Generic Game Pad Button #16
// KCLanguageSwitch Language Switch key.
// Toggles the current input language such as switching between English and Japanese on
// a QWERTY keyboard. On some devices, the same function may be performed by
// pressing Shift+Spacebar.
KCLanguageSwitch KeyCode = 204
// Manner Mode key.
// Toggles silent or vibrate mode on and off to make the device behave more politely
// in certain settings such as on a crowded train. On some devices, the key may only
// operate when long-pressed.
KCMannerMode KeyCode = 205
// 3D Mode key.
// Toggles the display between 2D and 3D mode.
KC3dMode KeyCode = 206
// Contacts special function key.
// Used to launch an address book application.
KCContacts KeyCode = 207
// Calendar special function key.
// Used to launch a calendar application.
KCCalendar KeyCode = 208
// Music special function key.
// Used to launch a music player application.
KCMusic KeyCode = 209
// Calculator special function key.
// Used to launch a calculator application.
KCCalculator KeyCode = 210
// Japanese full-width / half-width key.
KCZenkakuHankaku KeyCode = 211
// Japanese alphanumeric key.
KCEisu KeyCode = 212
// Japanese non-conversion key.
KCMuhenkan KeyCode = 213
// Japanese conversion key.
KCHenkan KeyCode = 214
// Japanese katakana / hiragana key.
KCKatakanaHiragana KeyCode = 215
// Japanese Yen key.
KCYen KeyCode = 216
// Japanese Ro key.
KCRo KeyCode = 217
// Japanese kana key.
KCKana KeyCode = 218
// Assist key.
// Launches the global assist activity. Not delivered to applications.
KCAssist KeyCode = 219
// Brightness Down key.
// Adjusts the screen brightness down.
KCBrightnessDown KeyCode = 220
// Brightness Up key.
// Adjusts the screen brightness up.
KCBrightnessUp KeyCode = 221
// Audio Track key.
// Switches the audio tracks.
KCMediaAudioTrack KeyCode = 222
// Sleep key.
// Puts the device to sleep. Behaves somewhat like {@link #KEYCODE_POWER} but it
// has no effect if the device is already asleep.
KCSleep KeyCode = 223
// Wakeup key.
// Wakes up the device. Behaves somewhat like {@link #KEYCODE_POWER} but it
// has no effect if the device is already awake.
KCWakeup KeyCode = 224
// Pairing key.
// Initiates peripheral pairing mode. Useful for pairing remote control
// devices or game controllers, especially if no other input mode is
// available.
KCPairing KeyCode = 225
// Media Top Menu key.
// Goes to the top of media menu.
KCMediaTopMenu KeyCode = 226
// '11' key.
KC11 KeyCode = 227
// '12' key.
KC12 KeyCode = 228
// Last Channel key.
// Goes to the last viewed channel.
KCLastChannel KeyCode = 229
// TV data service key.
// Displays data services like weather, sports.
KCTvDataService KeyCode = 230
// Voice Assist key.
// Launches the global voice assist activity. Not delivered to applications.
KCVoiceAssist KeyCode = 231
// Radio key.
// Toggles TV service / Radio service.
KCTvRadioService KeyCode = 232
// Teletext key.
// Displays Teletext service.
KCTvTeletext KeyCode = 233
// Number entry key.
// Initiates to enter multi-digit channel nubmber when each digit key is assigned
// for selecting separate channel. Corresponds to Number Entry Mode (0x1D) of CEC
// User Control Code.
KCTvNumberEntry KeyCode = 234
// Analog Terrestrial key.
// Switches to analog terrestrial broadcast service.
KCTvTerrestrialAnalog KeyCode = 235
// Digital Terrestrial key.
// Switches to digital terrestrial broadcast service.
KCTvTerrestrialDigital KeyCode = 236
// Satellite key.
// Switches to digital satellite broadcast service.
KCTvSatellite KeyCode = 237
// BS key.
// Switches to BS digital satellite broadcasting service available in Japan.
KCTvSatelliteBs KeyCode = 238
// CS key.
// Switches to CS digital satellite broadcasting service available in Japan.
KCTvSatelliteCs KeyCode = 239
// BS/CS key.
// Toggles between BS and CS digital satellite services.
KCTvSatelliteService KeyCode = 240
// Toggle Network key.
// Toggles selecting broacast services.
KCTvNetwork KeyCode = 241
// Antenna/Cable key.
// Toggles broadcast input source between antenna and cable.
KCTvAntennaCable KeyCode = 242
// HDMI #1 key.
// Switches to HDMI input #1.
KCTvInputHdmi1 KeyCode = 243
// HDMI #2 key.
// Switches to HDMI input #2.
KCTvInputHdmi2 KeyCode = 244
// HDMI #3 key.
// Switches to HDMI input #3.
KCTvInputHdmi3 KeyCode = 245
// HDMI #4 key.
// Switches to HDMI input #4.
KCTvInputHdmi4 KeyCode = 246
// Composite #1 key.
// Switches to composite video input #1.
KCTvInputComposite1 KeyCode = 247
// Composite #2 key.
// Switches to composite video input #2.
KCTvInputComposite2 KeyCode = 248
// Component #1 key.
// Switches to component video input #1.
KCTvInputComponent1 KeyCode = 249
// Component #2 key.
// Switches to component video input #2.
KCTvInputComponent2 KeyCode = 250
// VGA #1 key.
// Switches to VGA (analog RGB) input #1.
KCTvInputVga1 KeyCode = 251
// Audio description key.
// Toggles audio description off / on.
KCTvAudioDescription KeyCode = 252
// Audio description mixing volume up key.
// Louden audio description volume as compared with normal audio volume.
KCTvAudioDescriptionMixUp KeyCode = 253
// Audio description mixing volume down key.
// Lessen audio description volume as compared with normal audio volume.
KCTvAudioDescriptionMixDown KeyCode = 254
// Zoom mode key.
// Changes Zoom mode (Normal, Full, Zoom, Wide-zoom, etc.)
KCTvZoomMode KeyCode = 255
// Contents menu key.
// Goes to the title list. Corresponds to Contents Menu (0x0B) of CEC User Control
// Code
KCTvContentsMenu KeyCode = 256
// Media context menu key.
// Goes to the context menu of media contents. Corresponds to Media Context-sensitive
// Menu (0x11) of CEC User Control Code.
KCTvMediaContextMenu KeyCode = 257
// Timer programming key.
// Goes to the timer recording menu. Corresponds to Timer Programming (0x54) of
// CEC User Control Code.
KCTvTimerProgramming KeyCode = 258
// Help key.
KCHelp KeyCode = 259
// Navigate to previous key.
// Goes backward by one item in an ordered collection of items.
KCNavigatePrevious KeyCode = 260
// Navigate to next key.
// Advances to the next item in an ordered collection of items.
KCNavigateNext KeyCode = 261
// Navigate in key.
// Activates the item that currently has focus or expands to the next level of a navigation
// hierarchy.
KCNavigateIn KeyCode = 262
// Navigate out key.
// Backs out one level of a navigation hierarchy or collapses the item that currently has
// focus.
KCNavigateOut KeyCode = 263
// Primary stem key for Wear
// Main power/reset button on watch.
KCStemPrimary KeyCode = 264
// Generic stem key 1 for Wear
KCStem1 KeyCode = 265
// Generic stem key 2 for Wear
KCStem2 KeyCode = 266
// Generic stem key 3 for Wear
KCStem3 KeyCode = 267
// Directional Pad Up-Left
KCDPadUpLeft KeyCode = 268
// Directional Pad Down-Left
KCDPadDownLeft KeyCode = 269
// Directional Pad Up-Right
KCDPadUpRight KeyCode = 270
// Directional Pad Down-Right
KCDPadDownRight KeyCode = 271
// Skip forward media key.
KCMediaSkipForward KeyCode = 272
// Skip backward media key.
KCMediaSkipBackward KeyCode = 273
// Step forward media key.
// Steps media forward, one frame at a time.
KCMediaStepForward KeyCode = 274
// Step backward media key.
// Steps media backward, one frame at a time.
KCMediaStepBackward KeyCode = 275
// put device to sleep unless a wakelock is held.
KCSoftSleep KeyCode = 276
// Cut key.
KCCut KeyCode = 277
// Copy key.
KCCopy KeyCode = 278
// Paste key.
KCPaste KeyCode = 279
// Consumed by the system for navigation up
KCSystemNavigationUp KeyCode = 280
// Consumed by the system for navigation down
KCSystemNavigationDown KeyCode = 281
// Consumed by the system for navigation left*/
KCSystemNavigationLeft KeyCode = 282
// Consumed by the system for navigation right
KCSystemNavigationRight KeyCode = 283
// Show all apps
KCAllApps KeyCode = 284
// Refresh key.
KCRefresh KeyCode = 285
)

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +0,0 @@
package uixt
type uiaWebDriver struct{}

View File

@ -1 +0,0 @@
package uixt

104
hrp/internal/uixt/client.go Normal file
View File

@ -0,0 +1,104 @@
package uixt
import (
"bytes"
"context"
"encoding/json"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"path"
"strings"
"time"
"github.com/rs/zerolog/log"
)
type Driver struct {
urlPrefix *url.URL
sessionId string
client *http.Client
}
func (wd *Driver) concatURL(u *url.URL, elem ...string) string {
var tmp *url.URL
if u == nil {
u = wd.urlPrefix
}
tmp, _ = url.Parse(u.String())
tmp.Path = path.Join(append([]string{u.Path}, elem...)...)
return tmp.String()
}
func (wd *Driver) httpGET(pathElem ...string) (rawResp rawResponse, err error) {
return wd.httpRequest(http.MethodGet, wd.concatURL(nil, pathElem...), nil)
}
func (wd *Driver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, 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(nil, pathElem...), bsJSON)
}
func (wd *Driver) httpDELETE(pathElem ...string) (rawResp rawResponse, err error) {
return wd.httpRequest(http.MethodDelete, wd.concatURL(nil, pathElem...), nil)
}
func (wd *Driver) httpRequest(method string, rawURL string, rawBody []byte) (rawResp rawResponse, err error) {
log.Debug().Str("method", method).Str("url", rawURL).Str("body", string(rawBody)).Msg("request WDA")
var req *http.Request
if req, err = http.NewRequest(method, rawURL, bytes.NewBuffer(rawBody)); err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Accept", "application/json")
start := time.Now()
var resp *http.Response
if resp, err = wd.client.Do(req); err != nil {
return nil, err
}
defer func() {
// https://github.com/etcd-io/etcd/blob/v3.3.25/pkg/httputil/httputil.go#L16-L22
_, _ = io.Copy(ioutil.Discard, resp.Body)
_ = resp.Body.Close()
}()
rawResp, err = ioutil.ReadAll(resp.Body)
logger := log.Debug().Int("statusCode", resp.StatusCode).Str("duration", time.Since(start).String())
if !strings.HasSuffix(rawURL, "screenshot") {
// avoid printing screenshot data
logger.Str("response", string(rawResp))
}
logger.Msg("get WDA response")
if err != nil {
return nil, err
}
if err = rawResp.checkErr(); err != nil {
if resp.StatusCode == http.StatusOK {
return rawResp, nil
}
return nil, err
}
return
}
func convertToHTTPClient(conn net.Conn) *http.Client {
return &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return conn, nil
},
},
Timeout: 0,
}
}

View File

@ -4,6 +4,7 @@ package uixt
import (
"image"
"sort"
)
func (dExt *DriverExt) GesturePassword(pathname string, password ...int) (err error) {

View File

@ -164,6 +164,8 @@ type BatteryInfo struct {
// Battery state ( 1: on battery, discharging; 2: plugged in, less than 100%, 3: plugged in, at 100% )
State BatteryState `json:"state"`
Status BatteryStatus `json:"status"`
}
type BatteryState int
@ -683,6 +685,11 @@ type Point struct {
Y int `json:"y"` // upper left Y coordinate of selected element
}
type PointF struct {
X float64 `json:"x"`
Y float64 `json:"y"`
}
type Rect struct {
Point
Size
@ -708,6 +715,11 @@ func WithFrequency(frequency int) DataOption {
}
}
// current implemeted device: IOSDevice, AndroidDevice
type Device interface {
UUID() string
}
// WebDriver defines methods supported by WebDriver drivers.
type WebDriver interface {
// NewSession starts a new session and returns the SessionInfo.

View File

@ -2,7 +2,6 @@ package uixt
import (
"bytes"
"context"
"encoding/base64"
builtinJSON "encoding/json"
"fmt"
@ -14,7 +13,6 @@ import (
"net/url"
"regexp"
"strings"
"sync"
"time"
giDevice "github.com/electricbubble/gidevice"
@ -40,7 +38,7 @@ const (
)
const (
defaultPort = 8100
defaultWDAPort = 8100
defaultMjpegPort = 9100
)
@ -102,10 +100,6 @@ func InitWDAClient(device *IOSDevice) (*DriverExt, error) {
return driverExt, nil
}
type Device interface {
UUID() string
}
type IOSDeviceOption func(*IOSDevice)
func WithUDID(udid string) IOSDeviceOption {
@ -144,7 +138,7 @@ func NewIOSDevice(options ...IOSDeviceOption) (device *IOSDevice, err error) {
}
device = &IOSDevice{
Port: defaultPort,
Port: defaultWDAPort,
MjpegPort: defaultMjpegPort,
}
for _, option := range options {
@ -191,37 +185,33 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver
return nil, err
}
wd.sessionId = sessionInfo.SessionId
wd.client = http.DefaultClient
if wd.mjpegConn, err = net.Dial(
if wd.mjpegHTTPConn, err = net.Dial(
"tcp",
fmt.Sprintf("%s:%d", wd.urlPrefix.Hostname(),
dev.MjpegPort),
); err != nil {
return nil, err
}
wd.mjpegClient = convertToHTTPClient(wd.mjpegConn)
wd.mjpegClient = convertToHTTPClient(wd.mjpegHTTPConn)
return wd, nil
}
// NewUSBDriver creates new client via USB connected device, this will also start a new session.
func (dev *IOSDevice) NewUSBDriver(capabilities Capabilities) (driver WebDriver, err error) {
wd := &wdaDriver{
usbCli: &struct {
httpCli *http.Client
defaultConn, mjpegConn giDevice.InnerConn
sync.Mutex
}{},
}
if wd.usbCli.defaultConn, err = dev.NewConnect(dev.Port, 0); err != nil {
wd := new(wdaDriver)
if wd.defaultConn, err = dev.NewConnect(dev.Port, 0); err != nil {
return nil, fmt.Errorf("create connection: %w", err)
}
wd.usbCli.httpCli = convertToHTTPClient(wd.usbCli.defaultConn.RawConn())
wd.client = convertToHTTPClient(wd.defaultConn.RawConn())
if wd.usbCli.mjpegConn, err = dev.NewConnect(dev.MjpegPort, 0); err != nil {
if wd.mjpegUSBConn, err = dev.NewConnect(dev.MjpegPort, 0); err != nil {
return nil, fmt.Errorf("create connection MJPEG: %w", err)
}
wd.mjpegClient = convertToHTTPClient(wd.usbCli.mjpegConn.RawConn())
wd.mjpegClient = convertToHTTPClient(wd.mjpegUSBConn.RawConn())
if wd.urlPrefix, err = url.Parse("http://" + dev.UDID); err != nil {
return nil, err
@ -245,17 +235,6 @@ func (dev *IOSDevice) NewUSBDriver(capabilities Capabilities) (driver WebDriver,
return wd, err
}
func convertToHTTPClient(_conn net.Conn) *http.Client {
return &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return _conn, nil
},
},
Timeout: 0,
}
}
type wdaResponse struct {
Value string `json:"value"`
SessionID string `json:"sessionId"`
@ -376,7 +355,8 @@ func (r rawResponse) checkErr() (err error) {
Value struct {
Err string `json:"error"`
Message string `json:"message"`
Traceback string `json:"traceback"`
Traceback string `json:"traceback"` // wda
Stacktrace string `json:"stacktrace"` // uia
}
})
if err = json.Unmarshal(r, reply); err != nil {

View File

@ -5,57 +5,46 @@ import (
"encoding/base64"
builtinJSON "encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"path"
"strings"
"sync"
"time"
giDevice "github.com/electricbubble/gidevice"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
)
type wdaDriver struct {
urlPrefix *url.URL
sessionId string
Driver
usbCli *struct {
httpCli *http.Client
defaultConn, mjpegConn giDevice.InnerConn
sync.Mutex
}
// default port
defaultConn giDevice.InnerConn
// mjpeg port
mjpegUSBConn giDevice.InnerConn // via USB
mjpegHTTPConn net.Conn // via HTTP
mjpegClient *http.Client
mjpegConn net.Conn
}
func (wd *wdaDriver) GetMjpegHTTPClient() *http.Client {
func (wd *wdaDriver) GetMjpegClient() *http.Client {
return wd.mjpegClient
}
func (wd *wdaDriver) Close() error {
if wd.usbCli == nil {
if wd.defaultConn != nil {
wd.defaultConn.Close()
}
if wd.mjpegUSBConn != nil {
wd.mjpegUSBConn.Close()
}
if wd.mjpegClient != nil {
wd.mjpegClient.CloseIdleConnections()
return wd.mjpegConn.Close()
}
wd.usbCli.Lock()
defer wd.usbCli.Unlock()
if wd.usbCli.defaultConn != nil {
wd.usbCli.defaultConn.Close()
}
if wd.usbCli.mjpegConn != nil {
wd.usbCli.mjpegConn.Close()
}
return nil
return wd.mjpegHTTPConn.Close()
}
func (wd *wdaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionInfo, err error) {
@ -844,83 +833,3 @@ func (wd *wdaDriver) WaitWithTimeout(condition Condition, timeout time.Duration)
func (wd *wdaDriver) Wait(condition Condition) error {
return wd.WaitWithTimeoutAndInterval(condition, DefaultWaitTimeout, DefaultWaitInterval)
}
func (wd *wdaDriver) concatURL(u *url.URL, elem ...string) string {
var tmp *url.URL
if u == nil {
u = wd.urlPrefix
}
tmp, _ = url.Parse(u.String())
tmp.Path = path.Join(append([]string{u.Path}, elem...)...)
return tmp.String()
}
func (wd *wdaDriver) httpGET(pathElem ...string) (rawResp rawResponse, err error) {
return wd.httpRequest(http.MethodGet, wd.concatURL(nil, pathElem...), nil)
}
func (wd *wdaDriver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, 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(nil, pathElem...), bsJSON)
}
func (wd *wdaDriver) httpDELETE(pathElem ...string) (rawResp rawResponse, err error) {
return wd.httpRequest(http.MethodDelete, wd.concatURL(nil, pathElem...), nil)
}
func (wd *wdaDriver) httpRequest(method string, rawURL string, rawBody []byte) (rawResp rawResponse, err error) {
log.Debug().Str("method", method).Str("url", rawURL).Str("body", string(rawBody)).Msg("request WDA")
var req *http.Request
if req, err = http.NewRequest(method, rawURL, bytes.NewBuffer(rawBody)); err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Accept", "application/json")
var httpCli *http.Client
if wd.usbCli != nil {
wd.usbCli.Lock()
defer wd.usbCli.Unlock()
httpCli = wd.usbCli.httpCli
} else {
httpCli = http.DefaultClient
}
httpCli.Timeout = 0
start := time.Now()
var resp *http.Response
if resp, err = httpCli.Do(req); err != nil {
return nil, err
}
defer func() {
// https://github.com/etcd-io/etcd/blob/v3.3.25/pkg/httputil/httputil.go#L16-L22
_, _ = io.Copy(ioutil.Discard, resp.Body)
_ = resp.Body.Close()
}()
rawResp, err = ioutil.ReadAll(resp.Body)
logger := log.Debug().Int("statusCode", resp.StatusCode).Str("duration", time.Since(start).String())
if !strings.HasSuffix(rawURL, "screenshot") {
// avoid printing screenshot data
logger.Bytes("response", rawResp)
}
logger.Msg("get WDA response")
if err != nil {
return nil, err
}
if err = rawResp.checkErr(); err != nil {
if resp.StatusCode == http.StatusOK {
return rawResp, nil
}
return nil, err
}
return
}

View File

@ -351,8 +351,6 @@ func Test_remoteWD_Homescreen(t *testing.T) {
func Test_remoteWD_AppLaunch(t *testing.T) {
setup(t)
// SetDebug(true)
// bundleId = "com.hustlzp.xcz"
// bundleId = "com.github.stormbreaker.prod"
// bundleId = "com.360buy.jdmobile"

View File

@ -10,8 +10,6 @@ func TestDriverExt_TapWithNumber(t *testing.T) {
pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/flag7.png"
// SetDebug(true)
err = driverExt.TapWithNumber(pathSearch, 3)
checkErr(t, err)