mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-12 16:01:27 +08:00
feat: support new action: close_popups
This commit is contained in:
@@ -26,6 +26,7 @@ UI related:
|
|||||||
- refactor: ui validation methods, support parsing expect value
|
- refactor: ui validation methods, support parsing expect value
|
||||||
- fix: reuse the same request body during `GetImage` retry
|
- fix: reuse the same request body during `GetImage` retry
|
||||||
- fix: iOS `tap_xy` scale adaption error
|
- fix: iOS `tap_xy` scale adaption error
|
||||||
|
- feat: support new action: `close_popups`
|
||||||
|
|
||||||
others:
|
others:
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ const (
|
|||||||
ACTION_SwipeToTapText ActionMethod = "swipe_to_tap_text" // swipe up & down to find text and tap
|
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_SwipeToTapTexts ActionMethod = "swipe_to_tap_texts" // swipe up & down to find text and tap
|
||||||
ACTION_VideoCrawler ActionMethod = "video_crawler"
|
ACTION_VideoCrawler ActionMethod = "video_crawler"
|
||||||
|
ACTION_ClosePopups ActionMethod = "close_popups"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MobileAction struct {
|
type MobileAction struct {
|
||||||
@@ -118,6 +119,7 @@ type ActionOptions struct {
|
|||||||
ScreenShotWithUpload bool `json:"screenshot_with_upload,omitempty" yaml:"screenshot_with_upload,omitempty"`
|
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"`
|
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"`
|
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 {
|
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 {
|
func (dExt *DriverExt) ParseActionOptions(options ...ActionOption) []ActionOption {
|
||||||
actionOptions := NewActionOptions(options...)
|
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 errors.Wrapf(err, "invalid video crawler params: %v(%T)", action.Params, action.Params)
|
||||||
}
|
}
|
||||||
return dExt.VideoCrawler(configs)
|
return dExt.VideoCrawler(configs)
|
||||||
|
case ACTION_ClosePopups:
|
||||||
|
return dExt.ClosePopup(action.GetOptions()...)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ type ScreenResult struct {
|
|||||||
VideoType string `json:"video_type,omitempty"` // video type: feed, live-preview or live
|
VideoType string `json:"video_type,omitempty"` // video type: feed, live-preview or live
|
||||||
Feed *FeedVideo `json:"feed,omitempty"`
|
Feed *FeedVideo `json:"feed,omitempty"`
|
||||||
Live *LiveRoom `json:"live,omitempty"`
|
Live *LiveRoom `json:"live,omitempty"`
|
||||||
|
Popup *CPResult `json:"popup,omitempty"`
|
||||||
|
|
||||||
SwipeStartTime int64 `json:"swipe_start_time"` // 滑动开始时间戳
|
SwipeStartTime int64 `json:"swipe_start_time"` // 滑动开始时间戳
|
||||||
SwipeFinishTime int64 `json:"swipe_finish_time"` // 滑动结束时间戳
|
SwipeFinishTime int64 `json:"swipe_finish_time"` // 滑动结束时间戳
|
||||||
@@ -255,6 +256,7 @@ func (dExt *DriverExt) GetStepCacheData() map[string]interface{} {
|
|||||||
"screenshot_take_elapsed": screenResult.ScreenshotTakeElapsed,
|
"screenshot_take_elapsed": screenResult.ScreenshotTakeElapsed,
|
||||||
"screenshot_cv_elapsed": screenResult.ScreenshotCVElapsed,
|
"screenshot_cv_elapsed": screenResult.ScreenshotCVElapsed,
|
||||||
"total_elapsed": screenResult.TotalElapsed,
|
"total_elapsed": screenResult.TotalElapsed,
|
||||||
|
"popup": screenResult.Popup,
|
||||||
}
|
}
|
||||||
|
|
||||||
screenResults[imagePath] = data
|
screenResults[imagePath] = data
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/httprunner/funplugin"
|
"github.com/httprunner/funplugin"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -434,6 +436,10 @@ type PointF struct {
|
|||||||
Y float64 `json:"y"`
|
Y float64 `json:"y"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p PointF) IsOriginal() bool {
|
||||||
|
return builtin.IsZeroFloat64(p.X) && builtin.IsZeroFloat64(p.Y)
|
||||||
|
}
|
||||||
|
|
||||||
type Rect struct {
|
type Rect struct {
|
||||||
Point
|
Point
|
||||||
Size
|
Size
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package uixt
|
package uixt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,3 +73,75 @@ func (dExt *DriverExt) AutoPopupHandler() error {
|
|||||||
|
|
||||||
return dExt.handleTextPopup(screenResult.Texts)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,8 +65,9 @@ type ImageResult struct {
|
|||||||
// Media(媒体)
|
// Media(媒体)
|
||||||
// Chat(语音)
|
// Chat(语音)
|
||||||
// Event(赛事)
|
// Event(赛事)
|
||||||
LiveType string `json:"liveType"` // 直播间类型
|
LiveType string `json:"liveType"` // 直播间类型
|
||||||
UIResult UIResultMap `json:"uiResult"` // 图标检测
|
UIResult UIResultMap `json:"uiResult"` // 图标检测
|
||||||
|
CPResult CPResult `json:"closeResult"` // 弹窗按钮检测
|
||||||
}
|
}
|
||||||
|
|
||||||
type APIResponseImage struct {
|
type APIResponseImage struct {
|
||||||
@@ -385,6 +386,9 @@ func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *S
|
|||||||
LiveType: imageResult.LiveType,
|
LiveType: imageResult.LiveType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !imageResult.CPResult.CloseArea.IsEmpty() && !imageResult.CPResult.CloseArea.Point.IsOriginal() {
|
||||||
|
screenResult.Popup = &imageResult.CPResult
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dExt.cacheStepData.screenResults[imagePath] = screenResult
|
dExt.cacheStepData.screenResults[imagePath] = screenResult
|
||||||
@@ -435,21 +439,25 @@ func getRectangleCenterPoint(rect image.Rectangle) (point PointF) {
|
|||||||
return point
|
return point
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCenterPoint(point PointF, width, height float64) PointF {
|
type Box struct {
|
||||||
return PointF{
|
|
||||||
X: point.X + width*0.5,
|
|
||||||
Y: point.Y + height*0.5,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type UIResult struct {
|
|
||||||
Point PointF `json:"point"`
|
Point PointF `json:"point"`
|
||||||
Width float64 `json:"width"`
|
Width float64 `json:"width"`
|
||||||
Height float64 `json:"height"`
|
Height float64 `json:"height"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u UIResult) Center() PointF {
|
func (box Box) IsEmpty() bool {
|
||||||
return getCenterPoint(u.Point, u.Width, u.Height)
|
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
|
type UIResults []UIResult
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
"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))
|
options = append(options, WithOffset(0, -25))
|
||||||
}
|
}
|
||||||
// set default swipe interval to 1 second
|
// set default swipe interval to 1 second
|
||||||
if actionOptions.Interval == 0 {
|
if builtin.IsZeroFloat64(actionOptions.Interval) {
|
||||||
options = append(options, WithInterval(1))
|
options = append(options, WithInterval(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user