mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
merge master
This commit is contained in:
@@ -455,3 +455,31 @@ func IsZeroFloat64(f float64) bool {
|
|||||||
threshold := 1e-9
|
threshold := 1e-9
|
||||||
return math.Abs(f) < threshold
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -520,7 +520,7 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
|
|||||||
if texts, ok := action.Params.([]string); ok {
|
if texts, ok := action.Params.([]string); ok {
|
||||||
return dExt.swipeToTapTexts(texts, action.GetOptions()...)
|
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 dExt.swipeToTapTexts(texts, action.GetOptions()...)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("invalid %s params: %v", ACTION_SwipeToTapTexts, action.Params)
|
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")
|
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)
|
// getSimulationDuration returns simulation duration by given params (in seconds)
|
||||||
func getSimulationDuration(params []interface{}) (milliseconds int64) {
|
func getSimulationDuration(params []interface{}) (milliseconds int64) {
|
||||||
if len(params) == 1 {
|
if len(params) == 1 {
|
||||||
// given constant duration time
|
// given constant duration time
|
||||||
seconds, err := convertToFloat64(params[0])
|
seconds, err := builtin.ConvertToFloat64(params[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Interface("params", params).Msg("invalid params")
|
log.Error().Err(err).Interface("params", params).Msg("invalid params")
|
||||||
return 0
|
return 0
|
||||||
@@ -703,17 +675,17 @@ func getSimulationDuration(params []interface{}) (milliseconds int64) {
|
|||||||
}
|
}
|
||||||
totalProb := 0.0
|
totalProb := 0.0
|
||||||
for i := 0; i+3 <= len(params); i += 3 {
|
for i := 0; i+3 <= len(params); i += 3 {
|
||||||
min, err := convertToFloat64(params[i])
|
min, err := builtin.ConvertToFloat64(params[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Interface("min", params[i]).Msg("invalid minimum time")
|
log.Error().Err(err).Interface("min", params[i]).Msg("invalid minimum time")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
max, err := convertToFloat64(params[i+1])
|
max, err := builtin.ConvertToFloat64(params[i+1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Interface("max", params[i+1]).Msg("invalid maximum time")
|
log.Error().Err(err).Interface("max", params[i+1]).Msg("invalid maximum time")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
weight, err := convertToFloat64(params[i+2])
|
weight, err := builtin.ConvertToFloat64(params[i+2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Interface("weight", params[i+2]).Msg("invalid weight value")
|
log.Error().Err(err).Interface("weight", params[i+2]).Msg("invalid weight value")
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -114,12 +114,12 @@ func (screenResults ScreenResultMap) updatePopupCloseStatus() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// popup existed, but identical popups occurs during next retry
|
// popup existed, but identical popups occurs during next retry
|
||||||
if curPopup.Text == nextPopup.Text && curPopup.Type == nextPopup.Type {
|
if nextPopup.CloseArea.IsIdentical(curPopup.CloseArea) {
|
||||||
popupScreenResultList[i].Popup.CloseStatus = "fail"
|
popupScreenResultList[i].Popup.CloseStatus = CloseStatusFail
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// popup existed, but no popup or different popup occurs during next retry (IsClosed=true)
|
// popup existed, but no popup or different popup occurs during next retry (IsClosed=true)
|
||||||
popupScreenResultList[i].Popup.CloseStatus = "success"
|
popupScreenResultList[i].Popup.CloseStatus = CloseStatusSuccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package uixt
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/httprunner/funplugin"
|
"github.com/httprunner/funplugin"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -434,6 +437,11 @@ type PointF struct {
|
|||||||
Y float64 `json:"y"`
|
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 {
|
type Rect struct {
|
||||||
Point
|
Point
|
||||||
Size
|
Size
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ var popups = [][]string{
|
|||||||
{"管理使用时间", ".*忽略.*"},
|
{"管理使用时间", ".*忽略.*"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
CloseStatusFound = "found"
|
||||||
|
CloseStatusSuccess = "success"
|
||||||
|
CloseStatusFail = "fail"
|
||||||
|
)
|
||||||
|
|
||||||
func findTextPopup(screenTexts OCRTexts) (closePoint *OCRText) {
|
func findTextPopup(screenTexts OCRTexts) (closePoint *OCRText) {
|
||||||
for _, popup := range popups {
|
for _, popup := range popups {
|
||||||
if len(popup) != 2 {
|
if len(popup) != 2 {
|
||||||
@@ -127,9 +133,13 @@ func (dExt *DriverExt) ClosePopupsHandler(options ...ActionOption) error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
screenResult.Popup.RetryCount = retryCount
|
screenResult.Popup.RetryCount = retryCount
|
||||||
|
if !screenResult.Popup.PopupArea.IsEmpty() {
|
||||||
|
screenResult.Popup.CloseStatus = CloseStatusFound
|
||||||
|
}
|
||||||
if screenResult.Popup.CloseArea.IsEmpty() {
|
if screenResult.Popup.CloseArea.IsEmpty() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
screenResult.Popup.CloseStatus = CloseStatusFound
|
||||||
|
|
||||||
if err = dExt.tapPopupHandler(screenResult.Popup); err != nil {
|
if err = dExt.tapPopupHandler(screenResult.Popup); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -133,6 +134,11 @@ func (t OCRTexts) FindText(text string, options ...ActionOption) (result OCRText
|
|||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, 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 {
|
if len(results) == 0 {
|
||||||
@@ -468,6 +474,12 @@ func (box Box) IsEmpty() bool {
|
|||||||
return builtin.IsZeroFloat64(box.Width) && builtin.IsZeroFloat64(box.Height)
|
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 {
|
func (box Box) Center() PointF {
|
||||||
return PointF{
|
return PointF{
|
||||||
X: box.Point.X + box.Width*0.5,
|
X: box.Point.X + box.Width*0.5,
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ func (dExt *DriverExt) swipeToTapTexts(texts []string, options ...ActionOption)
|
|||||||
return errors.New("no text to tap")
|
return errors.New("no text to tap")
|
||||||
}
|
}
|
||||||
|
|
||||||
options = append(options, WithMatchOne(true))
|
options = append(options, WithMatchOne(true), WithRegex(true))
|
||||||
var point PointF
|
var point PointF
|
||||||
findTexts := func(d *DriverExt) error {
|
findTexts := func(d *DriverExt) error {
|
||||||
var err 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, WithDirection("left"))
|
||||||
options = append(options, WithRegex(true))
|
|
||||||
|
|
||||||
actionOptions := NewActionOptions(options...)
|
actionOptions := NewActionOptions(options...)
|
||||||
// default to retry 5 times
|
// default to retry 5 times
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ITestCase represents interface for testcases,
|
// ITestCase represents interface for testcases,
|
||||||
@@ -112,6 +113,13 @@ func (tc *TCase) MakeCompat() (err error) {
|
|||||||
|
|
||||||
// 3. deal with extract expr including hyphen
|
// 3. deal with extract expr including hyphen
|
||||||
convertExtract(step.Extract)
|
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
|
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
|
// convertJmespathExpr deals with limited jmespath expression conversion
|
||||||
func convertJmespathExpr(checkExpr string) string {
|
func convertJmespathExpr(checkExpr string) string {
|
||||||
if strings.Contains(checkExpr, textExtractorSubRegexp) {
|
if strings.Contains(checkExpr, textExtractorSubRegexp) {
|
||||||
|
|||||||
Reference in New Issue
Block a user