From 3fd3dab9aef39f5d2502be5b37661e8ae4c114b9 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Wed, 27 Jul 2022 10:22:04 +0800 Subject: [PATCH] feat: implement ios ui swipe --- go.mod | 1 + go.sum | 9 +++ hrp/runner.go | 2 + hrp/step_ios_ui.go | 170 +++++++++++++++++++++++++++++++++++++++++++- hrp/step_ui_test.go | 4 +- 5 files changed, 183 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c175a55c..3cfe23b2 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/andybalholm/brotli v1.0.4 github.com/denisbrodbeck/machineid v1.0.1 + github.com/electricbubble/gwda v0.3.0 github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.13.0 github.com/go-errors/errors v1.0.1 diff --git a/go.sum b/go.sum index 600942d3..1e0bd26b 100644 --- a/go.sum +++ b/go.sum @@ -99,6 +99,10 @@ github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6ps github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/electricbubble/gidevice v0.0.4 h1:PbOt4AngNQTtO5j0vCZ3Xcj9mByDtZmjBYLTh8PJ9kc= +github.com/electricbubble/gidevice v0.0.4/go.mod h1:hWRHIPf4uyiEB56hnVHVvu6MoVg7RlJY8ZV2FVgLKZA= +github.com/electricbubble/gwda v0.3.0 h1:uQMZxmp5D51iMsXrWfi21MlftrkPmOeLDE+gtw06fg4= +github.com/electricbubble/gwda v0.3.0/go.mod h1:co3ynSIVXEyI3aKdzfjqkFDFloFcxhc+e27U0ajyZsM= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -270,6 +274,7 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jinzhu/copier v0.3.2 h1:QdBOCbaouLDYaIPFfi1bKv5F5tPpeTwXe4sD0jqtz5w= @@ -419,6 +424,8 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -900,6 +907,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +howett.net/plist v0.0.0-20201203080718-1454fab16a06 h1:QDxUo/w2COstK1wIBYpzQlHX/NqaQTcf9jyz347nI58= +howett.net/plist v0.0.0-20201203080718-1454fab16a06/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/hrp/runner.go b/hrp/runner.go index e6662613..454058e8 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -48,6 +48,7 @@ func NewRunner(t *testing.T) *HRPRunner { Transport: &http2.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, + Jar: jar, // insert response cookies into request Timeout: 120 * time.Second, }, // use default handshake timeout (no timeout limit) here, enable timeout at step level @@ -69,6 +70,7 @@ type HRPRunner struct { httpClient *http.Client http2Client *http.Client wsDialer *websocket.Dialer + wdaClients map[string]*wdaClient // wda client used for iOS UI automation, key is udid } // SetClientTransport configures transport of http client for high concurrency load testing diff --git a/hrp/step_ios_ui.go b/hrp/step_ios_ui.go index cf2fb8c4..06b19ca8 100644 --- a/hrp/step_ios_ui.go +++ b/hrp/step_ios_ui.go @@ -1,5 +1,13 @@ package hrp +import ( + "fmt" + + "github.com/electricbubble/gwda" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" +) + type IOSAction struct { MobileAction UDID string `json:"udid,omitempty" yaml:"udid,omitempty"` @@ -159,12 +167,172 @@ func (s *StepIOSValidation) Run(r *SessionRunner) (*StepResult, error) { return runStepIOS(r, s.step) } +func (r *HRPRunner) InitWDAClient(udid string) (*wdaClient, error) { + // avoid duplicate init + if udid == "" && len(r.wdaClients) == 1 { + for _, v := range r.wdaClients { + return v, nil + } + } + + targetDevice, err := getAttachedIOSDevice(udid) + if err != nil { + return nil, err + } + + // avoid duplicate init + if client, ok := r.wdaClients[targetDevice.SerialNumber()]; ok { + return client, nil + } + + // init WDA driver + driver, err := gwda.NewUSBDriver(nil, *targetDevice) + if err != nil { + return nil, errors.Wrap(err, "failed to init WDA driver") + } + + // get device window size + windowSize, err := driver.WindowSize() + if err != nil { + return nil, errors.Wrap(err, "failed to get windows size") + } + + // cache wda client + r.wdaClients = make(map[string]*wdaClient) + client := &wdaClient{ + Driver: driver, + WindowSize: windowSize, + } + r.wdaClients[targetDevice.SerialNumber()] = client + + return client, nil +} + +func getAttachedIOSDevice(udid string) (*gwda.Device, error) { + // get all attached deivces + devices, err := gwda.DeviceList() + if err != nil { + return nil, errors.Wrap(err, "failed to get attached ios devices list") + } + if len(devices) == 0 { + return nil, errors.New("no ios devices attached") + } + + if udid == "" { + return &devices[0], nil + } + + // find device by udid + for _, device := range devices { + if device.SerialNumber() == udid { + return &device, nil + } + } + + return nil, fmt.Errorf("device %s is not attached", udid) +} + func runStepIOS(r *SessionRunner, step *TStep) (stepResult *StepResult, err error) { stepResult = &StepResult{ Name: step.Name, - StepType: stepTypeAndroid, + StepType: stepTypeIOS, Success: false, ContentSize: 0, } + + // init wdaClient driver + wdaClient, err := r.hrpRunner.InitWDAClient(step.IOS.UDID) + if err != nil { + return + } + + // prepare actions + var actions []MobileAction + if step.IOS.Actions == nil { + actions = []MobileAction{ + { + Method: step.IOS.Method, + Params: step.IOS.Params, + }, + } + } else { + actions = step.IOS.Actions + } + + // run actions + for _, action := range actions { + if err := wdaClient.doAction(action); err != nil { + return stepResult, err + } + } + + // do validation + // step.Validators + + stepResult.Success = true return stepResult, nil } + +var errActionNotImplemented = errors.New("UI action not implemented") + +type wdaClient struct { + Driver gwda.WebDriver + WindowSize gwda.Size +} + +func (w *wdaClient) doAction(action MobileAction) error { + log.Info().Str("method", string(action.Method)).Interface("params", action.Params).Msg("start iOS UI action") + + switch action.Method { + case appInstall: + // TODO + return errActionNotImplemented + case appStart: + // TODO + return errActionNotImplemented + case uiClick: + // TODO + return errActionNotImplemented + case uiDoubleClick: + // TODO + return errActionNotImplemented + case uiLongClick: + // TODO + return errActionNotImplemented + case uiSwipe: + width := w.WindowSize.Width + height := w.WindowSize.Height + + var fromX, fromY, toX, toY int + if direction, ok := action.Params.(string); ok { + switch direction { + case "up": + fromX, fromY, toX, toY = width/2, height*1/4, width/2, height*3/4 + case "down": + fromX, fromY, toX, toY = width/2, height*3/4, width/2, height*1/4 + case "left": + fromX, fromY, toX, toY = width*3/4, height/2, width*1/4, height/2 + case "right": + fromX, fromY, toX, toY = width*1/4, height/2, width*3/4, height/2 + } + } else if params, ok := action.Params.([]int); ok { + if len(params) != 4 { + return fmt.Errorf("invalid swipe params: %v", params) + } + fromX, fromY, toX, toY = params[0], params[1], params[2], params[3] + } + return w.Driver.Swipe(fromX, fromY, toX, toY) + case uiInput: + // TODO + return errActionNotImplemented + case appClick: + // TODO + return errActionNotImplemented + } + return nil +} + +func (w *wdaClient) doValidation() error { + // TODO + return errActionNotImplemented +} diff --git a/hrp/step_ui_test.go b/hrp/step_ui_test.go index 02e2044e..4e8ec019 100644 --- a/hrp/step_ui_test.go +++ b/hrp/step_ui_test.go @@ -32,12 +32,12 @@ func TestIOSAction(t *testing.T) { Config: NewConfig("ios ui action"), TestSteps: []IStep{ NewStep("launch douyin"). - IOS().UDID("xxx").Click("抖音"). + IOS().Click("抖音"). Validate(). AssertTextExists("首页", "首页 tab 不存在"). AssertTextExists("消息", "消息 tab 不存在"), NewStep("swipe up and down"). - IOS().UDID("xxx").SwipeUp().SwipeUp().SwipeDown(), + IOS().SwipeUp().SwipeUp().SwipeDown(), }, } tCase := testCase.ToTCase()