mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-11 18:11:21 +08:00
feat: implement ios ui swipe
This commit is contained in:
1
go.mod
1
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
|
||||
|
||||
9
go.sum
9
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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user