feat: support new action: close_popups

This commit is contained in:
buyuxiang
2023-08-25 23:40:07 +08:00
parent 667bdb5b0c
commit c6507447e2
7 changed files with 116 additions and 13 deletions

View File

@@ -58,6 +58,7 @@ const (
ACTION_SwipeToTapText ActionMethod = "swipe_to_tap_text" // swipe up & down to find text and tap
ACTION_SwipeToTapTexts ActionMethod = "swipe_to_tap_texts" // swipe up & down to find text and tap
ACTION_VideoCrawler ActionMethod = "video_crawler"
ACTION_ClosePopups ActionMethod = "close_popups"
)
type MobileAction struct {
@@ -118,6 +119,7 @@ type ActionOptions struct {
ScreenShotWithUpload bool `json:"screenshot_with_upload,omitempty" yaml:"screenshot_with_upload,omitempty"`
ScreenShotWithLiveType bool `json:"screenshot_with_live_type,omitempty" yaml:"screenshot_with_live_type,omitempty"`
ScreenShotWithUITypes []string `json:"screenshot_with_ui_types,omitempty" yaml:"screenshot_with_ui_types,omitempty"`
ScreenShotWithClose bool `json:"screenshot_with_close,omitempty" yaml:"screenshot_with_close,omitempty"`
}
func (o *ActionOptions) Options() []ActionOption {
@@ -438,6 +440,12 @@ func WithScreenShotUITypes(uiTypes ...string) ActionOption {
}
}
func WithScreenShotClose(closeOn bool) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithClose = closeOn
}
}
func (dExt *DriverExt) ParseActionOptions(options ...ActionOption) []ActionOption {
actionOptions := NewActionOptions(options...)
@@ -632,6 +640,8 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
return errors.Wrapf(err, "invalid video crawler params: %v(%T)", action.Params, action.Params)
}
return dExt.VideoCrawler(configs)
case ACTION_ClosePopups:
return dExt.ClosePopup(action.GetOptions()...)
}
return nil
}

View File

