mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
Merge pull request #1669 from httprunner/close-popups
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
|
||||
- fix: reuse the same request body during `GetImage` retry
|
||||
- fix: iOS `tap_xy` scale adaption error
|
||||
- feat: support new action: `close_popups`
|
||||
|
||||
others:
|
||||
|
||||
|
||||
@@ -59,15 +59,12 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "处理青少年弹窗 tap ocr 以及 ui_ocr exists 断言",
|
||||
"name": "处理弹窗 close_popups 默认配置 以及 ui_ocr exists 断言",
|
||||
"android": {
|
||||
"actions": [
|
||||
{
|
||||
"method": "tap_ocr",
|
||||
"params": "我知道了",
|
||||
"options": {
|
||||
"ignore_NotFoundError": true
|
||||
}
|
||||
"method": "close_popups",
|
||||
"options": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -317,14 +314,14 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "处理青少年弹窗 tap ocr 以及 ui_ocr exists 断言",
|
||||
"name": "处理弹窗 close_popups 自定义配置 以及 ui_ocr exists 断言",
|
||||
"android": {
|
||||
"actions": [
|
||||
{
|
||||
"method": "tap_ocr",
|
||||
"params": "我知道了",
|
||||
"method": "close_popups",
|
||||
"options": {
|
||||
"ignore_NotFoundError": true
|
||||
"max_retry_times": 3,
|
||||
"interval": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build localtest
|
||||
|
||||
package uitest
|
||||
|
||||
import (
|
||||
@@ -36,12 +34,9 @@ func TestAndroidExpertTest(t *testing.T) {
|
||||
Home().
|
||||
SwipeToTapApp("$app_name").
|
||||
Sleep(10),
|
||||
hrp.NewStep("处理青少年弹窗 tap ocr 以及 ui_ocr exists 断言").
|
||||
hrp.NewStep("处理弹窗 close_popups 默认配置 以及 ui_ocr exists 断言").
|
||||
Android().
|
||||
TapByOCR(
|
||||
"我知道了",
|
||||
uixt.WithIgnoreNotFoundError(true),
|
||||
).
|
||||
ClosePopups().
|
||||
Validate().
|
||||
AssertOCRExists("推荐", "进入抖音失败"),
|
||||
// 直播赛道
|
||||
@@ -118,11 +113,11 @@ func TestAndroidExpertTest(t *testing.T) {
|
||||
Home().
|
||||
SwipeToTapApp("$app_name", uixt.WithMaxRetryTimes(5), uixt.WithInterval(1), uixt.WithOffset(0, -50)).
|
||||
Sleep(10),
|
||||
hrp.NewStep("处理青少年弹窗 tap ocr 以及 ui_ocr exists 断言").
|
||||
hrp.NewStep("处理弹窗 close_popups 自定义配置 以及 ui_ocr exists 断言").
|
||||
Android().
|
||||
TapByOCR(
|
||||
"我知道了",
|
||||
uixt.WithIgnoreNotFoundError(true),
|
||||
ClosePopups(
|
||||
uixt.WithMaxRetryTimes(3),
|
||||
uixt.WithInterval(2),
|
||||
).
|
||||
Validate().
|
||||
AssertOCRExists("推荐", "进入抖音失败"),
|
||||
@@ -194,12 +189,9 @@ func TestIOSExpertTest(t *testing.T) {
|
||||
Home().
|
||||
SwipeToTapApp("$app_name").
|
||||
Sleep(10),
|
||||
hrp.NewStep("处理青少年弹窗 tap ocr 以及 ui_ocr exists 断言").
|
||||
hrp.NewStep("处理弹窗 close_popups 默认配置 以及 ui_ocr exists 断言").
|
||||
IOS().
|
||||
TapByOCR(
|
||||
"我知道了",
|
||||
uixt.WithIgnoreNotFoundError(true),
|
||||
).
|
||||
ClosePopups().
|
||||
Validate().
|
||||
AssertOCRExists("推荐", "进入抖音失败"),
|
||||
// 直播赛道
|
||||
@@ -275,11 +267,11 @@ func TestIOSExpertTest(t *testing.T) {
|
||||
Home().
|
||||
SwipeToTapApp("$app_name", uixt.WithMaxRetryTimes(5), uixt.WithInterval(1), uixt.WithOffset(0, -50)).
|
||||
Sleep(10),
|
||||
hrp.NewStep("处理青少年弹窗 tap ocr 以及 ui_ocr exists 断言").
|
||||
hrp.NewStep("处理弹窗 close_popups 自定义配置 以及 ui_ocr exists 断言").
|
||||
IOS().
|
||||
TapByOCR(
|
||||
"我知道了",
|
||||
uixt.WithIgnoreNotFoundError(true),
|
||||
ClosePopups(
|
||||
uixt.WithMaxRetryTimes(3),
|
||||
uixt.WithInterval(2),
|
||||
).
|
||||
Validate().
|
||||
AssertOCRExists("推荐", "进入抖音失败"),
|
||||
|
||||
@@ -52,15 +52,12 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "处理青少年弹窗 tap ocr 以及 ui_ocr exists 断言",
|
||||
"name": "处理弹窗 close_popups 默认配置 以及 ui_ocr exists 断言",
|
||||
"ios": {
|
||||
"actions": [
|
||||
{
|
||||
"method": "tap_ocr",
|
||||
"params": "我知道了",
|
||||
"options": {
|
||||
"ignore_NotFoundError": true
|
||||
}
|
||||
"method": "close_popups",
|
||||
"options": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -302,14 +299,14 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "处理青少年弹窗 tap ocr 以及 ui_ocr exists 断言",
|
||||
"name": "处理弹窗 close_popups 自定义配置 以及 ui_ocr exists 断言",
|
||||
"ios": {
|
||||
"actions": [
|
||||
{
|
||||
"method": "tap_ocr",
|
||||
"params": "我知道了",
|
||||
"method": "close_popups",
|
||||
"options": {
|
||||
"ignore_NotFoundError": true
|
||||
"max_retry_times": 3,
|
||||
"interval": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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 {
|
||||
@@ -114,10 +115,11 @@ type ActionOptions struct {
|
||||
Custom map[string]interface{} `json:"custom,omitempty" yaml:"custom,omitempty"`
|
||||
|
||||
// screenshot related
|
||||
ScreenShotWithOCR bool `json:"screenshot_with_ocr,omitempty" yaml:"screenshot_with_ocr,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"`
|
||||
ScreenShotWithUITypes []string `json:"screenshot_with_ui_types,omitempty" yaml:"screenshot_with_ui_types,omitempty"`
|
||||
ScreenShotWithOCR bool `json:"screenshot_with_ocr,omitempty" yaml:"screenshot_with_ocr,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"`
|
||||
ScreenShotWithUITypes []string `json:"screenshot_with_ui_types,omitempty" yaml:"screenshot_with_ui_types,omitempty"`
|
||||
ScreenShotWithClosePopups bool `json:"screenshot_with_close_popups,omitempty" yaml:"screenshot_with_close_popups,omitempty"`
|
||||
}
|
||||
|
||||
func (o *ActionOptions) Options() []ActionOption {
|
||||
@@ -217,12 +219,12 @@ func (o *ActionOptions) Options() []ActionOption {
|
||||
|
||||
func (o *ActionOptions) screenshotActions() []string {
|
||||
actions := []string{}
|
||||
if o.ScreenShotWithOCR {
|
||||
actions = append(actions, "ocr")
|
||||
}
|
||||
if o.ScreenShotWithUpload {
|
||||
actions = append(actions, "upload")
|
||||
}
|
||||
if o.ScreenShotWithOCR {
|
||||
actions = append(actions, "ocr")
|
||||
}
|
||||
if o.ScreenShotWithLiveType {
|
||||
actions = append(actions, "liveType")
|
||||
}
|
||||
@@ -230,6 +232,9 @@ func (o *ActionOptions) screenshotActions() []string {
|
||||
if len(o.ScreenShotWithUITypes) > 0 {
|
||||
actions = append(actions, "ui")
|
||||
}
|
||||
if o.ScreenShotWithClosePopups {
|
||||
actions = append(actions, "close")
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
@@ -438,6 +443,12 @@ func WithScreenShotUITypes(uiTypes ...string) ActionOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithScreenShotClosePopups(closeOn bool) ActionOption {
|
||||
return func(o *ActionOptions) {
|
||||
o.ScreenShotWithClosePopups = closeOn
|
||||
}
|
||||
}
|
||||
|
||||
func (dExt *DriverExt) ParseActionOptions(options ...ActionOption) []ActionOption {
|
||||
actionOptions := NewActionOptions(options...)
|
||||
|
||||
@@ -560,10 +571,11 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
|
||||
}
|
||||
return fmt.Errorf("invalid %s params: %v", ACTION_TapByOCR, action.Params)
|
||||
case ACTION_TapByCV:
|
||||
actionOptions := NewActionOptions(action.GetOptions()...)
|
||||
if imagePath, ok := action.Params.(string); ok {
|
||||
return dExt.TapByCV(imagePath, action.GetOptions()...)
|
||||
} else if err := dExt.TapByUIDetection(action.GetOptions()...); err == nil {
|
||||
return nil
|
||||
} else if len(actionOptions.ScreenShotWithUITypes) > 0 {
|
||||
return dExt.TapByUIDetection(action.GetOptions()...)
|
||||
}
|
||||
return fmt.Errorf("invalid %s params: %v", ACTION_TapByCV, action.Params)
|
||||
case ACTION_DoubleTapXY:
|
||||
@@ -632,6 +644,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.ClosePopups(action.GetOptions()...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -61,6 +62,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 *PopupInfo `json:"popup,omitempty"`
|
||||
|
||||
SwipeStartTime int64 `json:"swipe_start_time"` // 滑动开始时间戳
|
||||
SwipeFinishTime int64 `json:"swipe_finish_time"` // 滑动结束时间戳
|
||||
@@ -72,11 +74,59 @@ type ScreenResult struct {
|
||||
TotalElapsed int64 `json:"total_elapsed"` // current_swipe_finish -> next_swipe_start 整体耗时(ms)
|
||||
}
|
||||
|
||||
type ScreenResultMap map[string]*ScreenResult
|
||||
|
||||
// getScreenShotUrls returns screenShotsUrls using imagePath as key and uploaded URL as value
|
||||
func (screenResults ScreenResultMap) getScreenShotUrls() map[string]string {
|
||||
screenShotsUrls := make(map[string]string)
|
||||
for imagePath, screenResult := range screenResults {
|
||||
if screenResult.UploadedURL == "" {
|
||||
continue
|
||||
}
|
||||
screenShotsUrls[imagePath] = screenResult.UploadedURL
|
||||
}
|
||||
return screenShotsUrls
|
||||
}
|
||||
|
||||
// updatePopupCloseStatus checks if popup closed normally in every screenResult with close_popups on:
|
||||
func (screenResults ScreenResultMap) updatePopupCloseStatus() {
|
||||
var popupScreenResultList []*ScreenResult
|
||||
for _, screenResult := range screenResults {
|
||||
if screenResult.Popup == nil {
|
||||
continue
|
||||
}
|
||||
popupScreenResultList = append(popupScreenResultList, screenResult)
|
||||
}
|
||||
if len(popupScreenResultList) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Slice(popupScreenResultList, func(i, j int) bool {
|
||||
return popupScreenResultList[i].Popup.RetryCount < popupScreenResultList[j].Popup.RetryCount
|
||||
})
|
||||
|
||||
for i := 0; i < len(popupScreenResultList)-1; i++ {
|
||||
curPopup := popupScreenResultList[i].Popup
|
||||
nextPopup := popupScreenResultList[i+1].Popup
|
||||
|
||||
// popup not existed, no need to close
|
||||
if curPopup.CloseArea.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
// popup existed, but identical popups occurs during next retry
|
||||
if curPopup.Text == nextPopup.Text && curPopup.Type == nextPopup.Type {
|
||||
popupScreenResultList[i].Popup.CloseStatus = "fail"
|
||||
continue
|
||||
}
|
||||
// popup existed, but no popup or different popup occurs during next retry (IsClosed=true)
|
||||
popupScreenResultList[i].Popup.CloseStatus = "success"
|
||||
}
|
||||
}
|
||||
|
||||
type cacheStepData struct {
|
||||
// cache step screenshot paths
|
||||
screenShots []string
|
||||
// cache step screenshot ocr results, key is image path, value is ScreenResult
|
||||
screenResults map[string]*ScreenResult
|
||||
screenResults ScreenResultMap
|
||||
// cache feed/live video stat
|
||||
videoCrawler *VideoCrawler
|
||||
}
|
||||
@@ -223,14 +273,8 @@ func (dExt *DriverExt) GetStepCacheData() map[string]interface{} {
|
||||
cacheData["video_stat"] = dExt.cacheStepData.videoCrawler
|
||||
cacheData["screenshots"] = dExt.cacheStepData.screenShots
|
||||
|
||||
screenShotsUrls := make(map[string]string)
|
||||
for imagePath, screenResult := range dExt.cacheStepData.screenResults {
|
||||
if screenResult.UploadedURL == "" {
|
||||
continue
|
||||
}
|
||||
screenShotsUrls[imagePath] = screenResult.UploadedURL
|
||||
}
|
||||
cacheData["screenshots_urls"] = screenShotsUrls
|
||||
cacheData["screenshots_urls"] = dExt.cacheStepData.screenResults.getScreenShotUrls()
|
||||
dExt.cacheStepData.screenResults.updatePopupCloseStatus()
|
||||
|
||||
screenSize, err := dExt.Driver.WindowSize()
|
||||
if err != nil {
|
||||
@@ -255,6 +299,8 @@ func (dExt *DriverExt) GetStepCacheData() map[string]interface{} {
|
||||
"screenshot_take_elapsed": screenResult.ScreenshotTakeElapsed,
|
||||
"screenshot_cv_elapsed": screenResult.ScreenshotCVElapsed,
|
||||
"total_elapsed": screenResult.TotalElapsed,
|
||||
"icons": screenResult.Icons,
|
||||
"popup": screenResult.Popup,
|
||||
}
|
||||
|
||||
screenResults[imagePath] = data
|
||||
|
||||
@@ -201,6 +201,12 @@ func (wd *wdaDriver) WindowSize() (size Size, err error) {
|
||||
return Size{}, err
|
||||
}
|
||||
size = reply.Value.Size
|
||||
scale, err := wd.Scale()
|
||||
if err != nil {
|
||||
return Size{}, errors.Wrap(err, "get window size scale failed")
|
||||
}
|
||||
size.Height = size.Height * int(scale)
|
||||
size.Width = size.Width * int(scale)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -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,85 @@ func (dExt *DriverExt) AutoPopupHandler() error {
|
||||
|
||||
return dExt.handleTextPopup(screenResult.Texts)
|
||||
}
|
||||
|
||||
// ClosePopupsResult represents the result of recognized popup to close
|
||||
type ClosePopupsResult struct {
|
||||
Type string `json:"type"`
|
||||
PopupArea Box `json:"popupArea"`
|
||||
CloseArea Box `json:"closeArea"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type PopupInfo struct {
|
||||
CloseStatus string `json:"close_status"`
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
PicName string `json:"pic_name"`
|
||||
PicURL string `json:"pic_url"`
|
||||
PopupArea Box `json:"popup_area"`
|
||||
CloseArea Box `json:"close_area"`
|
||||
}
|
||||
|
||||
func (dExt *DriverExt) ClosePopups(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.ClosePopupsHandler(options...)
|
||||
}
|
||||
|
||||
func (dExt *DriverExt) ClosePopupsHandler(options ...ActionOption) error {
|
||||
actionOptions := NewActionOptions(options...)
|
||||
maxRetryTimes := actionOptions.MaxRetryTimes
|
||||
interval := actionOptions.Interval
|
||||
|
||||
for retryCount := 0; retryCount < maxRetryTimes; retryCount++ {
|
||||
screenResult, err := dExt.GetScreenResult(
|
||||
WithScreenShotClosePopups(true), WithScreenShotUpload(true))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("get screen result failed for popup handler")
|
||||
continue
|
||||
}
|
||||
// 1. there are no popups here (fast return normally)
|
||||
// 2. failed to close popup (maybe tap error, return error)
|
||||
// 3. successful to close popup (sleep and wait for next retry if existed)
|
||||
if screenResult.Popup == nil {
|
||||
break
|
||||
}
|
||||
screenResult.Popup.RetryCount = retryCount
|
||||
if screenResult.Popup.CloseArea.IsEmpty() {
|
||||
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(popup *PopupInfo) error {
|
||||
if popup == nil {
|
||||
return nil
|
||||
}
|
||||
if popup.CloseArea.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
log.Info().Str("type", popup.Type).Str("text", popup.Text).Msg("close popup")
|
||||
popupCenter := popup.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
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -65,8 +66,9 @@ type ImageResult struct {
|
||||
// Media(媒体)
|
||||
// Chat(语音)
|
||||
// Event(赛事)
|
||||
LiveType string `json:"liveType"` // 直播间类型
|
||||
UIResult UIResultMap `json:"uiResult"` // 图标检测
|
||||
LiveType string `json:"liveType"` // 直播间类型
|
||||
UIResult UIResultMap `json:"uiResult"` // 图标检测
|
||||
CPResult ClosePopupsResult `json:"closeResult"` // 弹窗按钮检测
|
||||
}
|
||||
|
||||
type APIResponseImage struct {
|
||||
@@ -356,6 +358,11 @@ type IImageService interface {
|
||||
func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *ScreenResult, err error) {
|
||||
startTime := time.Now()
|
||||
fileName := builtin.GenNameWithTimestamp("%d_screenshot")
|
||||
actionOptions := NewActionOptions(options...)
|
||||
screenshotActions := actionOptions.screenshotActions()
|
||||
if len(screenshotActions) != 0 {
|
||||
fileName = builtin.GenNameWithTimestamp("%d_" + strings.Join(screenshotActions, "_"))
|
||||
}
|
||||
bufSource, imagePath, err := dExt.takeScreenShot(fileName)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -385,6 +392,17 @@ func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *S
|
||||
LiveType: imageResult.LiveType,
|
||||
}
|
||||
}
|
||||
if actionOptions.ScreenShotWithClosePopups {
|
||||
screenResult.Popup = &PopupInfo{
|
||||
Type: imageResult.CPResult.Type,
|
||||
Text: imageResult.CPResult.Text,
|
||||
PicName: imagePath,
|
||||
PicURL: imageResult.URL,
|
||||
PopupArea: imageResult.CPResult.PopupArea,
|
||||
CloseArea: imageResult.CPResult.CloseArea,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dExt.cacheStepData.screenResults[imagePath] = screenResult
|
||||
@@ -435,21 +453,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
|
||||
|
||||
@@ -81,7 +81,7 @@ func TestTapUIWithScreenshot(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = driver.TapByUIDetection([]string{"dyhouse", "shoppingbag"})
|
||||
err = driver.TapByUIDetection(WithScreenShotUITypes("dyhouse", "shoppingbag"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -97,3 +97,25 @@ func TestDriverExtOCR(t *testing.T) {
|
||||
t.Logf("point.X: %v, point.Y: %v", point.X, point.Y)
|
||||
driverExt.Driver.TapFloat(point.X, point.Y-20)
|
||||
}
|
||||
|
||||
func TestClosePopup(t *testing.T) {
|
||||
setupAndroid(t)
|
||||
|
||||
screenResult, err := driverExt.GetScreenResult(
|
||||
WithScreenShotClosePopups(true), WithScreenShotUpload(true))
|
||||
if err != nil {
|
||||
t.Logf("get screen result failed for popup handler: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("screen result: %v", screenResult)
|
||||
|
||||
if screenResult.Popup == nil {
|
||||
t.Log("there are no popups here")
|
||||
return
|
||||
}
|
||||
t.Logf("popup info: %v", screenResult.Popup)
|
||||
|
||||
if err = driverExt.tapPopupHandler(screenResult.Popup); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -136,15 +137,19 @@ func (dExt *DriverExt) swipeToTapTexts(texts []string, options ...ActionOption)
|
||||
var point PointF
|
||||
findTexts := func(d *DriverExt) error {
|
||||
var err error
|
||||
screenTexts, err := d.GetScreenTexts()
|
||||
screenResult, err := d.GetScreenResult(
|
||||
WithScreenShotOCR(true),
|
||||
WithScreenShotUpload(true),
|
||||
WithScreenShotClosePopups(true),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
points, err := screenTexts.FindTexts(texts, dExt.ParseActionOptions(options...)...)
|
||||
points, err := screenResult.Texts.FindTexts(texts, dExt.ParseActionOptions(options...)...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("swipeToTapTexts failed")
|
||||
// target texts not found, try to auto handle popup
|
||||
if e := dExt.handleTextPopup(screenTexts); e != nil {
|
||||
if e := dExt.tapPopupHandler(screenResult.Popup); e != nil {
|
||||
log.Error().Err(e).Msg("auto handle popup failed")
|
||||
}
|
||||
return err
|
||||
@@ -188,7 +193,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))
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,8 @@ func (dExt *DriverExt) TapXY(x, y float64, options ...ActionOption) error {
|
||||
return fmt.Errorf("x, y percentage should be <= 1, got x=%v, y=%v", x, y)
|
||||
}
|
||||
|
||||
scale, err := dExt.Driver.Scale()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get scale: %v", err)
|
||||
}
|
||||
x = x * float64(dExt.windowSize.Width) * scale
|
||||
y = y * float64(dExt.windowSize.Height) * scale
|
||||
x = x * float64(dExt.windowSize.Width)
|
||||
y = y * float64(dExt.windowSize.Height)
|
||||
|
||||
return dExt.TapAbsXY(x, y, options...)
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) {
|
||||
if feedVideo.VideoID == crawler.lastFeed.VideoID {
|
||||
// app event tracking not changed
|
||||
// check and handle popups
|
||||
if err = crawler.driverExt.AutoPopupHandler(); err != nil {
|
||||
if err = crawler.driverExt.ClosePopupsHandler(WithMaxRetryTimes(1)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,6 +327,15 @@ func (s *StepMobile) StopCamera() *StepMobile {
|
||||
return &StepMobile{step: s.step}
|
||||
}
|
||||
|
||||
func (s *StepMobile) ClosePopups(options ...uixt.ActionOption) *StepMobile {
|
||||
s.mobileStep().Actions = append(s.mobileStep().Actions, uixt.MobileAction{
|
||||
Method: uixt.ACTION_ClosePopups,
|
||||
Params: nil,
|
||||
Options: uixt.NewActionOptions(options...),
|
||||
})
|
||||
return &StepMobile{step: s.step}
|
||||
}
|
||||
|
||||
// Validate switches to step validation.
|
||||
func (s *StepMobile) Validate() *StepMobileUIValidation {
|
||||
return &StepMobileUIValidation{
|
||||
@@ -615,7 +624,7 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
||||
}
|
||||
|
||||
// automatic handling of pop-up windows on each step finished
|
||||
if err2 := uiDriver.AutoPopupHandler(); err2 != nil {
|
||||
if err2 := uiDriver.ClosePopups(); err2 != nil {
|
||||
log.Error().Err(err2).Str("step", step.Name).Msg("auto handle popup failed")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user