From 5effb3897c3ce1832826af0b86e93455c185fa1c Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 28 Aug 2022 14:55:34 +0800 Subject: [PATCH] refactor: relocate code --- go.mod | 2 +- go.sum | 4 +- hrp/internal/uixt/ext.go | 21 ++++- hrp/internal/uixt/init.go | 72 ++++++++++++++++ hrp/runner.go | 2 +- hrp/step_ios_ui.go | 170 ++++++++------------------------------ hrp/step_ios_ui_test.go | 5 +- 7 files changed, 133 insertions(+), 143 deletions(-) create mode 100644 hrp/internal/uixt/init.go diff --git a/go.mod b/go.mod index 592963da..bd615864 100644 --- a/go.mod +++ b/go.mod @@ -40,4 +40,4 @@ require ( ) // replace github.com/httprunner/funplugin => ../funplugin -replace github.com/electricbubble/gwda => github.com/debugtalk/gwda v0.0.0-20220824022606-02ad6ca51de7 +replace github.com/electricbubble/gwda => github.com/debugtalk/gwda v0.0.0-20220828065105-59203789a7e7 diff --git a/go.sum b/go.sum index 0d59b3cb..d0059fe4 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/debugtalk/gwda v0.0.0-20220824022606-02ad6ca51de7 h1:DjPOXlkeCsxtFzieys2RjYEn6OCoAPQNiLmG2eeSVgw= -github.com/debugtalk/gwda v0.0.0-20220824022606-02ad6ca51de7/go.mod h1:kyzKpP1/iKJ2i4AxmT8sEmSvB8Pz5NcDVwc/m/Jsg6k= +github.com/debugtalk/gwda v0.0.0-20220828065105-59203789a7e7 h1:pAvqLivdxSqCttO6lbEzg/zjxJO6oOQayfPKqBVD3t0= +github.com/debugtalk/gwda v0.0.0-20220828065105-59203789a7e7/go.mod h1:kyzKpP1/iKJ2i4AxmT8sEmSvB8Pz5NcDVwc/m/Jsg6k= github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= diff --git a/hrp/internal/uixt/ext.go b/hrp/internal/uixt/ext.go index 4b24ccb8..cb8c57a3 100644 --- a/hrp/internal/uixt/ext.go +++ b/hrp/internal/uixt/ext.go @@ -332,4 +332,23 @@ func (dExt *DriverExt) PerformActions(actions *gwda.W3CActions) error { return dExt.PerformW3CActions(actions) } -// IsExist +func (dExt *DriverExt) IsNameExist(name string) bool { + selector := gwda.BySelector{ + LinkText: gwda.NewElementAttribute().WithName(name), + } + _, err := dExt.FindElement(selector) + return err == nil +} + +func (dExt *DriverExt) IsLabelExist(label string) bool { + selector := gwda.BySelector{ + LinkText: gwda.NewElementAttribute().WithLabel(label), + } + _, err := dExt.FindElement(selector) + return err == nil +} + +func (dExt *DriverExt) IsOCRExist(text string) bool { + _, _, _, _, err := dExt.FindTextByOCR(text) + return err == nil +} diff --git a/hrp/internal/uixt/init.go b/hrp/internal/uixt/init.go new file mode 100644 index 00000000..64c0c011 --- /dev/null +++ b/hrp/internal/uixt/init.go @@ -0,0 +1,72 @@ +package uixt + +import ( + "github.com/electricbubble/gwda" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" +) + +const ( + // Changes the value of maximum depth for traversing elements source tree. + // It may help to prevent out of memory or timeout errors while getting the elements source tree, + // but it might restrict the depth of source tree. + // A part of elements source tree might be lost if the value was too small. Defaults to 50 + snapshotMaxDepth = 10 + // Allows to customize accept/dismiss alert button selector. + // It helps you to handle an arbitrary element as accept button in accept alert command. + // The selector should be a valid class chain expression, where the search root is the alert element itself. + // The default button location algorithm is used if the provided selector is wrong or does not match any element. + // e.g. **/XCUIElementTypeButton[`label CONTAINS[c] ‘accept’`] + acceptAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'允许','好','仅在使用应用期间','稍后再说'}`]" + dismissAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'不允许','暂不'}`]" +) + +func InitWDAClient(udid string, port, mjpeg_port int) (*DriverExt, error) { + // init wda device + var options []gwda.DeviceOption + if udid != "" { + options = append(options, gwda.WithSerialNumber(udid)) + } + if port != 0 { + options = append(options, gwda.WithPort(port)) + } + if mjpeg_port != 0 { + options = append(options, gwda.WithMjpegPort(mjpeg_port)) + } + targetDevice, err := gwda.NewDevice(options...) + if err != nil { + return nil, err + } + + // switch to iOS springboard before init WDA session + // aviod getting stuck when some super app is activate such as douyin or wexin + log.Info().Msg("switch to iOS springboard") + bundleID := "com.apple.springboard" + _, err = targetDevice.GIDevice().AppLaunch(bundleID) + if err != nil { + return nil, errors.Wrap(err, "launch springboard failed") + } + + // init WDA driver + gwda.SetDebug(true) + capabilities := gwda.NewCapabilities() + capabilities.WithDefaultAlertAction(gwda.AlertActionAccept) + driver, err := gwda.NewUSBDriver(capabilities, *targetDevice) + if err != nil { + return nil, errors.Wrap(err, "failed to init WDA driver") + } + driverExt, err := Extend(driver, 0.95) + if err != nil { + return nil, errors.Wrap(err, "failed to extend gwda.WebDriver") + } + settings, err := driverExt.SetAppiumSettings(map[string]interface{}{ + "snapshotMaxDepth": snapshotMaxDepth, + "acceptAlertButtonSelector": acceptAlertButtonSelector, + }) + if err != nil { + return nil, errors.Wrap(err, "failed to set appium WDA settings") + } + log.Info().Interface("appiumWDASettings", settings).Msg("set appium WDA settings") + + return driverExt, nil +} diff --git a/hrp/runner.go b/hrp/runner.go index 0a7002f9..690fc232 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -71,7 +71,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 + wdaClients map[string]*uiDriver // 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 beebd782..823968cd 100644 --- a/hrp/step_ios_ui.go +++ b/hrp/step_ios_ui.go @@ -5,28 +5,12 @@ import ( "strings" "time" - "github.com/electricbubble/gwda" "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v4/hrp/internal/uixt" ) -const ( - // Changes the value of maximum depth for traversing elements source tree. - // It may help to prevent out of memory or timeout errors while getting the elements source tree, - // but it might restrict the depth of source tree. - // A part of elements source tree might be lost if the value was too small. Defaults to 50 - snapshotMaxDepth = 10 - // Allows to customize accept/dismiss alert button selector. - // It helps you to handle an arbitrary element as accept button in accept alert command. - // The selector should be a valid class chain expression, where the search root is the alert element itself. - // The default button location algorithm is used if the provided selector is wrong or does not match any element. - // e.g. **/XCUIElementTypeButton[`label CONTAINS[c] ‘accept’`] - acceptAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'允许','好','仅在使用应用期间','稍后再说'}`]" - dismissAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'不允许','暂不'}`]" -) - type IOSConfig struct { WDADevice } @@ -364,23 +348,7 @@ func (s *StepIOSValidation) Run(r *SessionRunner) (*StepResult, error) { return runStepIOS(r, s.step) } -func (r *HRPRunner) InitWDAClient(device WDADevice) (client *wdaClient, err error) { - defer func() { - if err != nil { - return - } - // check if WDA is healthy - ok, e := client.DriverExt.IsWdaHealthy() - if err != nil { - err = errors.Wrap(e, "check WDA health failed") - return - } - if !ok { - err = errors.New("WDA is not healthy") - return - } - }() - +func (r *HRPRunner) InitWDAClient(device WDADevice) (client *uiDriver, err error) { // avoid duplicate init if device.UDID == "" && len(r.wdaClients) == 1 { for _, v := range r.wdaClients { @@ -388,71 +356,26 @@ func (r *HRPRunner) InitWDAClient(device WDADevice) (client *wdaClient, err erro } } - // init wda device - var options []gwda.DeviceOption + // avoid duplicate init if device.UDID != "" { - options = append(options, gwda.WithSerialNumber(device.UDID)) + if client, ok := r.wdaClients[device.UDID]; ok { + return client, nil + } } - if device.Port != 0 { - options = append(options, gwda.WithPort(device.Port)) - } - if device.MjpegPort != 0 { - options = append(options, gwda.WithMjpegPort(device.MjpegPort)) - } - targetDevice, err := gwda.NewDevice(options...) + + driverExt, err := uixt.InitWDAClient(device.UDID, device.Port, device.MjpegPort) if err != nil { return nil, err } - - // avoid duplicate init - if client, ok := r.wdaClients[targetDevice.SerialNumber()]; ok { - return client, nil - } - - // switch to iOS springboard before init WDA session - // aviod getting stuck when some super app is activate such as douyin or wexin - log.Info().Msg("switch to iOS springboard") - bundleID := "com.apple.springboard" - _, err = targetDevice.GIDevice().AppLaunch(bundleID) - if err != nil { - return nil, errors.Wrap(err, "launch springboard failed") - } - - // init WDA driver - gwda.SetDebug(true) - capabilities := gwda.NewCapabilities() - capabilities.WithDefaultAlertAction(gwda.AlertActionAccept) - driver, err := gwda.NewUSBDriver(capabilities, *targetDevice) - if err != nil { - return nil, errors.Wrap(err, "failed to init WDA driver") - } - driverExt, err := uixt.Extend(driver, 0.95) - if err != nil { - return nil, errors.Wrap(err, "failed to extend gwda.WebDriver") - } - settings, err := driverExt.SetAppiumSettings(map[string]interface{}{ - "snapshotMaxDepth": snapshotMaxDepth, - "acceptAlertButtonSelector": acceptAlertButtonSelector, - }) - if err != nil { - return nil, errors.Wrap(err, "failed to set appium WDA settings") - } - log.Info().Interface("appiumWDASettings", settings).Msg("set appium WDA settings") - - // get device window size - windowSize, err := driverExt.WindowSize() - if err != nil { - return nil, errors.Wrap(err, "failed to get windows size") + client = &uiDriver{ + DriverExt: *driverExt, } // cache wda client - r.wdaClients = make(map[string]*wdaClient) - client = &wdaClient{ - Device: targetDevice, - DriverExt: driverExt, - WindowSize: windowSize, + if r.wdaClients == nil { + r.wdaClients = make(map[string]*uiDriver) } - r.wdaClients[targetDevice.SerialNumber()] = client + r.wdaClients[device.UDID] = client return client, nil } @@ -512,13 +435,11 @@ func runStepIOS(s *SessionRunner, step *TStep) (stepResult *StepResult, err erro var errActionNotImplemented = errors.New("UI action not implemented") -type wdaClient struct { - Device *gwda.Device - DriverExt *uixt.DriverExt - WindowSize gwda.Size +type uiDriver struct { + uixt.DriverExt } -func (w *wdaClient) doAction(action MobileAction) error { +func (ud *uiDriver) doAction(action MobileAction) error { log.Info().Str("method", string(action.Method)).Interface("params", action.Params).Msg("start iOS UI action") switch action.Method { @@ -527,17 +448,17 @@ func (w *wdaClient) doAction(action MobileAction) error { return errActionNotImplemented case appLaunch: if bundleId, ok := action.Params.(string); ok { - return w.DriverExt.AppLaunch(bundleId) + return ud.AppLaunch(bundleId) } return fmt.Errorf("app_launch params should be bundleId(string), got %v", action.Params) case appLaunchUnattached: if bundleId, ok := action.Params.(string); ok { - return w.DriverExt.AppLaunchUnattached(bundleId) + return ud.AppLaunchUnattached(bundleId) } return fmt.Errorf("app_launch_unattached params should be bundleId(string), got %v", action.Params) case appTerminate: if bundleId, ok := action.Params.(string); ok { - success, err := w.DriverExt.AppTerminate(bundleId) + success, err := ud.AppTerminate(bundleId) if err != nil { return errors.Wrap(err, "failed to terminate app") } @@ -548,19 +469,19 @@ func (w *wdaClient) doAction(action MobileAction) error { } return fmt.Errorf("app_terminate params should be bundleId(string), got %v", action.Params) case uiHome: - return w.DriverExt.Homescreen() + return ud.Homescreen() case uiTapXY: if location, ok := action.Params.([]float64); ok { // relative x,y of window size: [0.5, 0.5] if len(location) != 2 { return fmt.Errorf("invalid tap location params: %v", location) } - return w.DriverExt.TapXY(location[0], location[1]) + return ud.TapXY(location[0], location[1]) } return fmt.Errorf("invalid %s params: %v", uiTapXY, action.Params) case uiTap: if param, ok := action.Params.(string); ok { - return w.DriverExt.Tap(param) + return ud.Tap(param) } return fmt.Errorf("invalid %s params: %v", uiTap, action.Params) case uiDoubleTapXY: @@ -569,17 +490,17 @@ func (w *wdaClient) doAction(action MobileAction) error { if len(location) != 2 { return fmt.Errorf("invalid tap location params: %v", location) } - return w.DriverExt.DoubleTapXY(location[0], location[1]) + return ud.DoubleTapXY(location[0], location[1]) } return fmt.Errorf("invalid %s params: %v", uiDoubleTapXY, action.Params) case uiDoubleTap: if param, ok := action.Params.(string); ok { - return w.DriverExt.DoubleTap(param) + return ud.DoubleTap(param) } return fmt.Errorf("invalid %s params: %v", uiDoubleTap, action.Params) case uiSwipe: if param, ok := action.Params.(string); ok { - return w.DriverExt.SwipeTo(param) + return ud.SwipeTo(param) } return fmt.Errorf("invalid %s params: %v", uiSwipe, action.Params) case uiInput: @@ -587,7 +508,7 @@ func (w *wdaClient) doAction(action MobileAction) error { // append \n to send text with enter // send \b\b\b to delete 3 chars param := fmt.Sprintf("%v", action.Params) - return w.DriverExt.SendKeys(param) + return ud.SendKeys(param) case ctlSleep: if param, ok := action.Params.(int); ok { time.Sleep(time.Duration(param) * time.Second) @@ -600,18 +521,18 @@ func (w *wdaClient) doAction(action MobileAction) error { var screenshotPath string var err error if param, ok := action.Params.(string); ok { - screenshotPath, err = w.DriverExt.ScreenShot(fmt.Sprintf("screenshot_%s", param)) + screenshotPath, err = ud.ScreenShot(fmt.Sprintf("screenshot_%s", param)) } else { - screenshotPath, err = w.DriverExt.ScreenShot(fmt.Sprintf("screenshot_%d", time.Now().Unix())) + screenshotPath, err = ud.ScreenShot(fmt.Sprintf("screenshot_%d", time.Now().Unix())) } log.Info().Str("path", screenshotPath).Msg("take screenshot") return err case ctlStartCamera: // start camera, alias for app_launch com.apple.camera - return w.DriverExt.AppLaunch("com.apple.camera") + return ud.AppLaunch("com.apple.camera") case ctlStopCamera: // stop camera, alias for app_terminate com.apple.camera - success, err := w.DriverExt.AppTerminate("com.apple.camera") + success, err := ud.AppTerminate("com.apple.camera") if err != nil { return errors.Wrap(err, "failed to terminate camera") } @@ -623,7 +544,7 @@ func (w *wdaClient) doAction(action MobileAction) error { return nil } -func (w *wdaClient) doValidation(iValidators []interface{}) (validateResults []*ValidationResult, err error) { +func (ud *uiDriver) doValidation(iValidators []interface{}) (validateResults []*ValidationResult, err error) { for _, iValidator := range iValidators { validator, ok := iValidator.(Validator) if !ok { @@ -657,11 +578,11 @@ func (w *wdaClient) doValidation(iValidators []interface{}) (validateResults []* var result bool switch validator.Check { case uiSelectorName: - result = w.assertName(expected, exists) + result = (ud.IsNameExist(expected) == exists) case uiSelectorLabel: - result = w.assertLabel(expected, exists) + result = (ud.IsLabelExist(expected) == exists) case uiSelectorOCR: - result = w.assertOCR(expected, exists) + result = (ud.IsOCRExist(expected) == exists) } if result { @@ -678,29 +599,8 @@ func (w *wdaClient) doValidation(iValidators []interface{}) (validateResults []* Str("msg", validator.Message). Msg("validate UI failed") validateResults = append(validateResults, validataResult) - err = errors.New("step validation failed") + return validateResults, errors.New("step validation failed") } } - return -} - -func (w *wdaClient) assertName(name string, exists bool) bool { - selector := gwda.BySelector{ - LinkText: gwda.NewElementAttribute().WithName(name), - } - _, err := w.DriverExt.FindElement(selector) - return exists == (err == nil) -} - -func (w *wdaClient) assertLabel(label string, exists bool) bool { - selector := gwda.BySelector{ - LinkText: gwda.NewElementAttribute().WithLabel(label), - } - _, err := w.DriverExt.FindElement(selector) - return exists == (err == nil) -} - -func (w *wdaClient) assertOCR(text string, exists bool) bool { - _, _, _, _, err := w.DriverExt.FindTextByOCR(text) - return exists == (err == nil) + return validateResults, nil } diff --git a/hrp/step_ios_ui_test.go b/hrp/step_ios_ui_test.go index 6711fed6..c9e8732c 100644 --- a/hrp/step_ios_ui_test.go +++ b/hrp/step_ios_ui_test.go @@ -13,9 +13,8 @@ func TestIOSSettingsAction(t *testing.T) { IOS().Home().Tap("设置"). Validate(). AssertNameExists("飞行模式"). - AssertLabelExists("飞行模式"). - AssertOCRExists("飞行模式"). - AssertLabelNotExists("飞行模式2"), + AssertLabelExists("蓝牙"). + AssertOCRExists("个人热点"), NewStep("swipe up and down"). IOS().SwipeUp().SwipeUp().SwipeDown(), },