@@ -61,6 +61,7 @@ type ScreenResult struct {
VideoType string `json:"video_type,omitempty"` // video type: feed, live-preview or live
Feed *FeedVideo `json:"feed,omitempty"`
Live *LiveRoom `json:"live,omitempty"`
Popup *CPResult `json:"popup,omitempty"`
SwipeStartTime int64 `json:"swipe_start_time"` // 滑动开始时间戳
SwipeFinishTime int64 `json:"swipe_finish_time"` // 滑动结束时间戳
@@ -255,6 +256,7 @@ func (dExt *DriverExt) GetStepCacheData() map[string]interface{} {
"screenshot_take_elapsed": screenResult.ScreenshotTakeElapsed,
"screenshot_cv_elapsed": screenResult.ScreenshotCVElapsed,
"total_elapsed": screenResult.TotalElapsed,
"popup": screenResult.Popup,
}
screenResults[imagePath] = data

View File

@@ -6,6 +6,8 @@ import (
"time"
"github.com/httprunner/funplugin"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
)
var (
@@ -434,6 +436,10 @@ type PointF struct {
Y float64 `json:"y"`
}
func (p PointF) IsOriginal() bool {
return builtin.IsZeroFloat64(p.X) && builtin.IsZeroFloat64(p.Y)
}
type Rect struct {
Point
Size

View File

@@ -1,9 +1,12 @@
package uixt
import (
"time"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/code"
)
@@ -70,3 +73,75 @@ func (dExt *DriverExt) AutoPopupHandler() error {
return dExt.handleTextPopup(screenResult.Texts)
}
// CRResult represents the result of recognized popup to close
type CPResult struct {
Type string `json:"type"`
PopupArea Box `json:"popupArea"`
CloseArea Box `json:"closeArea"`
Text string `json:"text"`
}
type PopupInfo struct {
// TODO: support more types of popup report info
// Status bool `json:"status"`
Type string `json:"type"`
RetryCount int `json:"retry_count"`
PicName string `json:"pic_name"`
PicURL string `json:"pic_url"`
Point PointF `json:"point"`
}
func (dExt *DriverExt) ClosePopup(options ...ActionOption) error {
actionOptions := NewActionOptions(options...)
// default to retry 5 times
if actionOptions.MaxRetryTimes == 0 {
options = append(options, WithMaxRetryTimes(5))
}
// set default swipe interval to 1 second
if builtin.IsZeroFloat64(actionOptions.Interval) {
options = append(options, WithInterval(1))
}
return dExt.ClosePopupHandler(options...)
}
func (dExt *DriverExt) ClosePopupHandler(options ...ActionOption) error {
actionOptions := NewActionOptions(options...)
maxRetryTimes := actionOptions.MaxRetryTimes
interval := actionOptions.Interval
for retryCount := 1; retryCount <= maxRetryTimes; retryCount++ {
screenResult, err := dExt.GetScreenResult(
WithScreenShotClose(true), WithScreenShotUpload(true))
if err != nil {
return errors.Wrap(err, "get screen result failed for popup handler")
}
// not popup, fast return
if screenResult.Popup == nil {
break
}
if err = dExt.tapPopupHandler(screenResult.Popup); err != nil {
return err
}
// sleep for another popup (if existed) to pop
time.Sleep(time.Duration(1000*interval) * time.Millisecond)
}
return nil
}
func (dExt *DriverExt) tapPopupHandler(cpResult *CPResult) error {
if cpResult == nil {
return nil
}
log.Info().Str("type", cpResult.Type).Str("text", cpResult.Text).Msg("close popup")
popupCenter := cpResult.CloseArea.Center()
if err := dExt.TapAbsXY(popupCenter.X, popupCenter.Y); err != nil {
log.Error().Err(err).Msg("tap popup failed")
return errors.Wrap(code.MobileUIPopupError, err.Error())
}
// tap popup success
return nil
}

View File

@@ -65,8 +65,9 @@ type ImageResult struct {
// Media媒体
// Chat语音
// Event赛事
LiveType string `json:"liveType"` // 直播间类型
UIResult UIResultMap `json:"uiResult"` // 图标检测
LiveType string `json:"liveType"` // 直播间类型
UIResult UIResultMap `json:"uiResult"` // 图标检测
CPResult CPResult `json:"closeResult"` // 弹窗按钮检测
}
type APIResponseImage struct {
@@ -385,6 +386,9 @@ func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *S
LiveType: imageResult.LiveType,
}
}
if !imageResult.CPResult.CloseArea.IsEmpty() && !imageResult.CPResult.CloseArea.Point.IsOriginal() {
screenResult.Popup = &imageResult.CPResult
}
}
dExt.cacheStepData.screenResults[imagePath] = screenResult
@@ -435,21 +439,25 @@ func getRectangleCenterPoint(rect image.Rectangle) (point PointF) {
return point
}
func getCenterPoint(point PointF, width, height float64) PointF {
return PointF{
X: point.X + width*0.5,
Y: point.Y + height*0.5,
}
}
type UIResult struct {
type Box struct {
Point PointF `json:"point"`
Width float64 `json:"width"`
Height float64 `json:"height"`
}
func (u UIResult) Center() PointF {
return getCenterPoint(u.Point, u.Width, u.Height)
func (box Box) IsEmpty() bool {
return builtin.IsZeroFloat64(box.Width) && builtin.IsZeroFloat64(box.Height)
}
func (box Box) Center() PointF {
return PointF{
X: box.Point.X + box.Width*0.5,
Y: box.Point.Y + box.Height*0.5,
}
}
type UIResult struct {
Box
}
type UIResults []UIResult

View File

@@ -7,6 +7,7 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/code"
)
@@ -188,7 +189,7 @@ func (dExt *DriverExt) swipeToTapApp(appName string, options ...ActionOption) er
options = append(options, WithOffset(0, -25))
}
// set default swipe interval to 1 second
if actionOptions.Interval == 0 {
if builtin.IsZeroFloat64(actionOptions.Interval) {
options = append(options, WithInterval(1))
}