diff --git a/driver.go b/driver.go index e1246af..86f56da 100644 --- a/driver.go +++ b/driver.go @@ -496,16 +496,21 @@ func (wd *remoteWD) AppAuthReset(resource ProtectedResource) (err error) { return } -func (wd *remoteWD) Tap(x, y int) error { - return wd.TapFloat(float64(x), float64(y)) +func (wd *remoteWD) Tap(x, y int, options ...DataOption) error { + return wd.TapFloat(float64(x), float64(y), options...) } -func (wd *remoteWD) TapFloat(x, y float64) (err error) { +func (wd *remoteWD) TapFloat(x, y float64, options ...DataOption) (err error) { // [[FBRoute POST:@"/wda/tap/:uuid"] respondWithTarget:self action:@selector(handleTap:)] data := map[string]interface{}{ "x": x, "y": y, } + // append options in post data for extra WDA configurations + // e.g. add identifier in tap event logs + for _, option := range options { + option(data) + } _, err = wd.executePost(data, "/session", wd.sessionId, "/wda/tap/0") return } @@ -542,11 +547,11 @@ func (wd *remoteWD) TouchAndHoldFloat(x, y float64, second ...float64) (err erro return } -func (wd *remoteWD) Drag(fromX, fromY, toX, toY int, pressForDuration ...float64) error { - return wd.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), pressForDuration...) +func (wd *remoteWD) Drag(fromX, fromY, toX, toY int, options ...DataOption) error { + return wd.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...) } -func (wd *remoteWD) DragFloat(fromX, fromY, toX, toY float64, pressForDuration ...float64) (err error) { +func (wd *remoteWD) DragFloat(fromX, fromY, toX, toY float64, options ...DataOption) (err error) { // [[FBRoute POST:@"/wda/dragfromtoforduration"] respondWithTarget:self action:@selector(handleDragCoordinate:)] data := map[string]interface{}{ "fromX": fromX, @@ -554,20 +559,28 @@ func (wd *remoteWD) DragFloat(fromX, fromY, toX, toY float64, pressForDuration . "toX": toX, "toY": toY, } - if len(pressForDuration) == 0 || pressForDuration[0] < 0 { - pressForDuration = []float64{1.0} + + // append options in post data for extra WDA configurations + // e.g. use WithPressDuration to set pressForDuration + for _, option := range options { + option(data) + } + + if _, ok := data["duration"]; !ok { + data["duration"] = 1.0 // default duration } - data["duration"] = pressForDuration[0] _, err = wd.executePost(data, "/session", wd.sessionId, "/wda/dragfromtoforduration") return } -func (wd *remoteWD) Swipe(fromX, fromY, toX, toY int) error { - return wd.SwipeFloat(float64(fromX), float64(fromY), float64(toX), float64(toY)) +func (wd *remoteWD) Swipe(fromX, fromY, toX, toY int, options ...DataOption) error { + options = append(options, WithPressDuration(0)) + return wd.SwipeFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...) } -func (wd *remoteWD) SwipeFloat(fromX, fromY, toX, toY float64) error { - return wd.DragFloat(fromX, fromY, toX, toY, 0) +func (wd *remoteWD) SwipeFloat(fromX, fromY, toX, toY float64, options ...DataOption) error { + options = append(options, WithPressDuration(0)) + return wd.DragFloat(fromX, fromY, toX, toY, options...) } func (wd *remoteWD) ForceTouch(x, y int, pressure float64, second ...float64) error { @@ -624,13 +637,19 @@ func (wd *remoteWD) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer return } -func (wd *remoteWD) SendKeys(text string, frequency ...int) (err error) { +func (wd *remoteWD) SendKeys(text string, options ...DataOption) (err error) { // [[FBRoute POST:@"/wda/keys"] respondWithTarget:self action:@selector(handleKeys:)] data := map[string]interface{}{"value": strings.Split(text, "")} - if len(frequency) == 0 || frequency[0] <= 0 { - frequency = []int{60} + + // append options in post data for extra WDA configurations + // e.g. use WithFrequency to set frequency of typing + for _, option := range options { + option(data) + } + + if _, ok := data["frequency"]; !ok { + data["frequency"] = 60 // default frequency } - data["frequency"] = frequency[0] _, err = wd.executePost(data, "/session", wd.sessionId, "/wda/keys") return } diff --git a/driver_test.go b/driver_test.go index 6f380dc..817d96e 100644 --- a/driver_test.go +++ b/driver_test.go @@ -454,7 +454,7 @@ func Test_remoteWD_TouchAndHold(t *testing.T) { func Test_remoteWD_Drag(t *testing.T) { setup(t) - // err := driver.Drag(200, 300, 200, 500, -1) + // err := driver.Drag(200, 300, 200, 500, WithPressDuration(0.5)) err := driver.Swipe(200, 300, 200, 500) if err != nil { t.Fatal(err) @@ -511,7 +511,7 @@ func Test_remoteWD_SendKeys(t *testing.T) { setup(t) err := driver.SendKeys("App Store") - // err := driver.SendKeys("App Store", 3) + // err := driver.SendKeys("App Store", WithFrequency(3)) if err != nil { t.Fatal(err) } diff --git a/element.go b/element.go index a17359c..a9573f2 100644 --- a/element.go +++ b/element.go @@ -9,9 +9,12 @@ import ( "strings" ) +// All elements returned by search endpoints have assigned element_id. +// Given element_id you can query properties like: +// enabled, rect, size, location, text, displayed, accessible, name type remoteWE struct { parent *remoteWD - id string + id string // element_id } func (we remoteWE) Click() (err error) { diff --git a/element_test.go b/element_test.go index 932ff9e..931c758 100644 --- a/element_test.go +++ b/element_test.go @@ -155,6 +155,9 @@ func Test_remoteWE_PickerWheelSelect(t *testing.T) { element := setupElement(t, BySelector{ClassName: ElementType{PickerWheel: true}}) err := element.PickerWheelSelect(PickerWheelOrderNext, 3) + if err != nil { + t.Fatal(err) + } err = element.PickerWheelSelect(PickerWheelOrderPrevious) if err != nil { t.Fatal(err) @@ -236,7 +239,13 @@ func Test_remoteWE_Rect(t *testing.T) { element := setupElement(t, BySelector{ClassName: ElementType{Switch: true}}) rect, err := element.Rect() + if err != nil { + t.Fatal(err) + } location, err := element.Location() + if err != nil { + t.Fatal(err) + } size, err := element.Size() if err != nil { t.Fatal(err) diff --git a/examples/keyboard/main.go b/examples/keyboard/main.go index 8016d95..485521d 100644 --- a/examples/keyboard/main.go +++ b/examples/keyboard/main.go @@ -1,8 +1,9 @@ package main import ( - "github.com/electricbubble/gwda" "log" + + "github.com/electricbubble/gwda" ) func main() { @@ -16,6 +17,12 @@ func main() { log.Fatalln(err) } + // send keys with specified frequency + err = driver.SendKeys("world", gwda.WithFrequency(30)) + if err != nil { + log.Fatalln(err) + } + // element, err := driver.ActiveElement() // if err != nil { // log.Fatalln(err) diff --git a/examples/touch/main.go b/examples/touch/main.go index 00f0e50..2164ae4 100644 --- a/examples/touch/main.go +++ b/examples/touch/main.go @@ -1,8 +1,9 @@ package main import ( - "github.com/electricbubble/gwda" "log" + + "github.com/electricbubble/gwda" ) func main() { @@ -18,6 +19,16 @@ func main() { log.Fatalln(err) } + // tap action with specified identifier + option := gwda.WithCustomOption("log", map[string]interface{}{ + "enable": true, + "data": "identifier-tap-A", + }) + err = driver.Tap(x, y, option) + if err != nil { + log.Fatalln(err) + } + err = driver.DoubleTap(x, y) if err != nil { log.Fatalln(err) @@ -35,11 +46,31 @@ func main() { log.Fatalln(err) } + // drag action with specified identifier + option = gwda.WithCustomOption("log", map[string]interface{}{ + "enable": true, + "data": "identifier-drag-B", + }) + err = driver.Drag(fromX, fromY, toX, toY, option) + if err != nil { + log.Fatalln(err) + } + err = driver.Swipe(fromX, fromY, toX, toY) if err != nil { log.Fatalln(err) } + // swipe action with specified identifier + option = gwda.WithCustomOption("log", map[string]interface{}{ + "enable": true, + "data": "identifier-swipe-C", + }) + err = driver.Swipe(fromX, fromY, toX, toY, option) + if err != nil { + log.Fatalln(err) + } + // 需要 3D Touch 硬件支持 // err = driver.ForceTouch(x, y, 0.8) // if err != nil { diff --git a/gwda.go b/gwda.go index db40589..c8631a5 100644 --- a/gwda.go +++ b/gwda.go @@ -68,7 +68,12 @@ func executeHTTP(method string, rawURL string, rawBody []byte, httpCli *http.Cli }() rawResp, err = ioutil.ReadAll(resp.Body) - debugLog(fmt.Sprintf("<-- %s %s %d %s\n%s\n", method, rawURL, resp.StatusCode, time.Since(start), rawResp)) + respBody := fmt.Sprintf("<-- %s %s %d %s", method, rawURL, resp.StatusCode, time.Since(start)) + if !strings.HasSuffix(rawURL, "screenshot") { + // avoid printing screenshot data + respBody += fmt.Sprintf("\n%s", rawResp) + } + debugLog(respBody) if err != nil { return nil, err } @@ -606,6 +611,7 @@ func elementIDFromValue(val map[string]string) string { return "" } +// performance ranking: class name > accessibility id > link text > predicate > class chain > xpath type BySelector struct { ClassName ElementType `json:"class name"` @@ -624,7 +630,7 @@ type BySelector struct { ClassChain string `json:"class chain"` - XPath string `json:"xpath"` + XPath string `json:"xpath"` // not recommended, it's slow because it is not supported by XCTest natively } func (wl BySelector) getUsingAndValue() (using, value string) { @@ -879,8 +885,8 @@ const ( ) type Point struct { - X int `json:"x"` - Y int `json:"y"` + X int `json:"x"` // upper left X coordinate of selected element + Y int `json:"y"` // upper left Y coordinate of selected element } type Rect struct { @@ -888,6 +894,26 @@ type Rect struct { Size } +type DataOption func(data map[string]interface{}) + +func WithCustomOption(key string, value interface{}) DataOption { + return func(data map[string]interface{}) { + data[key] = value + } +} + +func WithPressDuration(duraion float64) DataOption { + return func(data map[string]interface{}) { + data["duration"] = duraion + } +} + +func WithFrequency(frequency int) DataOption { + return func(data map[string]interface{}) { + data["frequency"] = frequency + } +} + // WebDriver defines methods supported by WebDriver drivers. type WebDriver interface { // NewSession starts a new session and returns the SessionInfo. @@ -970,8 +996,8 @@ type WebDriver interface { AppAuthReset(ProtectedResource) error // Tap Sends a tap event at the coordinate. - Tap(x, y int) error - TapFloat(x, y float64) error + Tap(x, y int, options ...DataOption) error + TapFloat(x, y float64, options ...DataOption) error // DoubleTap Sends a double tap event at the coordinate. DoubleTap(x, y int) error @@ -983,13 +1009,13 @@ type WebDriver interface { TouchAndHoldFloat(x, y float64, second ...float64) error // Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate. - // pressForDuration: The default value is 1 second. - Drag(fromX, fromY, toX, toY int, pressForDuration ...float64) error - DragFloat(fromX, fromY, toX, toY float64, pressForDuration ...float64) error + // WithPressDuration option can be used to set pressForDuration (default to 1 second). + Drag(fromX, fromY, toX, toY int, options ...DataOption) error + DragFloat(fromX, fromY, toX, toY float64, options ...DataOption) error // Swipe works like Drag, but `pressForDuration` value is 0 - Swipe(fromX, fromY, toX, toY int) error - SwipeFloat(fromX, fromY, toX, toY float64) error + Swipe(fromX, fromY, toX, toY int, options ...DataOption) error + SwipeFloat(fromX, fromY, toX, toY float64, options ...DataOption) error ForceTouch(x, y int, pressure float64, second ...float64) error ForceTouchFloat(x, y, pressure float64, second ...float64) error @@ -1006,8 +1032,8 @@ type WebDriver interface { // SendKeys Types a string into active element. There must be element with keyboard focus, // otherwise an error is raised. - // frequency: Frequency of typing (letters per sec). The default value is 60 - SendKeys(text string, frequency ...int) error + // WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60 + SendKeys(text string, options ...DataOption) error // KeyboardDismiss Tries to dismiss the on-screen keyboard KeyboardDismiss(keyNames ...string) error