diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 9d149df6..918530b3 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -455,3 +455,31 @@ func IsZeroFloat64(f float64) bool { threshold := 1e-9 return math.Abs(f) < threshold } + +func ConvertToFloat64(val interface{}) (float64, error) { + switch v := val.(type) { + case float64: + return v, nil + case int: + return float64(v), nil + case int64: + return float64(v), nil + default: + return 0, fmt.Errorf("invalid type for conversion to float64: %T, value: %+v", val, val) + } +} + +func ConvertToStringSlice(val interface{}) ([]string, error) { + if valSlice, ok := val.([]interface{}); ok { + var res []string + for _, iVal := range valSlice { + valString, ok := iVal.(string) + if !ok { + return nil, fmt.Errorf("invalid type for converting one of the elements to string: %T, value: %v", iVal, iVal) + } + res = append(res, valString) + } + return res, nil + } + return nil, fmt.Errorf("invalid type for conversion to []string") +} diff --git a/hrp/pkg/uixt/action.go b/hrp/pkg/uixt/action.go index 40ebc6cf..1a813f3b 100644 --- a/hrp/pkg/uixt/action.go +++ b/hrp/pkg/uixt/action.go @@ -520,7 +520,7 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) { if texts, ok := action.Params.([]string); ok { return dExt.swipeToTapTexts(texts, action.GetOptions()...) } - if texts, err := convertToStringSlice(action.Params); err == nil { + if texts, err := builtin.ConvertToStringSlice(action.Params); err == nil { return dExt.swipeToTapTexts(texts, action.GetOptions()...) } return fmt.Errorf("invalid %s params: %v", ACTION_SwipeToTapTexts, action.Params) @@ -652,39 +652,11 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) { var errActionNotImplemented = errors.New("UI action not implemented") -func convertToFloat64(val interface{}) (float64, error) { - switch v := val.(type) { - case float64: - return v, nil - case int: - return float64(v), nil - case int64: - return float64(v), nil - default: - return 0, fmt.Errorf("invalid type for conversion to float64: %T, value: %+v", val, val) - } -} - -func convertToStringSlice(val interface{}) ([]string, error) { - if valSlice, ok := val.([]interface{}); ok { - var res []string - for _, iVal := range valSlice { - valString, ok := iVal.(string) - if !ok { - return nil, fmt.Errorf("invalid type for converting one of the elements to string: %T, value: %v", iVal, iVal) - } - res = append(res, valString) - } - return res, nil - } - return nil, fmt.Errorf("invalid type for conversion to []string") -} - // getSimulationDuration returns simulation duration by given params (in seconds) func getSimulationDuration(params []interface{}) (milliseconds int64) { if len(params) == 1 { // given constant duration time - seconds, err := convertToFloat64(params[0]) + seconds, err := builtin.ConvertToFloat64(params[0]) if err != nil { log.Error().Err(err).Interface("params", params).Msg("invalid params") return 0 @@ -703,17 +675,17 @@ func getSimulationDuration(params []interface{}) (milliseconds int64) { } totalProb := 0.0 for i := 0; i+3 <= len(params); i += 3 { - min, err := convertToFloat64(params[i]) + min, err := builtin.ConvertToFloat64(params[i]) if err != nil { log.Error().Err(err).Interface("min", params[i]).Msg("invalid minimum time") return 0 } - max, err := convertToFloat64(params[i+1]) + max, err := builtin.ConvertToFloat64(params[i+1]) if err != nil { log.Error().Err(err).Interface("max", params[i+1]).Msg("invalid maximum time") return 0 } - weight, err := convertToFloat64(params[i+2]) + weight, err := builtin.ConvertToFloat64(params[i+2]) if err != nil { log.Error().Err(err).Interface("weight", params[i+2]).Msg("invalid weight value") return 0 diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index 1afbe36a..198aa356 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -114,12 +114,12 @@ func (screenResults ScreenResultMap) updatePopupCloseStatus() { continue } // popup existed, but identical popups occurs during next retry - if curPopup.Text == nextPopup.Text && curPopup.Type == nextPopup.Type { - popupScreenResultList[i].Popup.CloseStatus = "fail" + if nextPopup.CloseArea.IsIdentical(curPopup.CloseArea) { + popupScreenResultList[i].Popup.CloseStatus = CloseStatusFail continue } // popup existed, but no popup or different popup occurs during next retry (IsClosed=true) - popupScreenResultList[i].Popup.CloseStatus = "success" + popupScreenResultList[i].Popup.CloseStatus = CloseStatusSuccess } } diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index e816573d..e292d93c 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -2,10 +2,13 @@ package uixt import ( "bytes" + "math" "strings" "time" "github.com/httprunner/funplugin" + + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) var ( @@ -434,6 +437,11 @@ type PointF struct { Y float64 `json:"y"` } +func (p PointF) IsIdentical(p2 PointF) bool { + return builtin.IsZeroFloat64(math.Abs(p.X-p2.X)) && + builtin.IsZeroFloat64(math.Abs(p.Y-p2.Y)) +} + type Rect struct { Point Size diff --git a/hrp/pkg/uixt/popups.go b/hrp/pkg/uixt/popups.go index ed8545a1..689efa5a 100644 --- a/hrp/pkg/uixt/popups.go +++ b/hrp/pkg/uixt/popups.go @@ -27,6 +27,12 @@ var popups = [][]string{ {"管理使用时间", ".*忽略.*"}, } +const ( + CloseStatusFound = "found" + CloseStatusSuccess = "success" + CloseStatusFail = "fail" +) + func findTextPopup(screenTexts OCRTexts) (closePoint *OCRText) { for _, popup := range popups { if len(popup) != 2 { @@ -127,9 +133,13 @@ func (dExt *DriverExt) ClosePopupsHandler(options ...ActionOption) error { break } screenResult.Popup.RetryCount = retryCount + if !screenResult.Popup.PopupArea.IsEmpty() { + screenResult.Popup.CloseStatus = CloseStatusFound + } if screenResult.Popup.CloseArea.IsEmpty() { break } + screenResult.Popup.CloseStatus = CloseStatusFound if err = dExt.tapPopupHandler(screenResult.Popup); err != nil { return err diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index cffc89c4..51eab537 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -5,6 +5,7 @@ import ( "fmt" "image" "io" + "math" "mime/multipart" "net/http" "regexp" @@ -133,6 +134,11 @@ func (t OCRTexts) FindText(text string, options ...ActionOption) (result OCRText } results = append(results, ocrText) + + // return the first one matched exactly when index not specified + if ocrText.Text == text && actionOptions.Index == 0 { + return ocrText, nil + } } if len(results) == 0 { @@ -468,6 +474,12 @@ func (box Box) IsEmpty() bool { return builtin.IsZeroFloat64(box.Width) && builtin.IsZeroFloat64(box.Height) } +func (box Box) IsIdentical(box2 Box) bool { + return box.Point.IsIdentical(box2.Point) && + builtin.IsZeroFloat64(math.Abs(box.Width-box2.Width)) && + builtin.IsZeroFloat64(math.Abs(box.Height-box2.Height)) +} + func (box Box) Center() PointF { return PointF{ X: box.Point.X + box.Width*0.5, diff --git a/hrp/pkg/uixt/swipe.go b/hrp/pkg/uixt/swipe.go index e3cf7f01..c1cc3bfb 100644 --- a/hrp/pkg/uixt/swipe.go +++ b/hrp/pkg/uixt/swipe.go @@ -133,7 +133,7 @@ func (dExt *DriverExt) swipeToTapTexts(texts []string, options ...ActionOption) return errors.New("no text to tap") } - options = append(options, WithMatchOne(true)) + options = append(options, WithMatchOne(true), WithRegex(true)) var point PointF findTexts := func(d *DriverExt) error { var err error @@ -181,7 +181,6 @@ func (dExt *DriverExt) swipeToTapApp(appName string, options ...ActionOption) er } options = append(options, WithDirection("left")) - options = append(options, WithRegex(true)) actionOptions := NewActionOptions(options...) // default to retry 5 times diff --git a/hrp/testcase.go b/hrp/testcase.go index 67d0d715..3640f6f5 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -11,6 +11,7 @@ import ( "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/code" + "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" ) // ITestCase represents interface for testcases, @@ -112,6 +113,13 @@ func (tc *TCase) MakeCompat() (err error) { // 3. deal with extract expr including hyphen convertExtract(step.Extract) + + // 4. deal with mobile step compatibility + if step.Android != nil { + convertCompatMobileStep(step.Android) + } else if step.IOS != nil { + convertCompatMobileStep(step.IOS) + } } return nil } @@ -352,6 +360,30 @@ func convertExtract(extract map[string]string) { } } +func convertCompatMobileStep(mobileStep *MobileStep) { + if mobileStep == nil { + return + } + for i := 0; i < len(mobileStep.Actions); i++ { + ma := mobileStep.Actions[i] + actionOptions := uixt.NewActionOptions(ma.GetOptions()...) + // append tap_cv params to screenshot_with_ui_types option + if ma.Method == uixt.ACTION_TapByCV { + uiTypes, _ := builtin.ConvertToStringSlice(ma.Params) + ma.ActionOptions.ScreenShotWithUITypes = append(ma.ActionOptions.ScreenShotWithUITypes, uiTypes...) + } + // set default max_retry_times to 10 for swipe_to_tap_texts + if ma.Method == uixt.ACTION_SwipeToTapTexts && actionOptions.MaxRetryTimes == 0 { + ma.ActionOptions.MaxRetryTimes = 10 + } + // set default max_retry_times to 10 for swipe_to_tap_text + if ma.Method == uixt.ACTION_SwipeToTapText && actionOptions.MaxRetryTimes == 0 { + ma.ActionOptions.MaxRetryTimes = 10 + } + mobileStep.Actions[i] = ma + } +} + // convertJmespathExpr deals with limited jmespath expression conversion func convertJmespathExpr(checkExpr string) string { if strings.Contains(checkExpr, textExtractorSubRegexp) {