diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index e2a30d93..e86206c6 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -310,6 +310,9 @@ func ConvertPoints(data string) (eps []ExportPoint) { for _, line := range lines { if strings.Contains(line, "ext") { idx := strings.Index(line, "{") + if idx == -1 { + continue + } line = line[idx:] p := ExportPoint{} err := json.Unmarshal([]byte(line), &p) diff --git a/hrp/pkg/uixt/android_driver.go b/hrp/pkg/uixt/android_driver.go index 255e9757..5ebf69dd 100644 --- a/hrp/pkg/uixt/android_driver.go +++ b/hrp/pkg/uixt/android_driver.go @@ -18,6 +18,17 @@ import ( "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" ) +// See https://developer.android.com/reference/android/view/KeyEvent +const ( + KEYCODE_BACK string = "4" + KEYCODE_CAMERA string = "27" + KEYCODE_ALT_LEFT string = "57" + KEYCODE_ALT_RIGHT string = "58" + KEYCODE_MENU string = "82" + KEYCODE_BREAK string = "121" + KEYCODE_ALL_APPS string = "284" +) + var errDriverNotImplemented = errors.New("driver method not implemented") type uiaDriver struct { @@ -233,9 +244,12 @@ func (ud *uiaDriver) Scale() (scale float64, err error) { } // PressBack simulates a short press on the BACK button. -func (ud *uiaDriver) PressBack() (err error) { +func (ud *uiaDriver) PressBack(options ...DataOption) (err error) { // register(postHandler, new PressBack("/wd/hub/session/:sessionId/back")) _, err = ud.httpPOST(nil, "/session", ud.sessionId, "back") + if err != nil { + _, err = ud.adbDevice.RunShellCommand("input", "keyevent", KEYCODE_BACK) + } return } @@ -257,7 +271,7 @@ func (ud *uiaDriver) StartCamera() (err error) { return err } time.Sleep(5 * time.Second) - if _, err = ud.adbDevice.RunShellCommand("input", "keyevent", "27"); err != nil { + if _, err = ud.adbDevice.RunShellCommand("input", "keyevent", KEYCODE_CAMERA); err != nil { return err } return @@ -266,7 +280,7 @@ func (ud *uiaDriver) StartCamera() (err error) { return err } time.Sleep(5 * time.Second) - if _, err = ud.adbDevice.RunShellCommand("input", "keyevent", "27"); err != nil { + if _, err = ud.adbDevice.RunShellCommand("input", "keyevent", KEYCODE_CAMERA); err != nil { return err } return @@ -578,7 +592,6 @@ func (ud *uiaDriver) _swipe(startX, startY, endX, endY interface{}, options ...D // per step. So for a 100 steps, the swipe will take about 1/2 second to complete. // `steps` is the number of move steps sent to the system func (ud *uiaDriver) Swipe(fromX, fromY, toX, toY int, options ...DataOption) error { - options = append(options, WithDataSteps(12)) return ud.SwipeFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...) } diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index 82e11553..531f95b8 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -53,6 +53,7 @@ const ( ACTION_DoubleTap MobileMethod = "double_tap" ACTION_Swipe MobileMethod = "swipe" ACTION_Input MobileMethod = "input" + ACTION_Back MobileMethod = "back" // custom actions ACTION_SwipeToTapApp MobileMethod = "swipe_to_tap_app" // swipe left & right to find app and tap @@ -67,6 +68,8 @@ type MobileAction struct { Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` // used to identify the action in log MaxRetryTimes int `json:"max_retry_times,omitempty" yaml:"max_retry_times,omitempty"` // max retry times WaitTime float64 `json:"wait_time,omitempty" yaml:"wait_time,omitempty"` // wait time between swipe and ocr, unit: second + Duration float64 `json:"duration,omitempty" yaml:"duration,omitempty"` // used to set duration of ios swipe action + Steps int `json:"steps,omitempty" yaml:"steps,omitempty"` // used to set steps of android swipe action Direction interface{} `json:"direction,omitempty" yaml:"direction,omitempty"` // used by swipe to tap text or app Scope []float64 `json:"scope,omitempty" yaml:"scope,omitempty"` // used by ocr to get text position in the scope Offset []int `json:"offset,omitempty" yaml:"offset,omitempty"` // used to tap offset of point @@ -98,6 +101,18 @@ func WithWaitTime(sec float64) ActionOption { } } +func WithDuration(duration float64) ActionOption { + return func(o *MobileAction) { + o.Duration = duration + } +} + +func WithSteps(steps int) ActionOption { + return func(o *MobileAction) { + o.Steps = steps + } +} + // WithDirection inputs direction (up, down, left, right) func WithDirection(direction string) ActionOption { return func(o *MobileAction) { @@ -584,6 +599,11 @@ func (dExt *DriverExt) DoAction(action MobileAction) error { return fmt.Errorf("invalid %s params: %v", ACTION_DoubleTap, action.Params) case ACTION_Swipe: identifierOption := WithDataIdentifier(action.Identifier) + durationOption := WithDataPressDuration(action.Duration) + if action.Steps == 0 { + action.Steps = 10 + } + stepsOption := WithDataSteps(action.Steps) if positions, ok := action.Params.([]interface{}); ok { // relative fromX, fromY, toX, toY of window size: [0.5, 0.9, 0.5, 0.1] if len(positions) != 4 { @@ -593,10 +613,10 @@ func (dExt *DriverExt) DoAction(action MobileAction) error { fromY, _ := positions[1].(float64) toX, _ := positions[2].(float64) toY, _ := positions[3].(float64) - return dExt.SwipeRelative(fromX, fromY, toX, toY, identifierOption) + return dExt.SwipeRelative(fromX, fromY, toX, toY, identifierOption, durationOption, stepsOption) } if direction, ok := action.Params.(string); ok { - return dExt.SwipeTo(direction, identifierOption) + return dExt.SwipeTo(direction, identifierOption, durationOption, stepsOption) } return fmt.Errorf("invalid %s params: %v", ACTION_Swipe, action.Params) case ACTION_Input: @@ -618,6 +638,8 @@ func (dExt *DriverExt) DoAction(action MobileAction) error { options = append(options, WithDataIdentifier(action.Identifier)) } return dExt.Driver.Input(param, options...) + case ACTION_Back: + return dExt.Driver.PressBack() case CtlSleep: if param, ok := action.Params.(json.Number); ok { seconds, _ := param.Float64() diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index c31612c6..77ae84cb 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -891,7 +891,7 @@ func NewData(data map[string]interface{}, options ...DataOption) *DataOptions { } if _, ok := dataOptions.Data["duration"]; !ok { - dataOptions.Data["duration"] = 1.0 // default duration + dataOptions.Data["duration"] = 0 // default duration } if _, ok := dataOptions.Data["frequency"]; !ok { @@ -1046,6 +1046,9 @@ type WebDriver interface { // PressButton Presses the corresponding hardware button on the device PressButton(devBtn DeviceButton) error + // PressBack Presses the back button + PressBack(options ...DataOption) error + // IOHIDEvent Emulated triggering of the given low-level IOHID device event. // duration: The event duration in float seconds (XCTest uses 0.005 for a single press event) IOHIDEvent(pageID EventPageID, usageID EventUsageID, duration ...float64) error diff --git a/hrp/pkg/uixt/ios_driver.go b/hrp/pkg/uixt/ios_driver.go index 4796d577..a14c08c8 100644 --- a/hrp/pkg/uixt/ios_driver.go +++ b/hrp/pkg/uixt/ios_driver.go @@ -440,12 +440,10 @@ func (wd *wdaDriver) DragFloat(fromX, fromY, toX, toY float64, options ...DataOp } func (wd *wdaDriver) Swipe(fromX, fromY, toX, toY int, options ...DataOption) error { - options = append(options, WithDataPressDuration(0)) return wd.SwipeFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...) } func (wd *wdaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...DataOption) error { - options = append(options, WithDataPressDuration(0)) return wd.DragFloat(fromX, fromY, toX, toY, options...) } @@ -528,6 +526,27 @@ func (wd *wdaDriver) KeyboardDismiss(keyNames ...string) (err error) { return } +// PressBack simulates a short press on the BACK button. +func (wd *wdaDriver) PressBack(options ...DataOption) (err error) { + windowSize, err := wd.WindowSize() + if err != nil { + return + } + + data := map[string]interface{}{ + "fromX": float64(windowSize.Width) * 0, + "fromY": float64(windowSize.Height) * 0.5, + "toX": float64(windowSize.Width) * 0.6, + "toY": float64(windowSize.Height) * 0.5, + } + + // new data options in post data for extra WDA configurations + d := NewData(data, options...) + + _, err = wd.httpPOST(d.Data, "/session", wd.sessionId, "/wda/dragfromtoforduration") + return +} + func (wd *wdaDriver) PressButton(devBtn DeviceButton) (err error) { // [[FBRoute POST:@"/wda/pressButton"] respondWithTarget:self action:@selector(handlePressButtonCommand:)] data := map[string]interface{}{"name": devBtn} diff --git a/hrp/pkg/uixt/ocr_vedem.go b/hrp/pkg/uixt/ocr_vedem.go index 3f456045..1bcc96ad 100644 --- a/hrp/pkg/uixt/ocr_vedem.go +++ b/hrp/pkg/uixt/ocr_vedem.go @@ -179,16 +179,16 @@ func (s *veDEMOCRService) FindText(text string, imageBuf []byte, options ...Data } rects = append(rects, rect) - } - // contains text while not match exactly - if ocrResult.Text != text { - continue - } + // contains text while not match exactly + if ocrResult.Text != text { + continue + } - // match exactly, and not specify index, return the first one - if data.Index == 0 { - return rect, nil + // match exactly, and not specify index, return the first one + if data.Index == 0 { + return rect, nil + } } } diff --git a/hrp/step.go b/hrp/step.go index 6c41f9d5..0df0347a 100644 --- a/hrp/step.go +++ b/hrp/step.go @@ -22,16 +22,18 @@ const ( var ( WithIdentifier = uixt.WithIdentifier WithMaxRetryTimes = uixt.WithMaxRetryTimes - WithWaitTime = uixt.WithWaitTime - WithIndex = uixt.WithIndex + WithWaitTime = uixt.WithWaitTime // only applicable to SwipeToTap* action + WithIndex = uixt.WithIndex // index of the target element, should start from 1, only applicable to ocr actions WithTimeout = uixt.WithTimeout WithIgnoreNotFoundError = uixt.WithIgnoreNotFoundError WithText = uixt.WithText WithID = uixt.WithID WithDescription = uixt.WithDescription + WithDuration = uixt.WithDuration // only applicable to ios swipe action + WithSteps = uixt.WithSteps // only applicable to android swipe action WithDirection = uixt.WithDirection WithCustomDirection = uixt.WithCustomDirection - WithScope = uixt.WithScope + WithScope = uixt.WithScope // only applicable to ocr actions WithOffset = uixt.WithOffset ) diff --git a/hrp/step_mobile_ui.go b/hrp/step_mobile_ui.go index 6f9fe7fb..4dcf2be8 100644 --- a/hrp/step_mobile_ui.go +++ b/hrp/step_mobile_ui.go @@ -181,6 +181,18 @@ func (s *StepMobile) DoubleTap(params string, options ...uixt.ActionOption) *Ste return &StepMobile{step: s.step} } +func (s *StepMobile) Back(options ...uixt.ActionOption) *StepMobile { + action := uixt.MobileAction{ + Method: uixt.ACTION_Back, + Params: nil, + } + for _, option := range options { + option(&action) + } + s.mobileStep().Actions = append(s.mobileStep().Actions, action) + return &StepMobile{step: s.step} +} + func (s *StepMobile) Swipe(sx, sy, ex, ey float64, options ...uixt.ActionOption) *StepMobile { action := uixt.MobileAction{ Method: uixt.ACTION_Swipe,