From 3613ffb2c096a7c14ba890dd20d256c2b86ddfab Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Tue, 26 Sep 2023 18:09:47 +0800 Subject: [PATCH 01/60] fix: compatible with swipe params --- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/swipe.go | 2 +- hrp/testcase.go | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 471e7432..9c6a0187 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.6 \ No newline at end of file +v4.3.6.20230926 \ No newline at end of file diff --git a/hrp/pkg/uixt/swipe.go b/hrp/pkg/uixt/swipe.go index 0e34e23b..5ee1c408 100644 --- a/hrp/pkg/uixt/swipe.go +++ b/hrp/pkg/uixt/swipe.go @@ -114,7 +114,7 @@ func (dExt *DriverExt) prepareSwipeAction(options ...ActionOption) func(d *Drive log.Error().Err(err).Msgf("swipe %s failed", d) return err } - } else if d, ok := swipeDirection.([]float64); ok { + } else if d, ok := swipeDirection.([]float64); ok && len(d) == 4 { // custom direction: [fromX, fromY, toX, toY] if err := dExt.SwipeRelative(d[0], d[1], d[2], d[3], options...); err != nil { log.Error().Err(err).Msgf("swipe from (%v, %v) to (%v, %v) failed", diff --git a/hrp/testcase.go b/hrp/testcase.go index 3640f6f5..9b301465 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -380,6 +380,9 @@ func convertCompatMobileStep(mobileStep *MobileStep) { if ma.Method == uixt.ACTION_SwipeToTapText && actionOptions.MaxRetryTimes == 0 { ma.ActionOptions.MaxRetryTimes = 10 } + if ma.Method == uixt.ACTION_Swipe { + ma.ActionOptions.Direction = ma.Params + } mobileStep.Actions[i] = ma } } From 77161849e57284da436d9da7aab69ae60dae1b1d Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Sat, 7 Oct 2023 18:49:57 +0800 Subject: [PATCH 02/60] fix: use ADBKeyBoard if uiautomator failed --- hrp/pkg/uixt/android_adb_driver.go | 34 +++++++++++++++++++++++++++++ hrp/pkg/uixt/android_uia2_driver.go | 12 ++++++++-- hrp/pkg/uixt/ios_driver.go | 4 ++-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index b2d84652..64b4d94e 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -14,6 +14,8 @@ import ( "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" ) +const AdbKeyBoardPackageName = "com.android.adbkeyboard/.AdbIME" + type adbDriver struct { Driver @@ -327,6 +329,38 @@ func (ad *adbDriver) SendKeys(text string, options ...ActionOption) (err error) return nil } +func (ad *adbDriver) IsAdbKeyBoardInstalled() bool { + output, err := ad.adbClient.RunShellCommand("ime", "list", "-a") + if err != nil { + return false + } + return strings.Contains(output, AdbKeyBoardPackageName) +} + +func (ad *adbDriver) SendKeysByAdbKeyBoard(text string) (err error) { + defer func() { + // Reset to default, don't care which keyboard was chosen before switch: + _, err = ad.adbClient.RunShellCommand("ime", "reset") + }() + + // Enable ADBKeyBoard from adb + if _, err = ad.adbClient.RunShellCommand("ime", "enable", AdbKeyBoardPackageName); err != nil { + return + } + // Switch to ADBKeyBoard from adb + if _, err = ad.adbClient.RunShellCommand("ime", "set", AdbKeyBoardPackageName); err != nil { + return + } + time.Sleep(time.Second) + // input Quoted text + text = strings.ReplaceAll(text, " ", "\\ ") + if _, err = ad.adbClient.RunShellCommand("am", "broadcast", "-a", "ADB_INPUT_TEXT", "--es", "msg", text); err != nil { + return + } + time.Sleep(time.Second) + return +} + func (ad *adbDriver) Input(text string, options ...ActionOption) (err error) { return ad.SendKeys(text, options...) } diff --git a/hrp/pkg/uixt/android_uia2_driver.go b/hrp/pkg/uixt/android_uia2_driver.go index 674b70c3..5f38556d 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -103,8 +103,8 @@ func (ud *uiaDriver) httpRequest(method string, rawURL string, rawBody []byte, d // wait for UIA2 server to resume automatically time.Sleep(3 * time.Second) oldSessionID := ud.sessionId - if err = ud.resetDriver(); err != nil { - log.Err(err).Msgf("failed to reset uia2 driver, retry count: %v", retryCount) + if err2 := ud.resetDriver(); err2 != nil { + log.Err(err2).Msgf("failed to reset uia2 driver, retry count: %v", retryCount) continue } log.Debug().Str("new session", ud.sessionId).Str("old session", oldSessionID).Msgf("successful to reset uia2 driver, retry count: %v", retryCount) @@ -426,6 +426,14 @@ func (ud *uiaDriver) SendKeys(text string, options ...ActionOption) (err error) actionOptions.updateData(data) _, err = ud.httpPOST(data, "/session", ud.sessionId, "keys") + if err != nil { + // use com.android.adbkeyboard if existed + if ud.IsAdbKeyBoardInstalled() { + err = ud.SendKeysByAdbKeyBoard(text) + } else { + _, err = ud.adbClient.RunShellCommand("input", "text", text) + } + } return } diff --git a/hrp/pkg/uixt/ios_driver.go b/hrp/pkg/uixt/ios_driver.go index 52390256..ea2d305f 100644 --- a/hrp/pkg/uixt/ios_driver.go +++ b/hrp/pkg/uixt/ios_driver.go @@ -55,8 +55,8 @@ func (wd *wdaDriver) httpRequest(method string, rawURL string, rawBody []byte, d // TODO: polling WDA to check if resumed automatically time.Sleep(5 * time.Second) oldSessionID := wd.sessionId - if err = wd.resetSession(); err != nil { - log.Err(err).Msgf("failed to reset wda driver, retry count: %v", retryCount) + if err2 := wd.resetSession(); err2 != nil { + log.Err(err2).Msgf("failed to reset wda driver, retry count: %v", retryCount) continue } log.Debug().Str("new session", wd.sessionId).Str("old session", oldSessionID).Msgf("successful to reset wda driver, retry count: %v", retryCount) From acbb3902ccbe0f1697fade66882db8ef5f67c55d Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Sat, 7 Oct 2023 18:53:13 +0800 Subject: [PATCH 03/60] change: use default ocrCluster and support to sepecify timeout --- hrp/pkg/uixt/service_vedem.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index 7e32a9e3..79d137fc 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -218,7 +218,9 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, options ...ActionOp bodyWriter.WriteField("uiTypes", uiType) } - bodyWriter.WriteField("ocrCluster", "highPrecision") + if actionOptions.Timeout > 0 { + bodyWriter.WriteField("timeout", fmt.Sprintf("%v", actionOptions.Timeout)) + } formWriter, err := bodyWriter.CreateFormFile("image", "screenshot.png") if err != nil { From 41f705c0d94f10545e37d3cb6f6a1ee8f00417aa Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Sat, 7 Oct 2023 19:45:41 +0800 Subject: [PATCH 04/60] fix: device serial/udid should be specified --- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/android_device.go | 7 +++++-- hrp/pkg/uixt/ios_device.go | 4 ++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 9c6a0187..d87fdf6c 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.6.20230926 \ No newline at end of file +v4.3.6.2310081724 \ No newline at end of file diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 42b4757e..84edf057 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -88,10 +88,13 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er for _, option := range options { option(device) } - deviceList, err := GetAndroidDevices(device.SerialNumber) if err != nil { - return nil, err + return nil, errors.Wrap(code.AndroidDeviceConnectionError, err.Error()) + } + + if device.SerialNumber == "" && len(deviceList) > 1 { + return nil, errors.Wrap(code.AndroidDeviceConnectionError, "more than one device connected, please specify the serial") } dev := deviceList[0] diff --git a/hrp/pkg/uixt/ios_device.go b/hrp/pkg/uixt/ios_device.go index 843a34b3..b5283667 100644 --- a/hrp/pkg/uixt/ios_device.go +++ b/hrp/pkg/uixt/ios_device.go @@ -237,6 +237,10 @@ func NewIOSDevice(options ...IOSDeviceOption) (device *IOSDevice, err error) { return nil, errors.Wrap(code.IOSDeviceConnectionError, err.Error()) } + if device.UDID == "" && len(deviceList) > 1 { + return nil, errors.Wrap(code.IOSDeviceConnectionError, "more than one device connected, please specify the udid") + } + dev := deviceList[0] udid := dev.Properties().SerialNumber From 79c91637db871524b5f45448b6e6e6982130a191 Mon Sep 17 00:00:00 2001 From: buyuxiang Date: Wed, 11 Oct 2023 14:59:49 +0800 Subject: [PATCH 05/60] close popups before swipeToTapApp --- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/service_vedem.go | 2 +- hrp/pkg/uixt/swipe.go | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index d87fdf6c..e764e622 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.6.2310081724 \ No newline at end of file +v4.3.6.2310111458 \ No newline at end of file diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index 79d137fc..74baef99 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -409,7 +409,7 @@ func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *S screenResult.UploadedURL = imageResult.URL screenResult.Icons = imageResult.UIResult - if actionOptions.ScreenShotWithClosePopups { + if actionOptions.ScreenShotWithClosePopups && imageResult.CPResult != nil { screenResult.Popup = &PopupInfo{ Type: imageResult.CPResult.Type, Text: imageResult.CPResult.Text, diff --git a/hrp/pkg/uixt/swipe.go b/hrp/pkg/uixt/swipe.go index 5ee1c408..4c97d212 100644 --- a/hrp/pkg/uixt/swipe.go +++ b/hrp/pkg/uixt/swipe.go @@ -178,6 +178,11 @@ func (dExt *DriverExt) swipeToTapApp(appName string, options ...ActionOption) er return errors.Wrap(err, "go to home screen failed") } + // automatic handling popups before swipe + if err := dExt.ClosePopups(); err != nil { + log.Error().Err(err).Msg("auto handle popup failed") + } + // swipe to first screen for i := 0; i < 5; i++ { dExt.SwipeRight() From c22dc0836c067cc29fe803ed53e1fcff1222472c Mon Sep 17 00:00:00 2001 From: buyuxiang Date: Fri, 20 Oct 2023 12:03:11 +0800 Subject: [PATCH 06/60] fix: unpredefined tap_cv not found error --- hrp/pkg/uixt/service_vedem.go | 2 +- hrp/testcase.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index 74baef99..d91e3950 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -537,7 +537,7 @@ func (u UIResultMap) FilterUIResults(uiTypes []string) (uiResults UIResults, err return } } - err = errors.Errorf("UI types %v not detected", uiTypes) + err = errors.Wrap(code.CVResultNotFoundError, fmt.Sprintf("UI types %v not detected", uiTypes)) return } diff --git a/hrp/testcase.go b/hrp/testcase.go index 9b301465..9cbccce0 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -371,6 +371,7 @@ func convertCompatMobileStep(mobileStep *MobileStep) { if ma.Method == uixt.ACTION_TapByCV { uiTypes, _ := builtin.ConvertToStringSlice(ma.Params) ma.ActionOptions.ScreenShotWithUITypes = append(ma.ActionOptions.ScreenShotWithUITypes, uiTypes...) + ma.ActionOptions.ScreenShotWithUpload = true } // set default max_retry_times to 10 for swipe_to_tap_texts if ma.Method == uixt.ACTION_SwipeToTapTexts && actionOptions.MaxRetryTimes == 0 { From a772f836ec546c4d1ed9b1c4020a8831a61422c6 Mon Sep 17 00:00:00 2001 From: buyuxiang Date: Thu, 26 Oct 2023 22:05:04 +0800 Subject: [PATCH 07/60] fix: add lost options --- hrp/pkg/uixt/action.go | 16 ++++++++++++++++ hrp/pkg/uixt/service_vedem.go | 3 +++ 2 files changed, 19 insertions(+) diff --git a/hrp/pkg/uixt/action.go b/hrp/pkg/uixt/action.go index 242b59ea..cf28c4b8 100644 --- a/hrp/pkg/uixt/action.go +++ b/hrp/pkg/uixt/action.go @@ -121,6 +121,7 @@ type ActionOptions struct { 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"` + ScreenShotWithOCRCluster string `json:"screenshot_with_ocr_cluster,omitempty" yaml:"screenshot_with_ocr_cluster,omitempty"` } func (o *ActionOptions) Options() []ActionOption { @@ -180,6 +181,9 @@ func (o *ActionOptions) Options() []ActionOption { if len(o.AbsScope) == 4 { options = append(options, WithAbsScope( o.AbsScope[0], o.AbsScope[1], o.AbsScope[2], o.AbsScope[3])) + } else if len(o.Scope) == 4 { + options = append(options, WithScope( + o.Scope[0], o.Scope[1], o.Scope[2], o.Scope[3])) } if len(o.Offset) == 2 { // for tap [x,y] offset @@ -224,6 +228,12 @@ func (o *ActionOptions) Options() []ActionOption { if len(o.ScreenShotWithUITypes) > 0 { options = append(options, WithScreenShotUITypes(o.ScreenShotWithUITypes...)) } + if o.ScreenShotWithClosePopups { + options = append(options, WithScreenShotClosePopups(true)) + } + if o.ScreenShotWithOCRCluster != "" { + options = append(options, WithScreenOCRCluster(o.ScreenShotWithOCRCluster)) + } return options } @@ -472,6 +482,12 @@ func WithScreenShotClosePopups(closeOn bool) ActionOption { } } +func WithScreenOCRCluster(ocrCluster string) ActionOption { + return func(o *ActionOptions) { + o.ScreenShotWithOCRCluster = ocrCluster + } +} + func (dExt *DriverExt) ParseActionOptions(options ...ActionOption) []ActionOption { actionOptions := NewActionOptions(options...) diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index d91e3950..e2c4330b 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -217,6 +217,9 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, options ...ActionOp for _, uiType := range actionOptions.ScreenShotWithUITypes { bodyWriter.WriteField("uiTypes", uiType) } + if actionOptions.ScreenShotWithOCRCluster != "" { + bodyWriter.WriteField("ocrCluster", actionOptions.ScreenShotWithOCRCluster) + } if actionOptions.Timeout > 0 { bodyWriter.WriteField("timeout", fmt.Sprintf("%v", actionOptions.Timeout)) From 81d1fd7da0f3961abcbe5d3b509815c512389683 Mon Sep 17 00:00:00 2001 From: buyuxiang Date: Wed, 18 Oct 2023 21:31:32 +0800 Subject: [PATCH 08/60] feat: support live end to end delay collection --- examples/uitest/android_e2e_delay_test.go | 59 +++++++ examples/uitest/android_e2e_delay_test.json | 146 +++++++++++++++++ hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/action.go | 4 + hrp/pkg/uixt/ext.go | 3 + hrp/pkg/uixt/live_e2e.go | 173 ++++++++++++++++++++ hrp/step_mobile_ui.go | 9 + 7 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 examples/uitest/android_e2e_delay_test.go create mode 100644 examples/uitest/android_e2e_delay_test.json create mode 100644 hrp/pkg/uixt/live_e2e.go diff --git a/examples/uitest/android_e2e_delay_test.go b/examples/uitest/android_e2e_delay_test.go new file mode 100644 index 00000000..be5f9b35 --- /dev/null +++ b/examples/uitest/android_e2e_delay_test.go @@ -0,0 +1,59 @@ +package uitest + +import ( + "testing" + + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" +) + +func TestAndroidDouyinE2E(t *testing.T) { + testCase := &hrp.TestCase{ + Config: hrp.NewConfig("直播_抖音_端到端时延_android"). + WithVariables(map[string]interface{}{ + "device": "${ENV(SerialNumber)}", + "ups": "${ENV(LIVEUPLIST)}", + }). + SetAndroid(uixt.WithSerialNumber("$device"), uixt.WithAdbLogOn(true)), + TestSteps: []hrp.IStep{ + hrp.NewStep("启动抖音"). + Android(). + AppTerminate("com.ss.android.ugc.aweme"). + AppLaunch("com.ss.android.ugc.aweme"). + Home(). + SwipeToTapApp( + "抖音", + uixt.WithMaxRetryTimes(5), + uixt.WithTapOffset(0, -50), + ). + Sleep(20). + Validate(). + AssertOCRExists("推荐", "进入抖音失败"), + hrp.NewStep("点击放大镜"). + Android(). + TapXY(0.9, 0.08). + Sleep(5), + hrp.NewStep("输入账号名称"). + Android(). + Input("$ups"). + Sleep(5), + hrp.NewStep("点击搜索"). + Android(). + TapByOCR("搜索"). + Sleep(5), + hrp.NewStep("端到端采集").Loop(5). + Android(). + TapByOCR( + "直播中", + uixt.WithIgnoreNotFoundError(true), + uixt.WithIndex(-1), + ). + EndToEndDelay(uixt.WithInterval(5), uixt.WithTimeout(120)). + TapByUITypes(uixt.WithScreenShotUITypes("close")), + }, + } + + if err := testCase.Dump2JSON("android_e2e_delay_test.json"); err != nil { + t.Fatal(err) + } +} diff --git a/examples/uitest/android_e2e_delay_test.json b/examples/uitest/android_e2e_delay_test.json new file mode 100644 index 00000000..d3707119 --- /dev/null +++ b/examples/uitest/android_e2e_delay_test.json @@ -0,0 +1,146 @@ +{ + "config": { + "name": "直播_抖音_端到端时延_android", + "variables": { + "device": "${ENV(SerialNumber)}", + "ups": "${ENV(LIVEUPLIST)}" + }, + "android": [ + { + "serial": "$device", + "log_on": true + } + ] + }, + "teststeps": [ + { + "name": "启动抖音", + "android": { + "actions": [ + { + "method": "app_terminate", + "params": "com.ss.android.ugc.aweme" + }, + { + "method": "app_launch", + "params": "com.ss.android.ugc.aweme" + }, + { + "method": "home" + }, + { + "method": "swipe_to_tap_app", + "params": "抖音", + "options": { + "max_retry_times": 5, + "offset": [ + 0, + -50 + ] + } + }, + { + "method": "sleep", + "params": 20 + } + ] + }, + "validate": [ + { + "check": "ui_ocr", + "assert": "exists", + "expect": "推荐", + "msg": "进入抖音失败" + } + ] + }, + { + "name": "点击放大镜", + "android": { + "actions": [ + { + "method": "tap_xy", + "params": [ + 0.9, + 0.08 + ], + "options": { + + } + }, + { + "method": "sleep", + "params": 5 + } + ] + } + }, + { + "name": "输入账号名称", + "android": { + "actions": [ + { + "method": "input", + "params": "$ups", + "options": { + + } + }, + { + "method": "sleep", + "params": 5 + } + ] + } + }, + { + "name": "点击搜索", + "android": { + "actions": [ + { + "method": "tap_ocr", + "params": "搜索", + "options": { + + } + }, + { + "method": "sleep", + "params": 5 + } + ] + } + }, + { + "name": "端到端采集", + "android": { + "actions": [ + { + "method": "tap_ocr", + "params": "直播中", + "options": { + "ignore_NotFoundError": true, + "index": -1 + } + }, + { + "method": "live_e2e", + "options": { + "interval": 5, + "timeout": 120 + } + }, + { + "method": "tap_cv", + "options": { + "screenshot_with_ui_types": [ + "close" + ] + } + } + ] + }, + "loops": 5 + } + ] +} diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index e764e622..96a9a9b4 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.6.2310111458 \ No newline at end of file +v4.3.6.2310201510 diff --git a/hrp/pkg/uixt/action.go b/hrp/pkg/uixt/action.go index cf28c4b8..59e903b8 100644 --- a/hrp/pkg/uixt/action.go +++ b/hrp/pkg/uixt/action.go @@ -59,6 +59,7 @@ const ( 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" + ACTION_EndToEndDelay ActionMethod = "live_e2e" ) type MobileAction struct { @@ -685,6 +686,9 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) { return dExt.VideoCrawler(configs) case ACTION_ClosePopups: return dExt.ClosePopups(action.GetOptions()...) + case ACTION_EndToEndDelay: + dExt.CollectEndToEndDelay(action.GetOptions()...) + return nil } return nil } diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index 54009453..c242f341 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -128,12 +128,14 @@ type cacheStepData struct { screenResults ScreenResultMap // cache feed/live video stat videoCrawler *VideoCrawler + e2eDelay []timeLog } func (d *cacheStepData) reset() { d.screenShots = make([]string, 0) d.screenResults = make(map[string]*ScreenResult) d.videoCrawler = nil + d.e2eDelay = nil } type DriverExt struct { @@ -275,6 +277,7 @@ func (dExt *DriverExt) GetStepCacheData() map[string]interface{} { cacheData["screenshots_urls"] = dExt.cacheStepData.screenResults.getScreenShotUrls() dExt.cacheStepData.screenResults.updatePopupCloseStatus() cacheData["screen_results"] = dExt.cacheStepData.screenResults + cacheData["e2e_results"] = dExt.cacheStepData.e2eDelay // clear cache dExt.cacheStepData.reset() diff --git a/hrp/pkg/uixt/live_e2e.go b/hrp/pkg/uixt/live_e2e.go new file mode 100644 index 00000000..51c238cb --- /dev/null +++ b/hrp/pkg/uixt/live_e2e.go @@ -0,0 +1,173 @@ +package uixt + +import ( + "fmt" + "os" + "os/signal" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + + "github.com/httprunner/funplugin/myexec" + "github.com/rs/zerolog/log" +) + +type timeLog struct { + UTCTimeStr string `json:"utc_time_str"` + UTCTime int64 `json:"utc_time"` + LiveTimeStr string `json:"live_time_str"` + LiveTime int64 `json:"live_time"` + Delay float64 `json:"delay"` +} + +type EndToEndDelay struct { + driver *DriverExt + file *os.File + resultDir string + UUID string `json:"uuid"` + AppName string `json:"appName"` + BundleID string `json:"bundleID"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + Interval int `json:"interval"` // seconds + Duration int `json:"duration"` // seconds + Timelines []timeLog `json:"timelines"` + PerfFile string `json:"perf"` +} + +func (dExt *DriverExt) CollectEndToEndDelay(options ...ActionOption) { + dataOptions := NewActionOptions(options...) + var err error + startTime := time.Now() + resultDir := filepath.Join("endtoenddelay", startTime.Format("2006-01-02 15:04:05")) + + if err = os.MkdirAll(filepath.Join(resultDir, "screenshot"), 0o755); err != nil { + log.Fatal().Err(err).Msg("failed to create result dir") + } + + filename := filepath.Join(resultDir, "log.txt") + f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o755) + if err != nil { + log.Fatal().Err(err).Msg("failed to open file") + } + // write title + f.WriteString("utc_time\tutc_timestamp\tlive_time\tlive_seconds\n") + + if dataOptions.Interval == 0 { + dataOptions.Interval = 5 + } + if dataOptions.Timeout == 0 { + dataOptions.Timeout = 60 + } + + endToEndDelay := &EndToEndDelay{ + driver: dExt, + file: f, + resultDir: resultDir, + Duration: int(dataOptions.Timeout), + Interval: int(dataOptions.Interval), + StartTime: startTime.Format("2006-01-02 15:04:05"), + } + + SntpCheckTime() + + endToEndDelay.Start() + + dExt.cacheStepData.e2eDelay = endToEndDelay.Timelines +} + +func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error { + utcTimeStr := utcTime.Format("2006-01-02 15:04:05") + ocrTexts, err := ete.driver.GetScreenTexts() + if err != nil { + log.Error().Err(err).Msg("get ocr texts failed") + return err + } + + // filter ocr texts with time format + var liveTimeTexts []string + for _, ocrText := range ocrTexts { + if strings.HasPrefix(ocrText.Text, "16") && + len(ocrText.Text) > 8 && + !strings.Contains(ocrText.Text, ":") { + liveTimeTexts = append(liveTimeTexts, ocrText.Text) + } + } + + var liveTimeText string + if len(liveTimeTexts) != 0 { + liveTimeText = liveTimeTexts[0] + } else { + log.Warn().Msg("no time text found") + return nil + } + + if len(liveTimeText) < 13 { + for (13 - len(liveTimeText)) > 0 { + liveTimeText += "0" + } + } + liveTimeInt, err := strconv.Atoi(liveTimeText) + if err != nil { + liveTimeInt = 0 + } + liveTimeSInt, err := strconv.Atoi(liveTimeText[:10]) + if err != nil { + liveTimeSInt = 0 + } + liveTimeNSInt, err := strconv.Atoi(liveTimeText[10:13]) + if err != nil { + liveTimeNSInt = 0 + } + liveTimeStr := time.Unix(int64(liveTimeSInt), int64(liveTimeNSInt*1000*1000)).Format("2006-01-02 15:04:05") + line := fmt.Sprintf("%s\t%d\t%s\t%s\n", + utcTimeStr, utcTime.UnixMicro(), liveTimeStr, liveTimeText) + log.Info(). + Str("utcTime", utcTimeStr). + Int64("utcTimeInt", utcTime.UnixMilli()). + Str("liveTime", liveTimeStr). + Int64("liveTimeInt", int64(liveTimeInt)). + Float64("delay", float64(utcTime.UnixMilli()-int64(liveTimeInt))/1000). + Msg("log live time") + if _, err := ete.file.WriteString(line); err != nil { + log.Error().Err(err).Str("line", line).Msg("write timeseries failed") + } + ete.Timelines = append(ete.Timelines, timeLog{ + UTCTimeStr: utcTimeStr, + UTCTime: utcTime.UnixMilli(), + LiveTimeStr: liveTimeStr, + LiveTime: int64(liveTimeInt), + Delay: float64(utcTime.UnixMilli()-int64(liveTimeInt)) / 1000, + }) + return nil +} + +func (ete *EndToEndDelay) Start() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGTERM, syscall.SIGINT) + timer := time.NewTimer(time.Duration(ete.Duration) * time.Second) + for { + select { + case <-timer.C: + return + case <-c: + return + default: + utcTime := time.Now() + if utcTime.Unix()%int64(ete.Interval) == 0 { + _ = ete.getCurrentLiveTime(utcTime) + } else { + time.Sleep(500 * time.Millisecond) + } + } + } +} + +func SntpCheckTime() { + err := myexec.RunCommand("sudo", "sntp", "-sS", "time.asia.apple.com") + if err != nil { + log.Error().Err(err).Msg("failed to synchronize time using sntp") + } +} diff --git a/hrp/step_mobile_ui.go b/hrp/step_mobile_ui.go index 09a3f194..6cb28be3 100644 --- a/hrp/step_mobile_ui.go +++ b/hrp/step_mobile_ui.go @@ -300,6 +300,15 @@ func (s *StepMobile) VideoCrawler(params map[string]interface{}) *StepMobile { return &StepMobile{step: s.step} } +func (s *StepMobile) EndToEndDelay(options ...uixt.ActionOption) *StepMobile { + s.mobileStep().Actions = append(s.mobileStep().Actions, uixt.MobileAction{ + Method: uixt.ACTION_EndToEndDelay, + Params: nil, + Options: uixt.NewActionOptions(options...), + }) + return &StepMobile{step: s.step} +} + func (s *StepMobile) ScreenShot(options ...uixt.ActionOption) *StepMobile { s.mobileStep().Actions = append(s.mobileStep().Actions, uixt.MobileAction{ Method: uixt.ACTION_ScreenShot, From b30461e90a67c2beb15632c2c4a25b91892984ee Mon Sep 17 00:00:00 2001 From: "huangbin.beal" Date: Tue, 31 Oct 2023 14:43:55 +0800 Subject: [PATCH 09/60] feat: set ocrCluster to default_1600 for live --- examples/uitest/android_follow_live_test.json | 192 ++++++++++++++++++ hrp/pkg/uixt/service_vedem.go | 4 + 2 files changed, 196 insertions(+) create mode 100644 examples/uitest/android_follow_live_test.json diff --git a/examples/uitest/android_follow_live_test.json b/examples/uitest/android_follow_live_test.json new file mode 100644 index 00000000..e1c57a25 --- /dev/null +++ b/examples/uitest/android_follow_live_test.json @@ -0,0 +1,192 @@ +{ + "config": { + "name": "直播_抖音_关注天窗_android", + "variables": { + "device": "${ENV(SerialNumber)}", + "ups": "${ENV(LIVEUPLIST)}" + }, + "android": [ + { + "serial": "$device", + "log_on": true, + "close_popup": true + } + ] + }, + "teststeps": [ + { + "name": "启动抖音", + "android": { + "actions": [ + { + "method": "app_terminate", + "params": "com.ss.android.ugc.aweme" + }, + { + "method": "app_launch", + "params": "com.ss.android.ugc.aweme" + }, + { + "method": "home" + }, + { + "method": "swipe_to_tap_app", + "params": "抖音", + "max_retry_times": 5, + "offset": [ + 0, + -50 + ] + }, + { + "method": "sleep", + "params": 20 + } + ] + } + }, + { + "name": "处理青少年弹窗", + "android": { + "actions": [ + { + "method": "tap_ocr", + "params": "我知道了", + "ignore_NotFoundError": true + } + ] + }, + "validate": [ + { + "check": "ui_ocr", + "assert": "exists", + "expect": "推荐", + "msg": "进入抖音失败" + } + ] + }, + { + "name": "点击关注", + "android": { + "actions": [ + { + "method": "tap_ocr", + "params": "关注", + "index": -1 + }, + { + "method": "sleep", + "params": 10 + } + ] + } + }, + { + "name": "点击直播标签,进入直播间", + "android": { + "actions": [ + { + "method": "swipe_to_tap_texts", + "params": "${split_by_comma($ups)}", + "identifier": "click_live", + "wait_time": 1, + "direction": [ + 0.6, + 0.2, + 0.4, + 0.2 + ], + "offset": [ + 0, + -50 + ] + } + ] + } + }, + { + "name": "等待1分钟", + "android": { + "actions": [ + { + "method": "sleep", + "params": 30 + } + ] + } + }, + { + "name": "上滑进入下一个直播间", + "android": { + "actions": [ + { + "method": "swipe", + "params": [ + 0.9, + 0.9, + 0.9, + 0.1 + ], + "identifier": "slide_in_live" + }, + { + "method": "sleep", + "params": 30 + } + ] + } + }, + { + "name": "再上滑进入下一个直播间", + "android": { + "actions": [ + { + "method": "swipe", + "params": [ + 0.9, + 0.9, + 0.9, + 0.1 + ], + "identifier": "slide_in_live" + }, + { + "method": "sleep", + "params": 30 + } + ] + } + }, + { + "name": "返回主界面,并打开本地时间戳", + "android": { + "actions": [ + { + "method": "home" + }, + { + "method": "swipe_to_tap_app", + "params": "local", + "max_retry_times": 5, + "offset": [ + 0, + -50 + ] + }, + { + "method": "sleep", + "params": 10 + } + ] + }, + "validate": [ + { + "check": "ui_ocr", + "assert": "exists", + "expect": "16", + "msg": "打开本地时间戳失败" + } + ] + } + ] +} \ No newline at end of file diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index e2c4330b..3d7c6e68 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -217,6 +217,10 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, options ...ActionOp for _, uiType := range actionOptions.ScreenShotWithUITypes { bodyWriter.WriteField("uiTypes", uiType) } + + // 临时支持直播使用高精度集群 + bodyWriter.WriteField("ocrCluster", "default_1600") + if actionOptions.ScreenShotWithOCRCluster != "" { bodyWriter.WriteField("ocrCluster", actionOptions.ScreenShotWithOCRCluster) } From dc9310d638f9d8cd16b7351624271f148b47213f Mon Sep 17 00:00:00 2001 From: "huangbin.beal" Date: Tue, 31 Oct 2023 20:46:43 +0800 Subject: [PATCH 10/60] feat: compress screenshot --- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/ext.go | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index e764e622..38b378c2 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.6.2310111458 \ No newline at end of file +v4.3.7.2310312044 \ No newline at end of file diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index 54009453..46b571c2 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -239,7 +239,9 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin return "", errors.Wrap(err, "decode screenshot image failed") } - screenshotPath := filepath.Join(fmt.Sprintf("%s.%s", fileName, format)) + // The default format uses jpeg for compression + screenshotPath := filepath.Join(fmt.Sprintf("%s.%s", fileName, "jpeg")) + // screenshotPath := filepath.Join(fmt.Sprintf("%s.%s", fileName, "jpeg")) file, err := os.Create(screenshotPath) if err != nil { return "", errors.Wrap(err, "create screenshot image file failed") @@ -249,11 +251,15 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin }() switch format { - case "png": + // Convert to jpeg uniformly and compress with a compression rate of 95 + case "jpeg", "png": + jpegOptions := &jpeg.Options{Quality: 95} + err = jpeg.Encode(file, img, jpegOptions) + case "png_import": err = png.Encode(file, img) - case "jpeg": - err = jpeg.Encode(file, img, nil) - case "gif": + // case "jpeg": + // err = jpeg.Encode(file, img, nil) + case "gif_import": err = gif.Encode(file, img, nil) default: return "", fmt.Errorf("unsupported image format: %s", format) From b83c2a38534fd6f872db3f474b75032641acf2ff Mon Sep 17 00:00:00 2001 From: buyuxiang Date: Tue, 14 Nov 2023 20:10:42 +0800 Subject: [PATCH 11/60] remove unused endtoenddelay directory --- hrp/pkg/uixt/live_e2e.go | 41 ++-------------------------------------- 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/hrp/pkg/uixt/live_e2e.go b/hrp/pkg/uixt/live_e2e.go index 51c238cb..6ce4bdb1 100644 --- a/hrp/pkg/uixt/live_e2e.go +++ b/hrp/pkg/uixt/live_e2e.go @@ -1,16 +1,13 @@ package uixt import ( - "fmt" "os" "os/signal" - "path/filepath" "strconv" "strings" "syscall" "time" - "github.com/httprunner/funplugin/myexec" "github.com/rs/zerolog/log" ) @@ -24,36 +21,16 @@ type timeLog struct { type EndToEndDelay struct { driver *DriverExt - file *os.File - resultDir string - UUID string `json:"uuid"` - AppName string `json:"appName"` - BundleID string `json:"bundleID"` StartTime string `json:"startTime"` EndTime string `json:"endTime"` Interval int `json:"interval"` // seconds Duration int `json:"duration"` // seconds Timelines []timeLog `json:"timelines"` - PerfFile string `json:"perf"` } func (dExt *DriverExt) CollectEndToEndDelay(options ...ActionOption) { dataOptions := NewActionOptions(options...) - var err error startTime := time.Now() - resultDir := filepath.Join("endtoenddelay", startTime.Format("2006-01-02 15:04:05")) - - if err = os.MkdirAll(filepath.Join(resultDir, "screenshot"), 0o755); err != nil { - log.Fatal().Err(err).Msg("failed to create result dir") - } - - filename := filepath.Join(resultDir, "log.txt") - f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o755) - if err != nil { - log.Fatal().Err(err).Msg("failed to open file") - } - // write title - f.WriteString("utc_time\tutc_timestamp\tlive_time\tlive_seconds\n") if dataOptions.Interval == 0 { dataOptions.Interval = 5 @@ -64,15 +41,11 @@ func (dExt *DriverExt) CollectEndToEndDelay(options ...ActionOption) { endToEndDelay := &EndToEndDelay{ driver: dExt, - file: f, - resultDir: resultDir, Duration: int(dataOptions.Timeout), Interval: int(dataOptions.Interval), StartTime: startTime.Format("2006-01-02 15:04:05"), } - SntpCheckTime() - endToEndDelay.Start() dExt.cacheStepData.e2eDelay = endToEndDelay.Timelines @@ -122,8 +95,6 @@ func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error { liveTimeNSInt = 0 } liveTimeStr := time.Unix(int64(liveTimeSInt), int64(liveTimeNSInt*1000*1000)).Format("2006-01-02 15:04:05") - line := fmt.Sprintf("%s\t%d\t%s\t%s\n", - utcTimeStr, utcTime.UnixMicro(), liveTimeStr, liveTimeText) log.Info(). Str("utcTime", utcTimeStr). Int64("utcTimeInt", utcTime.UnixMilli()). @@ -131,9 +102,6 @@ func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error { Int64("liveTimeInt", int64(liveTimeInt)). Float64("delay", float64(utcTime.UnixMilli()-int64(liveTimeInt))/1000). Msg("log live time") - if _, err := ete.file.WriteString(line); err != nil { - log.Error().Err(err).Str("line", line).Msg("write timeseries failed") - } ete.Timelines = append(ete.Timelines, timeLog{ UTCTimeStr: utcTimeStr, UTCTime: utcTime.UnixMilli(), @@ -151,8 +119,10 @@ func (ete *EndToEndDelay) Start() { for { select { case <-timer.C: + ete.EndTime = time.Now().Format("2006-01-02 15:04:05") return case <-c: + ete.EndTime = time.Now().Format("2006-01-02 15:04:05") return default: utcTime := time.Now() @@ -164,10 +134,3 @@ func (ete *EndToEndDelay) Start() { } } } - -func SntpCheckTime() { - err := myexec.RunCommand("sudo", "sntp", "-sS", "time.asia.apple.com") - if err != nil { - log.Error().Err(err).Msg("failed to synchronize time using sntp") - } -} From 2b24c83f5fbfc988841c820420bf15d3ca973f32 Mon Sep 17 00:00:00 2001 From: buyuxiang Date: Tue, 14 Nov 2023 20:25:36 +0800 Subject: [PATCH 12/60] fix timestamp recognition error --- hrp/pkg/uixt/live_e2e.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/hrp/pkg/uixt/live_e2e.go b/hrp/pkg/uixt/live_e2e.go index 6ce4bdb1..58a00913 100644 --- a/hrp/pkg/uixt/live_e2e.go +++ b/hrp/pkg/uixt/live_e2e.go @@ -62,11 +62,15 @@ func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error { // filter ocr texts with time format var liveTimeTexts []string for _, ocrText := range ocrTexts { - if strings.HasPrefix(ocrText.Text, "16") && - len(ocrText.Text) > 8 && - !strings.Contains(ocrText.Text, ":") { - liveTimeTexts = append(liveTimeTexts, ocrText.Text) + if len(ocrText.Text) < 10 || strings.Contains(ocrText.Text, ":") { + continue } + // exclude digit(s) recognized as letter(s) + _, errParseInt := strconv.ParseInt(ocrText.Text[:10], 10, 64) + if errParseInt != nil { + continue + } + liveTimeTexts = append(liveTimeTexts, ocrText.Text) } var liveTimeText string From fb01e80153b6c05ab8fecf5fe067f5acc343552f Mon Sep 17 00:00:00 2001 From: "huangbin.beal" Date: Wed, 15 Nov 2023 11:20:27 +0800 Subject: [PATCH 13/60] feat: set ocr timeout to 10s --- hrp/pkg/uixt/service_vedem.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index 3d7c6e68..00e8efee 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -227,6 +227,8 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, options ...ActionOp if actionOptions.Timeout > 0 { bodyWriter.WriteField("timeout", fmt.Sprintf("%v", actionOptions.Timeout)) + } else { + bodyWriter.WriteField("timeout", fmt.Sprintf("%v", 10)) } formWriter, err := bodyWriter.CreateFormFile("image", "screenshot.png") From c690b86f2e12ea2c1a43b2a4f19c9883405b86f9 Mon Sep 17 00:00:00 2001 From: buyuxiang Date: Thu, 23 Nov 2023 16:08:16 +0800 Subject: [PATCH 14/60] update hrp version --- hrp/internal/version/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 38b378c2..7f16698d 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.7.2310312044 \ No newline at end of file +v4.3.7.2311231600 \ No newline at end of file From b41aa6fde66b2dca51dcf8f432c733ee51eab795 Mon Sep 17 00:00:00 2001 From: "huangbin.beal" Date: Wed, 29 Nov 2023 21:08:06 +0800 Subject: [PATCH 15/60] fix: use log file when not found log content --- examples/uitest/android_follow_live_test.json | 130 +------- examples/uitest/demo_android_feed_swipe.json | 309 ++++++++++++------ hrp/internal/env/env.go | 17 +- hrp/pkg/uixt/android_adb_driver.go | 40 ++- hrp/runner.go | 7 + 5 files changed, 263 insertions(+), 240 deletions(-) diff --git a/examples/uitest/android_follow_live_test.json b/examples/uitest/android_follow_live_test.json index e1c57a25..3c50234c 100644 --- a/examples/uitest/android_follow_live_test.json +++ b/examples/uitest/android_follow_live_test.json @@ -25,22 +25,6 @@ { "method": "app_launch", "params": "com.ss.android.ugc.aweme" - }, - { - "method": "home" - }, - { - "method": "swipe_to_tap_app", - "params": "抖音", - "max_retry_times": 5, - "offset": [ - 0, - -50 - ] - }, - { - "method": "sleep", - "params": 20 } ] } @@ -72,121 +56,11 @@ { "method": "tap_ocr", "params": "关注", - "index": -1 - }, - { - "method": "sleep", - "params": 10 + "index": -1, + "identifier": "click_live_new" } ] } - }, - { - "name": "点击直播标签,进入直播间", - "android": { - "actions": [ - { - "method": "swipe_to_tap_texts", - "params": "${split_by_comma($ups)}", - "identifier": "click_live", - "wait_time": 1, - "direction": [ - 0.6, - 0.2, - 0.4, - 0.2 - ], - "offset": [ - 0, - -50 - ] - } - ] - } - }, - { - "name": "等待1分钟", - "android": { - "actions": [ - { - "method": "sleep", - "params": 30 - } - ] - } - }, - { - "name": "上滑进入下一个直播间", - "android": { - "actions": [ - { - "method": "swipe", - "params": [ - 0.9, - 0.9, - 0.9, - 0.1 - ], - "identifier": "slide_in_live" - }, - { - "method": "sleep", - "params": 30 - } - ] - } - }, - { - "name": "再上滑进入下一个直播间", - "android": { - "actions": [ - { - "method": "swipe", - "params": [ - 0.9, - 0.9, - 0.9, - 0.1 - ], - "identifier": "slide_in_live" - }, - { - "method": "sleep", - "params": 30 - } - ] - } - }, - { - "name": "返回主界面,并打开本地时间戳", - "android": { - "actions": [ - { - "method": "home" - }, - { - "method": "swipe_to_tap_app", - "params": "local", - "max_retry_times": 5, - "offset": [ - 0, - -50 - ] - }, - { - "method": "sleep", - "params": 10 - } - ] - }, - "validate": [ - { - "check": "ui_ocr", - "assert": "exists", - "expect": "16", - "msg": "打开本地时间戳失败" - } - ] } ] } \ No newline at end of file diff --git a/examples/uitest/demo_android_feed_swipe.json b/examples/uitest/demo_android_feed_swipe.json index c50ba576..7f8fe3bc 100644 --- a/examples/uitest/demo_android_feed_swipe.json +++ b/examples/uitest/demo_android_feed_swipe.json @@ -1,27 +1,223 @@ { "config": { - "name": "点播_抖音_滑动场景_随机间隔_android", + "name": "直播_抖极_feed卡片_android", "variables": { "device": "${ENV(SerialNumber)}" }, "android": [ { - "serial": "$device" + "serial": "$device", + "log_on": true, + "close_popup": true } ] }, "teststeps": [ { - "name": "启动抖音", + "name": "清理android无关进程", "android": { "actions": [ { "method": "app_terminate", "params": "com.ss.android.ugc.aweme" }, + { + "method": "app_terminate", + "params": "com.ss.android.ugc.aweme.lite" + }, + { + "method": "app_terminate", + "params": "com.smile.gifmaker" + }, + { + "method": "app_terminate", + "params": "com.kuaishou.nebula" + }, + { + "method": "app_terminate", + "params": "com.tencent.mm" + }, + { + "method": "app_terminate", + "params": "com.duowan.kiwi" + }, + { + "method": "app_terminate", + "params": "air.tv.douyu.android" + }, + { + "method": "app_terminate", + "params": "com.xingin.xhs" + }, + { + "method": "app_terminate", + "params": "com.taobao.taobao" + }, + { + "method": "app_terminate", + "params": "tv.danmaku.bili" + }, + { + "method": "app_terminate", + "params": "com.cmcc.cmvideo" + }, + { + "method": "app_terminate", + "params": "com.xunmeng.pinduoduo" + }, + { + "method": "app_terminate", + "params": "com.cctv.yangshipin.app.androidp" + } + ] + } + }, + { + "name": "启动抖音极速版", + "android": { + "actions": [ + { + "method": "app_terminate", + "params": "com.ss.android.ugc.aweme.lite" + }, { "method": "app_launch", - "params": "com.ss.android.ugc.aweme" + "params": "com.ss.android.ugc.aweme.lite" + }, + { + "method": "sleep", + "params": 10 + }, + { + "method": "close_popups", + "options": { + "max_retry_times": 2 + } + } + ] + }, + "validate": [ + { + "check": "ui_foreground_app", + "assert": "equal", + "expect": "com.ss.android.ugc.aweme.lite", + "msg": "app [com.ss.android.ugc.aweme.lite] should be in foreground" + } + ] + }, + { + "name": "处理通讯录弹窗", + "android": { + "actions": [ + { + "method": "tap_ocr", + "params": "拒绝", + "ignore_NotFoundError": true + } + ] + } + }, + { + "name": "处理青少年弹窗", + "android": { + "actions": [ + { + "method": "sleep", + "params": 5 + }, + { + "method": "tap_ocr", + "params": "我知道了", + "ignore_NotFoundError": true + } + ] + } + }, + { + "name": "点击直播标签,进入直播间", + "android": { + "actions": [ + { + "method": "swipe_to_tap_texts", + "params": [ + "看直播开宝箱", + "最高领", + "点击进入直播间" + ], + "identifier": "click_live_new", + "max_retry_times": 40, + "wait_time": 2, + "direction": [ + 0.5, + 0.8, + 0.5, + 0.2 + ], + "scope": [ + 0.1, + 0.5, + 0.9, + 0.9 + ], + "offset": [ + 0, + -100 + ] + } + ] + } + }, + { + "name": "等待30s", + "android": { + "actions": [ + { + "method": "sleep", + "params": 30 + } + ] + } + }, + { + "name": "下滑进入下一个直播间", + "android": { + "actions": [ + { + "method": "swipe", + "params": [ + 0.5, + 0.7, + 0.5, + 0.1 + ], + "identifier": "slide_in_live_new" + }, + { + "method": "sleep", + "params": 30 + } + ] + } + }, + { + "name": "返回主界面,并打开本地时间戳", + "android": { + "actions": [ + { + "method": "app_terminate", + "params": "com.ss.android.ugc.aweme.lite" + }, + { + "method": "home" + }, + { + "method": "swipe_to_tap_app", + "params": "local", + "max_retry_times": 5, + "offset": [ + 0, + -50 + ] }, { "method": "sleep", @@ -31,107 +227,10 @@ }, "validate": [ { - "check": "ui_foreground_app", - "assert": "equal", - "expect": "com.ss.android.ugc.aweme", - "msg": "app [com.ss.android.ugc.aweme] should be in foreground" - } - ] - }, - { - "name": "处理青少年弹窗", - "android": { - "actions": [ - { - "method": "tap_ocr", - "params": "我知道了", - "options": { - "ignore_NotFoundError": true - } - } - ] - } - }, - { - "name": "滑动 Feed 3 次,随机间隔 0-5s", - "android": { - "actions": [ - { - "method": "swipe", - "params": "up", - "options": {} - }, - { - "method": "sleep_random", - "params": [ - 0, - 5 - ] - } - ] - }, - "loops": 3 - }, - { - "name": "滑动 Feed 1 次,随机间隔 5-10s", - "android": { - "actions": [ - { - "method": "swipe", - "params": "up", - "options": {} - }, - { - "method": "sleep_random", - "params": [ - 5, - 10 - ] - } - ] - }, - "loops": 1 - }, - { - "name": "滑动 Feed 10 次,70% 随机间隔 0-5s,30% 随机间隔 5-10s", - "android": { - "actions": [ - { - "method": "swipe", - "params": "up", - "options": {} - }, - { - "method": "sleep_random", - "params": [ - 0, - 5, - 0.7, - 5, - 10, - 0.3 - ] - } - ] - }, - "loops": 10 - }, - { - "name": "exit", - "android": { - "actions": [ - { - "method": "app_terminate", - "params": "com.ss.android.ugc.aweme" - } - ] - }, - "validate": [ - { - "check": "ui_foreground_app", - "assert": "not_equal", - "expect": "com.ss.android.ugc.aweme", - "msg": "app [com.ss.android.ugc.aweme] should not be in foreground" + "check": "ui_ocr", + "assert": "exists", + "expect": "17", + "msg": "打开本地时间戳失败" } ] } diff --git a/hrp/internal/env/env.go b/hrp/internal/env/env.go index 8bb808bf..5168990d 100644 --- a/hrp/internal/env/env.go +++ b/hrp/internal/env/env.go @@ -22,15 +22,18 @@ var ( const ( ResultsDirName = "results" ScreenshotsDirName = "screenshots" + ActionLogDireName = "action_log" ) var ( - RootDir string - ResultsDir string - ResultsPath string - ScreenShotsPath string - StartTime = time.Now() - StartTimeStr = StartTime.Format("20060102150405") + RootDir string + ResultsDir string + ResultsPath string + ScreenShotsPath string + StartTime = time.Now() + StartTimeStr = StartTime.Format("20060102150405") + ActionLogFilePath string + DeviceActionLogFilePath string ) func init() { @@ -43,4 +46,6 @@ func init() { ResultsDir = filepath.Join(ResultsDirName, StartTimeStr) ResultsPath = filepath.Join(RootDir, ResultsDir) ScreenShotsPath = filepath.Join(ResultsPath, ScreenshotsDirName) + ActionLogFilePath = filepath.Join(ResultsDir, ActionLogDireName) + DeviceActionLogFilePath = "/sdcard/Android/data/io.appium.uiautomator2.server/files/hodor" } diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index 64b4d94e..2fc80a57 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -3,6 +3,9 @@ package uixt import ( "bytes" "fmt" + "io/fs" + "io/ioutil" + "path/filepath" "strconv" "strings" "time" @@ -10,7 +13,9 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/httprunner/funplugin/myexec" "github.com/httprunner/httprunner/v4/hrp/internal/code" + "github.com/httprunner/httprunner/v4/hrp/internal/env" "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" ) @@ -448,7 +453,40 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) { } content := ad.logcat.logBuffer.String() log.Info().Str("logcat content", content).Msg("display logcat content") - return ConvertPoints(content), nil + pointRes := ConvertPoints(content) + // 没有解析到打点日志,走兜底逻辑 + if len(pointRes) == 0 { + logFilePathPrefix := fmt.Sprintf("%v/data", env.ActionLogFilePath) + files := []string{} + myexec.RunCommand("adb", "pull", env.DeviceActionLogFilePath, env.ActionLogFilePath) + err = filepath.Walk(env.ActionLogFilePath, func(path string, info fs.FileInfo, err error) error { + // 只是需要日志文件 + if ok := strings.Contains(path, logFilePathPrefix); ok { + files = append(files, path) + } + return nil + }) + + // 先保持原有状态码不变,这里不return error + if err != nil { + log.Error().Err(err).Msg("read log file fail") + return pointRes, nil + } + + if len(files) != 1 { + log.Error().Err(err).Msg("log file count error") + return pointRes, nil + } + + data, err := ioutil.ReadFile(files[0]) + if err != nil { + log.Info().Msg("read File error") + return pointRes, nil + } + + pointRes = ConvertPoints(string(data)) + } + return pointRes, nil } func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) { diff --git a/hrp/runner.go b/hrp/runner.go index b68a5208..986dc931 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -18,6 +18,7 @@ import ( "github.com/gorilla/websocket" "github.com/httprunner/funplugin" + "github.com/httprunner/funplugin/myexec" "github.com/jinzhu/copier" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -25,6 +26,7 @@ import ( "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/code" + "github.com/httprunner/httprunner/v4/hrp/internal/env" "github.com/httprunner/httprunner/v4/hrp/internal/sdk" "github.com/httprunner/httprunner/v4/hrp/internal/version" "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" @@ -523,6 +525,11 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) error { config := r.caseRunner.testCase.Config log.Info().Str("testcase", config.Name).Msg("run testcase start") + // 安卓系统删除打点日志文件 + if r.caseRunner.testCase.Config.Android != nil { + myexec.RunCommand("adb", "-s", r.caseRunner.testCase.Config.Android[0].SerialNumber, "shell", "rm", "-r", env.DeviceActionLogFilePath) + } + // update config variables with given variables r.InitWithParameters(givenVars) From 7da433285b6852e5a0ea1d818e711b776a1be229 Mon Sep 17 00:00:00 2001 From: "huangbin.beal" Date: Thu, 30 Nov 2023 11:12:18 +0800 Subject: [PATCH 16/60] chore: add log for action log file --- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/android_adb_driver.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 7f16698d..c776583b 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.7.2311231600 \ No newline at end of file +v4.3.7.2311301111 \ No newline at end of file diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index 2fc80a57..c20ce648 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -456,6 +456,7 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) { pointRes := ConvertPoints(content) // 没有解析到打点日志,走兜底逻辑 if len(pointRes) == 0 { + log.Info().Msg("action log is null, use action file >>>") logFilePathPrefix := fmt.Sprintf("%v/data", env.ActionLogFilePath) files := []string{} myexec.RunCommand("adb", "pull", env.DeviceActionLogFilePath, env.ActionLogFilePath) From e5bf385aa933b83fc3fb8a03b5eeda2e6f617e1e Mon Sep 17 00:00:00 2001 From: "huangbin.beal" Date: Thu, 30 Nov 2023 12:05:23 +0800 Subject: [PATCH 17/60] fix: device serial in action log file --- hrp/pkg/uixt/android_adb_driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index c20ce648..790f4544 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -459,7 +459,7 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) { log.Info().Msg("action log is null, use action file >>>") logFilePathPrefix := fmt.Sprintf("%v/data", env.ActionLogFilePath) files := []string{} - myexec.RunCommand("adb", "pull", env.DeviceActionLogFilePath, env.ActionLogFilePath) + myexec.RunCommand("adb", "-s", ad.adbClient.Serial(), "pull", env.DeviceActionLogFilePath, env.ActionLogFilePath) err = filepath.Walk(env.ActionLogFilePath, func(path string, info fs.FileInfo, err error) error { // 只是需要日志文件 if ok := strings.Contains(path, logFilePathPrefix); ok { From 95fa434d2c542ff0860785285253df362d518b70 Mon Sep 17 00:00:00 2001 From: buyuxiang Date: Thu, 30 Nov 2023 19:07:54 +0800 Subject: [PATCH 18/60] fix: updateData isReplace -> replace --- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/action.go | 4 ++-- hrp/pkg/uixt/android_adb_driver.go | 11 ++++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 96a9a9b4..0c444e75 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.6.2310201510 +v4.3.6.2311301910 \ No newline at end of file diff --git a/hrp/pkg/uixt/action.go b/hrp/pkg/uixt/action.go index 59e903b8..c15b4406 100644 --- a/hrp/pkg/uixt/action.go +++ b/hrp/pkg/uixt/action.go @@ -300,8 +300,8 @@ func (o *ActionOptions) updateData(data map[string]interface{}) { data["frequency"] = 60 // default frequency } - if _, ok := data["isReplace"]; !ok { - data["isReplace"] = true // default true + if _, ok := data["replace"]; !ok { + data["replace"] = true // default true } // custom options diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index 64b4d94e..e44b0745 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -340,21 +340,30 @@ func (ad *adbDriver) IsAdbKeyBoardInstalled() bool { func (ad *adbDriver) SendKeysByAdbKeyBoard(text string) (err error) { defer func() { // Reset to default, don't care which keyboard was chosen before switch: - _, err = ad.adbClient.RunShellCommand("ime", "reset") + if _, resetErr := ad.adbClient.RunShellCommand("ime", "reset"); resetErr != nil { + log.Error().Err(err).Msg("failed to reset ime") + } }() // Enable ADBKeyBoard from adb if _, err = ad.adbClient.RunShellCommand("ime", "enable", AdbKeyBoardPackageName); err != nil { + log.Error().Err(err).Msg("failed to enable adbKeyBoard") return } // Switch to ADBKeyBoard from adb if _, err = ad.adbClient.RunShellCommand("ime", "set", AdbKeyBoardPackageName); err != nil { + log.Error().Err(err).Msg("failed to set adbKeyBoard") return } time.Sleep(time.Second) // input Quoted text text = strings.ReplaceAll(text, " ", "\\ ") if _, err = ad.adbClient.RunShellCommand("am", "broadcast", "-a", "ADB_INPUT_TEXT", "--es", "msg", text); err != nil { + log.Error().Err(err).Msg("failed to input by adbKeyBoard") + return + } + if _, err = ad.adbClient.RunShellCommand("input", "keyevent", fmt.Sprintf("%d", KCEnter)); err != nil { + log.Error().Err(err).Msg("failed to input keyevent enter") return } time.Sleep(time.Second) From 46410355005304f3994b2d7b12241515889388bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Wed, 11 Oct 2023 14:38:52 +0800 Subject: [PATCH 19/60] fix: the recommended page slides to exit by mistake --- hrp/pkg/uixt/video_crawler.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 314206cc..22534b86 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -149,6 +149,9 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { dExt.cacheStepData.videoCrawler = crawler }() + // flag,仅当 flag 为 false 时,并处于内流时,才执行退出直播间逻辑 + isFeed := true + // loop until target count achieved or timeout // the main loop is feed crawler crawler.timer = time.NewTimer(time.Duration(configs.Timeout) * time.Second) @@ -231,7 +234,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { sleepTime := math.Min(float64(currentVideo.SimulationPlayDuration), float64(currentVideo.RandomPlayDuration)) currentVideo.PlayDuration = int64(sleepTime) } - + isFeed = false fallthrough case VideoType_Live: @@ -279,7 +282,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { log.Info().Msg("exit live room by 10% chance") exitLive = true } - if exitLive && currentVideo.Type == VideoType_Live { + if (!isFeed) && exitLive && currentVideo.Type == VideoType_Live { err = crawler.exitLiveRoom() if err != nil { if errors.Is(err, code.TimeoutError) || errors.Is(err, code.InterruptError) { @@ -287,9 +290,12 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { } log.Error().Err(err).Msg("run live crawler failed, continue") } + } else { + isFeed = false } default: + isFeed = true // 点播 || 图文 || 广告 || etc. crawler.FeedCount++ log.Info().Interface("video", currentVideo).Msg(FOUND_FEED_SUCCESS) From b9827c0c6ef799f72330e57259d3e4e28d8f0799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Fri, 20 Oct 2023 15:42:35 +0800 Subject: [PATCH 20/60] fix: adjust access probability to 50% --- hrp/pkg/uixt/video_crawler.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 22534b86..b5d064c6 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -208,9 +208,9 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { log.Info().Interface("video", currentVideo). Msg("live count achieved, skip entering live room") skipEnterLive = true - } else if rand.Float64() <= 0.10 { - // 10% chance skip entering live room - log.Info().Msg("skip entering preview live by 10% chance") + } else if rand.Float64() <= 0.50 { + // 50% chance skip entering live room + log.Info().Msg("skip entering preview live by 50% chance") skipEnterLive = true } From f98abbda7fe9b7cd83231aaf66571a3c76684ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Thu, 16 Nov 2023 11:07:19 +0800 Subject: [PATCH 21/60] fix: adjust the probability of entering the broadcast room --- hrp/pkg/uixt/video_crawler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index b5d064c6..9e2d88a9 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -277,7 +277,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { log.Info().Interface("live", currentVideo). Msg("live count achieved, exit live room") exitLive = true - } else if rand.Float64() <= 0.10 { + } else if rand.Float64() <= 0.40 { // 10% chance exit live room log.Info().Msg("exit live room by 10% chance") exitLive = true From 9fa3ae52f6cf05c3e3bc61dc2c7c5f1c8120d033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Thu, 4 Jan 2024 11:26:22 +0800 Subject: [PATCH 22/60] fix: if entry from the preview to the main stream fails, block the use of the exit live room feature --- hrp/pkg/uixt/video_crawler.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 9e2d88a9..f96a961c 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -202,14 +202,15 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { switch currentVideo.Type { case VideoType_PreviewLive: + isFeed = true // 直播预览流 var skipEnterLive bool if crawler.isLiveTargetAchieved() { log.Info().Interface("video", currentVideo). Msg("live count achieved, skip entering live room") skipEnterLive = true - } else if rand.Float64() <= 0.50 { - // 50% chance skip entering live room + } else if rand.Float64() <= 0.70 { + // 70% chance skip entering live room log.Info().Msg("skip entering preview live by 50% chance") skipEnterLive = true } @@ -234,7 +235,6 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { sleepTime := math.Min(float64(currentVideo.SimulationPlayDuration), float64(currentVideo.RandomPlayDuration)) currentVideo.PlayDuration = int64(sleepTime) } - isFeed = false fallthrough case VideoType_Live: @@ -277,11 +277,12 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { log.Info().Interface("live", currentVideo). Msg("live count achieved, exit live room") exitLive = true - } else if rand.Float64() <= 0.40 { - // 10% chance exit live room + } else if rand.Float64() <= 0.50 { + // 50% chance exit live room log.Info().Msg("exit live room by 10% chance") exitLive = true } + // isFeed:通过预览流进入内流失败的情况下,防止使用退出直播间逻辑,影响:首次进入内流,至少会消费两个直播间才能退出 if (!isFeed) && exitLive && currentVideo.Type == VideoType_Live { err = crawler.exitLiveRoom() if err != nil { From 9c99664e0007512b6af8438c09a54069fe732099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Thu, 4 Jan 2024 11:38:52 +0800 Subject: [PATCH 23/60] feat: add UserID and FollowerCount fields to video crawler --- hrp/pkg/uixt/video_crawler.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index f96a961c..3f24bc03 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -348,6 +348,9 @@ type Video struct { UserName string `json:"user_name"` // 视频作者 Duration int64 `json:"duration,omitempty"` // 视频时长(ms) Caption string `json:"caption,omitempty"` // 视频文案 + // 作者信息 + UserID string `json:"user_id"` // 作者用户名 + FollowerCount int64 `json:"follower_count"` // 作者粉丝数 // 视频热度数据 ViewCount int64 `json:"view_count,omitempty"` // feed 观看数 LikeCount int64 `json:"like_count,omitempty"` // feed 点赞数 From 72f98766f42fd125d069a69bd3c8921880d3e677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Thu, 4 Jan 2024 11:42:42 +0800 Subject: [PATCH 24/60] update version: v4.3.6.202401041140 --- hrp/internal/version/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 98b734ff..9e3fcbc0 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.8-202401091556 \ No newline at end of file +v4.3.6.202401041140 \ No newline at end of file From d838b70788457634c63de33b371cafff4024c964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Sun, 11 Feb 2024 21:01:14 +0800 Subject: [PATCH 25/60] fix: close edge popup --- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/video_crawler.go | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 9e3fcbc0..6abe9888 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.6.202401041140 \ No newline at end of file +v4.3.8.202401201310 \ No newline at end of file diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 3f24bc03..7fe29cff 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -196,6 +196,19 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { return err } + if crawler.failedCount > 1 && !isFeed { + // enter live room + entryPoint := PointF{ + X: float64(dExt.windowSize.Width / 2), + Y: float64(dExt.windowSize.Height / 2), + } + + log.Info().Msg("tap screen center to close edge popup") + if err := crawler.driverExt.TapAbsXY(entryPoint.X, entryPoint.Y, + WithOffsetRandomRange(-20, 20)); err != nil { + } + } + // retry continue } @@ -209,8 +222,8 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { log.Info().Interface("video", currentVideo). Msg("live count achieved, skip entering live room") skipEnterLive = true - } else if rand.Float64() <= 0.70 { - // 70% chance skip entering live room + } else if rand.Float64() <= 0.50 { + // 50% chance skip entering live room log.Info().Msg("skip entering preview live by 50% chance") skipEnterLive = true } From 3a77578c5ddaf7725b8b416983156eb984bede6f Mon Sep 17 00:00:00 2001 From: "huangbin.beal@163.com" Date: Tue, 27 Feb 2024 15:58:07 +0800 Subject: [PATCH 26/60] fix: return error when testcase count less than 1 --- hrp/internal/version/VERSION | 2 +- hrp/loader.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index c776583b..bbdcf681 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.7.2311301111 \ No newline at end of file +v4.3.7 \ No newline at end of file diff --git a/hrp/loader.go b/hrp/loader.go index ff737c6d..4af517eb 100644 --- a/hrp/loader.go +++ b/hrp/loader.go @@ -53,6 +53,7 @@ func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) { testCasePath := TestCasePath(path) tc, err := testCasePath.ToTestCase() if err != nil { + log.Error().Err(err).Msg("fail to parse test:") return nil } testCases = append(testCases, tc) @@ -63,6 +64,10 @@ func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) { } } + if len(testCases) < 1 { + return nil, errors.New("test case count less than 1 or parse error") + } + log.Info().Int("count", len(testCases)).Msg("load testcases successfully") return testCases, nil } From b6138f12d95f5dc01f98a1892da6c0ec5c6132ef Mon Sep 17 00:00:00 2001 From: "huangbin.beal@163.com" Date: Tue, 27 Feb 2024 16:00:48 +0800 Subject: [PATCH 27/60] chore: change note --- hrp/pkg/uixt/service_vedem.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index 00e8efee..e8e1f51e 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -218,7 +218,7 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, options ...ActionOp bodyWriter.WriteField("uiTypes", uiType) } - // 临时支持直播使用高精度集群 + // 使用高精度集群 bodyWriter.WriteField("ocrCluster", "default_1600") if actionOptions.ScreenShotWithOCRCluster != "" { From 167d58cf7a28abb53f82fa3d747ff6c0e36e48fe Mon Sep 17 00:00:00 2001 From: "huangbin.beal@163.com" Date: Tue, 27 Feb 2024 16:21:35 +0800 Subject: [PATCH 28/60] chore: remove useless note --- hrp/pkg/uixt/ext.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index c7258be6..aa917fee 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -259,8 +259,6 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin err = jpeg.Encode(file, img, jpegOptions) case "png_import": err = png.Encode(file, img) - // case "jpeg": - // err = jpeg.Encode(file, img, nil) case "gif_import": err = gif.Encode(file, img, nil) default: From ec6181d65a483b43ae87233f8926f56e9b62a9b2 Mon Sep 17 00:00:00 2001 From: "huangbin.beal@163.com" Date: Tue, 27 Feb 2024 16:25:05 +0800 Subject: [PATCH 29/60] chore: remove useless note --- hrp/pkg/uixt/ext.go | 1 - 1 file changed, 1 deletion(-) diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index aa917fee..f234cdae 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -243,7 +243,6 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin // The default format uses jpeg for compression screenshotPath := filepath.Join(fmt.Sprintf("%s.%s", fileName, "jpeg")) - // screenshotPath := filepath.Join(fmt.Sprintf("%s.%s", fileName, "jpeg")) file, err := os.Create(screenshotPath) if err != nil { return "", errors.Wrap(err, "create screenshot image file failed") From 4acf9e5deca0bb28df073d142ffb7ee70a4a2c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Mon, 11 Mar 2024 16:45:30 +0800 Subject: [PATCH 30/60] fix: timeout for vedem service --- hrp/pkg/uixt/service_vedem.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index 961dbc36..00d9ec2c 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -220,6 +220,12 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, options ...ActionOp bodyWriter.WriteField("ocrCluster", "highPrecision") + if actionOptions.Timeout > 0 { + bodyWriter.WriteField("timeout", fmt.Sprintf("%v", actionOptions.Timeout)) + } else { + bodyWriter.WriteField("timeout", fmt.Sprintf("%v", 10)) + } + formWriter, err := bodyWriter.CreateFormFile("image", "screenshot.png") if err != nil { err = errors.Wrap(code.CVRequestError, From cc4944b1333cb80637b66837842b6b4fa5db1cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Mon, 18 Mar 2024 14:49:03 +0800 Subject: [PATCH 31/60] fix: adb device parse attributes error --- hrp/pkg/gadb/client.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hrp/pkg/gadb/client.go b/hrp/pkg/gadb/client.go index 1c867dd1..eb6b9d8c 100644 --- a/hrp/pkg/gadb/client.go +++ b/hrp/pkg/gadb/client.go @@ -108,7 +108,7 @@ func (c Client) DeviceList() (devices []*Device, err error) { } fields := strings.Fields(line) - if len(fields) < 5 || len(fields[0]) == 0 { + if len(fields) < 4 || len(fields[0]) == 0 { log.Error().Str("line", line).Msg("get unexpected line") continue } @@ -117,6 +117,9 @@ func (c Client) DeviceList() (devices []*Device, err error) { mapAttrs := map[string]string{} for _, field := range sliceAttrs { split := strings.Split(field, ":") + if len(split) == 1 { + continue + } key, val := split[0], split[1] mapAttrs[key] = val } From bea0a6a2730dfc3f085dccbb1373a52df5dcb3fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Mon, 18 Mar 2024 15:07:47 +0800 Subject: [PATCH 32/60] fix: adb device parse attributes error --- hrp/cmd/adb/devices.go | 6 +++++- hrp/pkg/gadb/device.go | 42 ++++++++++++++++++++++++++++++++---------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/hrp/cmd/adb/devices.go b/hrp/cmd/adb/devices.go index 6640a45e..7aef4d18 100644 --- a/hrp/cmd/adb/devices.go +++ b/hrp/cmd/adb/devices.go @@ -41,7 +41,11 @@ var listAndroidDevicesCmd = &cobra.Command{ if isDetail { fmt.Println(format(d.DeviceInfo())) } else { - fmt.Println(d.Serial(), d.Usb()) + if usb, err := d.Usb(); err != nil { + fmt.Println(d.Serial()) + } else { + fmt.Println(d.Serial(), usb) + } } } return nil diff --git a/hrp/pkg/gadb/device.go b/hrp/pkg/gadb/device.go index 8b9705b6..c9f61602 100644 --- a/hrp/pkg/gadb/device.go +++ b/hrp/pkg/gadb/device.go @@ -107,20 +107,37 @@ func (d *Device) features() (features Features, err error) { return features, nil } -func (d *Device) Product() string { - return d.attrs["product"] +func (d Device) HasAttribute(key string) bool { + _, ok := d.attrs[key] + return ok } -func (d *Device) Model() string { - return d.attrs["model"] +func (d Device) Product() (string, error) { + if d.HasAttribute("product") { + return d.attrs["product"], nil + } + return "", errors.New("does not have attribute: product") } -func (d *Device) Usb() string { - return d.attrs["usb"] +func (d Device) Model() (string, error) { + if d.HasAttribute("model") { + return d.attrs["model"], nil + } + return "", errors.New("does not have attribute: model") } -func (d *Device) transportId() string { - return d.attrs["transport_id"] +func (d *Device) Usb() (string, error) { + if d.HasAttribute("usb") { + return d.attrs["usb"], nil + } + return "", errors.New("does not have attribute: usb") +} + +func (d Device) transportId() (string, error) { + if d.HasAttribute("transport_id") { + return d.attrs["transport_id"], nil + } + return "", errors.New("does not have attribute: transport_id") } func (d *Device) DeviceInfo() map[string]string { @@ -132,8 +149,13 @@ func (d *Device) Serial() string { return d.serial } -func (d *Device) IsUsb() bool { - return d.Usb() != "" +func (d *Device) IsUsb() (bool, error) { + usb, err := d.Usb() + if err != nil { + return false, err + } + + return usb != "", nil } func (d *Device) State() (DeviceState, error) { From 4979d4e1e296d0c854553c05ff98d20d9807ee95 Mon Sep 17 00:00:00 2001 From: "huangbin.beal@163.com" Date: Tue, 19 Mar 2024 11:53:28 +0800 Subject: [PATCH 33/60] fix: ocr cluster --- hrp/pkg/uixt/ext.go | 8 ++------ hrp/pkg/uixt/service_vedem.go | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index f234cdae..b9ad0041 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -4,9 +4,9 @@ import ( "bytes" "fmt" "image" - "image/gif" + _ "image/gif" "image/jpeg" - "image/png" + _ "image/png" "math/rand" "mime" "mime/multipart" @@ -256,10 +256,6 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin case "jpeg", "png": jpegOptions := &jpeg.Options{Quality: 95} err = jpeg.Encode(file, img, jpegOptions) - case "png_import": - err = png.Encode(file, img) - case "gif_import": - err = gif.Encode(file, img, nil) default: return "", fmt.Errorf("unsupported image format: %s", format) } diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index e8e1f51e..86d66dd1 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -219,7 +219,7 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, options ...ActionOp } // 使用高精度集群 - bodyWriter.WriteField("ocrCluster", "default_1600") + bodyWriter.WriteField("ocrCluster", "highPrecision") if actionOptions.ScreenShotWithOCRCluster != "" { bodyWriter.WriteField("ocrCluster", actionOptions.ScreenShotWithOCRCluster) From e9d039b58ba17d426fa796fef999843a6f74714b Mon Sep 17 00:00:00 2001 From: "huangbin.beal@163.com" Date: Tue, 19 Mar 2024 16:33:03 +0800 Subject: [PATCH 34/60] chore: add change log --- docs/CHANGELOG.md | 10 ++++++++++ hrp/internal/version/VERSION | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ed37df31..901e1744 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,15 @@ # Release History +## v4.3.8 (2023-09-19) +- feat: OCR calls use the high-precision cluster interface, and the default timeout is changed from 3s to 10s +- feat: use jpeg to compress screenshots +- feat: increase live broadcast end-to-end collection capabilities +- fix: add file reading and writing methods as a log management logic +- fix: throws an error when the number of use cases is 0 +- fix: fixed the problem that the new version of adb format fails to parse + +**go version** + ## v4.3.7 (2023-09-19) **go version** diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index f59ab889..fdd709db 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.7 +v4.3.8 From 4ca4a2f94613264677530369a90576d822556a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Mon, 25 Mar 2024 11:34:38 +0800 Subject: [PATCH 35/60] fix: failed to compress image for requesting vedm algorithm service --- hrp/cmd/adb/devices.go | 6 ++++- hrp/cmd/run.go | 2 +- hrp/pkg/gadb/client.go | 5 ++++- hrp/pkg/gadb/device.go | 42 ++++++++++++++++++++++++++--------- hrp/pkg/uixt/ext.go | 38 ++++++++++++++++++++++--------- hrp/pkg/uixt/service_vedem.go | 7 +++--- hrp/pkg/uixt/video_crawler.go | 6 ++--- 7 files changed, 76 insertions(+), 30 deletions(-) diff --git a/hrp/cmd/adb/devices.go b/hrp/cmd/adb/devices.go index 6640a45e..7aef4d18 100644 --- a/hrp/cmd/adb/devices.go +++ b/hrp/cmd/adb/devices.go @@ -41,7 +41,11 @@ var listAndroidDevicesCmd = &cobra.Command{ if isDetail { fmt.Println(format(d.DeviceInfo())) } else { - fmt.Println(d.Serial(), d.Usb()) + if usb, err := d.Usb(); err != nil { + fmt.Println(d.Serial()) + } else { + fmt.Println(d.Serial(), usb) + } } } return nil diff --git a/hrp/cmd/run.go b/hrp/cmd/run.go index e042a5b7..cb14a8c3 100644 --- a/hrp/cmd/run.go +++ b/hrp/cmd/run.go @@ -42,7 +42,7 @@ func init() { runCmd.Flags().BoolVarP(&continueOnFailure, "continue-on-failure", "c", false, "continue running next step when failure occurs") runCmd.Flags().BoolVar(&requestsLogOff, "log-requests-off", false, "turn off request & response details logging") runCmd.Flags().BoolVar(&httpStatOn, "http-stat", false, "turn on HTTP latency stat (DNSLookup, TCP Connection, etc.)") - runCmd.Flags().BoolVar(&pluginLogOn, "log-plugin", false, "turn on plugin logging") + runCmd.Flags().BoolVar(&pluginLogOn, "log-plugin", true, "turn on plugin logging") runCmd.Flags().StringVarP(&proxyUrl, "proxy-url", "p", "", "set proxy url") runCmd.Flags().BoolVarP(&saveTests, "save-tests", "s", false, "save tests summary") runCmd.Flags().BoolVarP(&genHTMLReport, "gen-html-report", "g", false, "generate html report") diff --git a/hrp/pkg/gadb/client.go b/hrp/pkg/gadb/client.go index 1c867dd1..eb6b9d8c 100644 --- a/hrp/pkg/gadb/client.go +++ b/hrp/pkg/gadb/client.go @@ -108,7 +108,7 @@ func (c Client) DeviceList() (devices []*Device, err error) { } fields := strings.Fields(line) - if len(fields) < 5 || len(fields[0]) == 0 { + if len(fields) < 4 || len(fields[0]) == 0 { log.Error().Str("line", line).Msg("get unexpected line") continue } @@ -117,6 +117,9 @@ func (c Client) DeviceList() (devices []*Device, err error) { mapAttrs := map[string]string{} for _, field := range sliceAttrs { split := strings.Split(field, ":") + if len(split) == 1 { + continue + } key, val := split[0], split[1] mapAttrs[key] = val } diff --git a/hrp/pkg/gadb/device.go b/hrp/pkg/gadb/device.go index 8b9705b6..c9f61602 100644 --- a/hrp/pkg/gadb/device.go +++ b/hrp/pkg/gadb/device.go @@ -107,20 +107,37 @@ func (d *Device) features() (features Features, err error) { return features, nil } -func (d *Device) Product() string { - return d.attrs["product"] +func (d Device) HasAttribute(key string) bool { + _, ok := d.attrs[key] + return ok } -func (d *Device) Model() string { - return d.attrs["model"] +func (d Device) Product() (string, error) { + if d.HasAttribute("product") { + return d.attrs["product"], nil + } + return "", errors.New("does not have attribute: product") } -func (d *Device) Usb() string { - return d.attrs["usb"] +func (d Device) Model() (string, error) { + if d.HasAttribute("model") { + return d.attrs["model"], nil + } + return "", errors.New("does not have attribute: model") } -func (d *Device) transportId() string { - return d.attrs["transport_id"] +func (d *Device) Usb() (string, error) { + if d.HasAttribute("usb") { + return d.attrs["usb"], nil + } + return "", errors.New("does not have attribute: usb") +} + +func (d Device) transportId() (string, error) { + if d.HasAttribute("transport_id") { + return d.attrs["transport_id"], nil + } + return "", errors.New("does not have attribute: transport_id") } func (d *Device) DeviceInfo() map[string]string { @@ -132,8 +149,13 @@ func (d *Device) Serial() string { return d.serial } -func (d *Device) IsUsb() bool { - return d.Usb() != "" +func (d *Device) IsUsb() (bool, error) { + usb, err := d.Usb() + if err != nil { + return false, err + } + + return usb != "", nil } func (d *Device) State() (DeviceState, error) { diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index 28f09fc9..cb86fff9 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -4,9 +4,9 @@ import ( "bytes" "fmt" "image" - "image/gif" + _ "image/gif" "image/jpeg" - "image/png" + _ "image/png" "math/rand" "mime" "mime/multipart" @@ -190,8 +190,26 @@ func (dExt *DriverExt) takeScreenShot(fileName string) (raw *bytes.Buffer, path } func compressImageBuffer(raw *bytes.Buffer) (compressed *bytes.Buffer, err error) { - // TODO: compress image data - return raw, nil + // 解码原始图像数据 + img, format, err := image.Decode(raw) + if err != nil { + return nil, err + } + + // 创建一个用来保存压缩后数据的buffer + var buf bytes.Buffer + + switch format { + // Convert to jpeg uniformly and compress with a compression rate of 95 + case "jpeg", "png": + jpegOptions := &jpeg.Options{Quality: 95} + err = jpeg.Encode(&buf, img, jpegOptions) + default: + return nil, fmt.Errorf("unsupported image format: %s", format) + } + + // 返回压缩后的图像数据 + return &buf, nil } // saveScreenShot saves image file with file name @@ -207,7 +225,8 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin return "", errors.Wrap(err, "decode screenshot image failed") } - screenshotPath := filepath.Join(fmt.Sprintf("%s.%s", fileName, format)) + // The default format uses jpeg for compression + screenshotPath := filepath.Join(fmt.Sprintf("%s.%s", fileName, "jpeg")) file, err := os.Create(screenshotPath) if err != nil { return "", errors.Wrap(err, "create screenshot image file failed") @@ -217,12 +236,9 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin }() switch format { - case "png": - err = png.Encode(file, img) - case "jpeg": - err = jpeg.Encode(file, img, nil) - case "gif": - err = gif.Encode(file, img, nil) + case "jpeg", "png": + jpegOptions := &jpeg.Options{} + err = jpeg.Encode(file, img, jpegOptions) default: return "", fmt.Errorf("unsupported image format: %s", format) } diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index 00d9ec2c..1b7ede88 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -218,6 +218,7 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, options ...ActionOp bodyWriter.WriteField("uiTypes", uiType) } + // 使用高精度集群 bodyWriter.WriteField("ocrCluster", "highPrecision") if actionOptions.Timeout > 0 { @@ -488,8 +489,8 @@ func (box Box) IsEmpty() bool { func (box Box) IsIdentical(box2 Box) bool { // set the coordinate precision to 1 pixel return box.Point.IsIdentical(box2.Point) && - math.Abs(box.Width-box2.Width) < 1 && - math.Abs(box.Height-box2.Height) < 1 + builtin.IsZeroFloat64(math.Abs(box.Width-box2.Width)) && + builtin.IsZeroFloat64(math.Abs(box.Height-box2.Height)) } func (box Box) Center() PointF { @@ -544,7 +545,7 @@ func (u UIResultMap) FilterUIResults(uiTypes []string) (uiResults UIResults, err return } } - err = errors.Errorf("UI types %v not detected", uiTypes) + err = errors.Wrap(code.CVResultNotFoundError, fmt.Sprintf("UI types %v not detected", uiTypes)) return } diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 7fe29cff..17bcb630 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -171,7 +171,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { // swipe to next feed video log.Info().Msg("swipe to next feed video") swipeStartTime := time.Now() - if err = dExt.SwipeRelative(0.9, 0.8, 0.9, 0.1, WithOffsetRandomRange(-10, 10)); err != nil { + if err = dExt.SwipeRelative(0.85, 0.83-(float64(crawler.failedCount)*0.01), 0.85, 0.1, WithOffsetRandomRange(-10, 10)); err != nil { log.Error().Err(err).Msg("feed swipe up failed") return err } @@ -182,10 +182,10 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { currentVideo, err := crawler.getCurrentVideo() if err != nil || currentVideo.Type == "" { crawler.failedCount++ - if crawler.failedCount >= 10 { + if crawler.failedCount >= 3 { // failed 10 consecutive times return errors.Wrap(code.TrackingGetError, - "get current feed video failed 10 consecutive times") + "get current feed video failed 3 consecutive times") } log.Warn(). Int64("failedCount", crawler.failedCount). From a69ef2d87fdfe075db13ca342376d08ebf738fd0 Mon Sep 17 00:00:00 2001 From: "huangbin.beal@163.com" Date: Mon, 25 Mar 2024 20:34:03 +0800 Subject: [PATCH 36/60] fix: compress image for req --- docs/CHANGELOG.md | 2 +- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/ext.go | 23 +++++++++++++++++++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 901e1744..b6feb0ff 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## v4.3.8 (2023-09-19) +## v4.3.9 (2023-09-19) - feat: OCR calls use the high-precision cluster interface, and the default timeout is changed from 3s to 10s - feat: use jpeg to compress screenshots - feat: increase live broadcast end-to-end collection capabilities diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index fdd709db..3a6c610b 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.8 +v4.3.9 diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index b9ad0041..63f4e67f 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -224,8 +224,27 @@ func (dExt *DriverExt) takeScreenShot(fileName string) (raw *bytes.Buffer, path } func compressImageBuffer(raw *bytes.Buffer) (compressed *bytes.Buffer, err error) { - // TODO: compress image data - return raw, nil + // 解码原始图像数据 + img, format, err := image.Decode(raw) + if err != nil { + return nil, err + } + + // 创建一个用来保存压缩后数据的buffer + var buf bytes.Buffer + + switch format { + // Convert to jpeg uniformly and compress with a compression rate of 95 + case "jpeg", "png": + jpegOptions := &jpeg.Options{Quality: 95} + err = jpeg.Encode(&buf, img, jpegOptions) + default: + return nil, fmt.Errorf("unsupported image format: %s", format) + } + + // 返回压缩后的图像数据 + return &buf, nil + } // saveScreenShot saves image file with file name From 41b32a76a2381a13848b224854e17991ba23b412 Mon Sep 17 00:00:00 2001 From: "huangbin.beal@163.com" Date: Fri, 29 Mar 2024 11:43:05 +0800 Subject: [PATCH 37/60] chore: change log --- docs/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b6feb0ff..54e7546f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,9 @@ # Release History ## v4.3.9 (2023-09-19) +- fix: OCR calls use compressed image + +## v4.3.8 (2023-09-19) - feat: OCR calls use the high-precision cluster interface, and the default timeout is changed from 3s to 10s - feat: use jpeg to compress screenshots - feat: increase live broadcast end-to-end collection capabilities From ecdb2cb9e0427d98a55096c28e5fc07390ba922c Mon Sep 17 00:00:00 2001 From: "huangbin.beal@163.com" Date: Fri, 29 Mar 2024 11:51:30 +0800 Subject: [PATCH 38/60] fix: error deal --- hrp/pkg/uixt/ext.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index 63f4e67f..4fac1419 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -238,6 +238,9 @@ func compressImageBuffer(raw *bytes.Buffer) (compressed *bytes.Buffer, err error case "jpeg", "png": jpegOptions := &jpeg.Options{Quality: 95} err = jpeg.Encode(&buf, img, jpegOptions) + if err != nil { + return nil, err + } default: return nil, fmt.Errorf("unsupported image format: %s", format) } From f6bd3419c0ae062fe9297f8c7a19036cea4951d1 Mon Sep 17 00:00:00 2001 From: "huangbin.beal@163.com" Date: Mon, 1 Apr 2024 16:49:16 +0800 Subject: [PATCH 39/60] fix: jpeg options in save screenshot --- hrp/pkg/uixt/ext.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index 4fac1419..f283c7f9 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -276,7 +276,7 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin switch format { // Convert to jpeg uniformly and compress with a compression rate of 95 case "jpeg", "png": - jpegOptions := &jpeg.Options{Quality: 95} + jpegOptions := &jpeg.Options{} err = jpeg.Encode(file, img, jpegOptions) default: return "", fmt.Errorf("unsupported image format: %s", format) From b5136ff1f19b00dea828eef0f17f0d7064b59aac Mon Sep 17 00:00:00 2001 From: "huangbin.beal@163.com" Date: Mon, 1 Apr 2024 16:58:46 +0800 Subject: [PATCH 40/60] chore: remove useless code --- hrp/pkg/uixt/ext.go | 1 - 1 file changed, 1 deletion(-) diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index f283c7f9..28eea07c 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -274,7 +274,6 @@ func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (strin }() switch format { - // Convert to jpeg uniformly and compress with a compression rate of 95 case "jpeg", "png": jpegOptions := &jpeg.Options{} err = jpeg.Encode(file, img, jpegOptions) From 93dc17b8deb35ea15db25412522a986c586d7843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E5=B8=85?= Date: Mon, 29 Apr 2024 08:06:38 +0000 Subject: [PATCH 41/60] chore: identifier * chore: identifier * chore: identifier https://code.byted.org/iesqa/httprunner/merge_requests/35 --- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/service_vedem.go | 1 + hrp/step.go | 1 + hrp/step_mobile_ui.go | 14 ++++++++++++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 3a6c610b..d1e4171f 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.9 +v4.3.10 diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index 86d66dd1..186296c3 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -417,6 +417,7 @@ func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *S screenResult.Texts = imageResult.OCRResult.ToOCRTexts() screenResult.UploadedURL = imageResult.URL screenResult.Icons = imageResult.UIResult + screenResult.Video = &Video{LiveType: imageResult.LiveType} if actionOptions.ScreenShotWithClosePopups && imageResult.CPResult != nil { screenResult.Popup = &PopupInfo{ diff --git a/hrp/step.go b/hrp/step.go index 88641370..04a21211 100644 --- a/hrp/step.go +++ b/hrp/step.go @@ -16,6 +16,7 @@ const ( type StepResult struct { Name string `json:"name" yaml:"name"` // step name + Identifier string `json:"identifier" yaml:"identifier"` // step identifier StartTime int64 `json:"start_time" yaml:"time"` // step start time StepType StepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous Success bool `json:"success" yaml:"success"` // step execution result diff --git a/hrp/step_mobile_ui.go b/hrp/step_mobile_ui.go index 6cb28be3..12b49ee7 100644 --- a/hrp/step_mobile_ui.go +++ b/hrp/step_mobile_ui.go @@ -599,8 +599,22 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err "osType": osType, }) + identifer := mobileStep.Identifier + if mobileStep.Options != nil && identifer == "" { + identifer = mobileStep.Options.Identifier + } + if len(mobileStep.Actions) != 0 && identifer == "" { + for _, action := range mobileStep.Actions { + if action.Identifier != "" { + identifer = action.Identifier + break + } + } + } + stepResult = &StepResult{ Name: step.Name, + Identifier: identifer, StepType: StepType(osType), Success: false, ContentSize: 0, From 3b4367cac4c7d5069d05a5411dbcdd43d9cc84df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Fri, 10 May 2024 07:02:41 +0000 Subject: [PATCH 42/60] Feat/yuhongzheng/pre auto install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: fix rotate tap swipe error * fix: default input frequency from 60 to 10 * fix: error getting window size during screen rotation * fix: kuake input unicode error * feat: android input by appium ime * feat: android swipe and tap with duration * fix: format import file * fix: format import file * feat: 新增按控件点击,获取设备应用,修改日志获取 * feat: 新增ui2控件点击 * fix: format file * fix: format file * Merge branch 'video-release' into 'feat/yuhongzheng/pre_auto_install' * merge * Merge branch 'feat/yuhongzheng/pre_auto_install' of… * fix: close reader * Merge branch 'video-release' into 'feat/yuhongzheng/pre_auto_install' * fix: format code * Merge branch 'feat/yuhongzheng/pre_auto_install' of… * fix: test send key https://code.byted.org/iesqa/httprunner/merge_requests/34 --- go.mod | 14 +- go.sum | 20 +++ hrp/internal/builtin/utils.go | 7 + hrp/internal/version/VERSION | 3 +- hrp/pkg/gadb/device.go | 66 +++++-- hrp/pkg/gadb/device_test.go | 50 +++++- hrp/pkg/uixt/action.go | 7 +- hrp/pkg/uixt/android_adb_driver.go | 255 +++++++++++++++++++++++++--- hrp/pkg/uixt/android_device.go | 89 ++++++---- hrp/pkg/uixt/android_layout.go | 62 +++++++ hrp/pkg/uixt/android_test.go | 89 ++++++++-- hrp/pkg/uixt/android_uia2_driver.go | 187 +++++++++++++++++--- hrp/pkg/uixt/interface.go | 11 ++ hrp/pkg/uixt/ios_driver.go | 20 ++- hrp/pkg/uixt/ios_test.go | 34 +++- hrp/pkg/uixt/swipe.go | 23 ++- hrp/pkg/uixt/tap.go | 31 +++- hrp/pkg/utf7/decoder.go | 149 ++++++++++++++++ hrp/pkg/utf7/encoder.go | 91 ++++++++++ hrp/pkg/utf7/utf7.go | 34 ++++ 20 files changed, 1108 insertions(+), 134 deletions(-) create mode 100644 hrp/pkg/uixt/android_layout.go create mode 100644 hrp/pkg/utf7/decoder.go create mode 100644 hrp/pkg/utf7/encoder.go create mode 100644 hrp/pkg/utf7/utf7.go diff --git a/go.mod b/go.mod index 23cc3089..1cc8d8c4 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,9 @@ require ( github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.4 gocv.io/x/gocv v0.32.1 - golang.org/x/net v0.14.0 + golang.org/x/net v0.20.0 golang.org/x/oauth2 v0.8.0 + golang.org/x/text v0.14.0 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 @@ -47,10 +48,12 @@ require ( github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-plugin v1.4.10 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/incu6us/goimports-reviser/v2 v2.5.3 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -67,17 +70,20 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.5.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/tools v0.17.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + mvdan.cc/gofumpt v0.6.0 // indirect ) // replace github.com/httprunner/funplugin => ../funplugin diff --git a/go.sum b/go.sum index 19e9da71..40bce52f 100644 --- a/go.sum +++ b/go.sum @@ -146,6 +146,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -178,6 +180,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/incu6us/goimports-reviser/v2 v2.5.3 h1:DzvFl1+qOIDukqN8vMM/10MQswFQywUdwXxsjuowxlc= +github.com/incu6us/goimports-reviser/v2 v2.5.3/go.mod h1:P18aXhQaED7izHIP9IPI9PqEs7Y7D9okq71Q8Y8yHN4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= @@ -290,6 +294,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= @@ -369,6 +375,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -402,6 +410,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -420,6 +430,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -468,6 +480,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -479,6 +493,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -522,6 +538,8 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -638,6 +656,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= +mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 918530b3..9f1b24d5 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -483,3 +483,10 @@ func ConvertToStringSlice(val interface{}) ([]string, error) { } return nil, fmt.Errorf("invalid type for conversion to []string") } + +func GetCurrentDay() string { + now := time.Now() + // 格式化日期为 yyyyMMdd + formattedDate := now.Format("20060102") + return formattedDate +} diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index d1e4171f..42cdebb8 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1,2 @@ -v4.3.10 +v4.5.0 + diff --git a/hrp/pkg/gadb/device.go b/hrp/pkg/gadb/device.go index c9f61602..ead9227d 100644 --- a/hrp/pkg/gadb/device.go +++ b/hrp/pkg/gadb/device.go @@ -107,25 +107,38 @@ func (d *Device) features() (features Features, err error) { return features, nil } -func (d Device) HasAttribute(key string) bool { +func (d *Device) HasAttribute(key string) bool { _, ok := d.attrs[key] return ok } -func (d Device) Product() (string, error) { +func (d *Device) Product() (string, error) { if d.HasAttribute("product") { return d.attrs["product"], nil } return "", errors.New("does not have attribute: product") } -func (d Device) Model() (string, error) { +func (d *Device) Model() (string, error) { if d.HasAttribute("model") { return d.attrs["model"], nil } return "", errors.New("does not have attribute: model") } +func (d *Device) Brand() (string, error) { + if d.HasAttribute("brand") { + return d.attrs["brand"], nil + } + brand, err := d.RunShellCommand("getprop", "ro.product.brand") + brand = strings.TrimSpace(brand) + if err != nil { + return "", errors.New("does not have attribute: brand") + } + d.attrs["brand"] = brand + return brand, nil +} + func (d *Device) Usb() (string, error) { if d.HasAttribute("usb") { return d.attrs["usb"], nil @@ -133,7 +146,7 @@ func (d *Device) Usb() (string, error) { return "", errors.New("does not have attribute: usb") } -func (d Device) transportId() (string, error) { +func (d *Device) transportId() (string, error) { if d.HasAttribute("transport_id") { return d.attrs["transport_id"], nil } @@ -524,7 +537,7 @@ func (d *Device) Pull(remotePath string, dest io.Writer) (err error) { return } -func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) { +func (d *Device) installViaABBExec(apk io.ReadSeeker, args ...string) (raw []byte, err error) { var ( tp transport filesize int64 @@ -537,8 +550,11 @@ func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) { return nil, err } defer func() { _ = tp.Close() }() - - cmd := fmt.Sprintf("abb_exec:package\x00install\x00-t\x00-S\x00%d", filesize) + cmd := "abb_exec:package\x00install\x00-t" + for _, arg := range args { + cmd += "\x00" + arg + } + cmd += fmt.Sprintf("\x00-S\x00%d", filesize) if err = tp.SendWithCheck(cmd); err != nil { return nil, err } @@ -555,7 +571,7 @@ func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) { return } -func (d *Device) InstallAPK(apk io.ReadSeeker) (string, error) { +func (d *Device) InstallAPK(apk io.ReadSeeker, args ...string) (string, error) { haserr := func(ret string) bool { return strings.Contains(ret, "Failure") } @@ -575,8 +591,9 @@ func (d *Device) InstallAPK(apk io.ReadSeeker) (string, error) { if err != nil { return "", fmt.Errorf("error pushing: %v", err) } - - res, err := d.RunShellCommand("pm", "install", "-f", remote) + args = append([]string{"install"}, args...) + args = append(args, "-f", remote) + res, err := d.RunShellCommand("pm", args...) if err != nil { return "", errors.Wrap(err, "install apk failed") } @@ -591,7 +608,7 @@ func (d *Device) Uninstall(packageName string, keepData ...bool) (string, error) if len(keepData) == 0 { keepData = []bool{false} } - packageName = strings.ReplaceAll(packageName, " ", "") + packageName = strings.TrimSpace(packageName) if len(packageName) == 0 { return "", fmt.Errorf("invalid package name") } @@ -603,6 +620,33 @@ func (d *Device) Uninstall(packageName string, keepData ...bool) (string, error) return d.RunShellCommand("pm", args...) } +func (d *Device) ListPackages() ([]string, error) { + args := []string{"list", "packages"} + resRaw, err := d.RunShellCommand("pm", args...) + if err != nil { + return []string{}, err + } + lines := strings.Split(resRaw, "\n") + var packages []string + for _, line := range lines { + packageName := strings.TrimPrefix(line, "package:") + packages = append(packages, packageName) + } + return packages, nil +} + +func (d *Device) IsPackagesInstalled(packageName string) bool { + packages, err := d.ListPackages() + if err != nil { + return false + } + packageName = strings.TrimSpace(packageName) + if len(packageName) == 0 { + return false + } + return builtin.Contains(packages, packageName) +} + func (d *Device) ScreenCap() ([]byte, error) { if d.HasFeature(FeatShellV2) { return d.RunShellCommandV2WithBytes("screencap", "-p") diff --git a/hrp/pkg/gadb/device_test.go b/hrp/pkg/gadb/device_test.go index 50b37175..e1279051 100644 --- a/hrp/pkg/gadb/device_test.go +++ b/hrp/pkg/gadb/device_test.go @@ -60,7 +60,10 @@ func TestDevice_Product(t *testing.T) { for i := range devices { dev := devices[i] - product := dev.Product() + product, err := dev.Product() + if err != nil { + t.Fatal(err) + } t.Log(dev.Serial(), product) } } @@ -70,7 +73,24 @@ func TestDevice_Model(t *testing.T) { for i := range devices { dev := devices[i] - t.Log(dev.Serial(), dev.Model()) + model, err := dev.Model() + if err != nil { + t.Fatal(err) + } + t.Log(dev.Serial(), model) + } +} + +func TestDevice_Brand(t *testing.T) { + setupDevices(t) + + for i := range devices { + dev := devices[i] + brand, err := dev.Brand() + if err != nil { + t.Fatal(err) + } + t.Log(dev.Serial(), brand) } } @@ -79,7 +99,15 @@ func TestDevice_Usb(t *testing.T) { for i := range devices { dev := devices[i] - t.Log(dev.Serial(), dev.Usb(), dev.IsUsb()) + usb, err := dev.Usb() + if err != nil { + t.Fatal(err) + } + isUsb, err := dev.IsUsb() + if err != nil { + t.Fatal(err) + } + t.Log(dev.Serial(), usb, isUsb) } } @@ -315,6 +343,22 @@ func TestDevice_InstallAPK(t *testing.T) { } } +func TestDevice_ListPackages(t *testing.T) { + setupDevices(t) + for _, dev := range devices { + res, err := dev.ListPackages() + if err != nil { + t.Fatal(err) + } + t.Log(res) + installed := dev.IsPackagesInstalled("io.appium.uiautomator2.server") + if err != nil { + t.Fatal(err) + } + t.Log(installed) + } +} + func TestDevice_HasFeature(t *testing.T) { setupDevices(t) diff --git a/hrp/pkg/uixt/action.go b/hrp/pkg/uixt/action.go index c15b4406..6ca6bcce 100644 --- a/hrp/pkg/uixt/action.go +++ b/hrp/pkg/uixt/action.go @@ -297,7 +297,7 @@ func (o *ActionOptions) updateData(data map[string]interface{}) { data["frequency"] = o.Frequency } if _, ok := data["frequency"]; !ok { - data["frequency"] = 60 // default frequency + data["frequency"] = 10 // default frequency } if _, ok := data["replace"]; !ok { @@ -320,6 +320,11 @@ func NewActionOptions(options ...ActionOption) *ActionOptions { return actionOptions } +type TapTextAction struct { + Text string + Options []ActionOption +} + type ActionOption func(o *ActionOptions) func WithCustomOption(key string, value interface{}) ActionOption { diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index 2d552a36..c642a245 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -1,25 +1,32 @@ package uixt import ( + "bufio" "bytes" + "encoding/xml" "fmt" "io/fs" - "io/ioutil" + "os" "path/filepath" + "regexp" "strconv" "strings" "time" + "github.com/httprunner/funplugin/myexec" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/httprunner/funplugin/myexec" "github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/internal/env" "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" + "github.com/httprunner/httprunner/v4/hrp/pkg/utf7" ) -const AdbKeyBoardPackageName = "com.android.adbkeyboard/.AdbIME" +const ( + AdbKeyBoardPackageName = "com.android.adbkeyboard/.AdbIME" + UnicodeImePackageName = "io.appium.settings/.UnicodeIME" +) type adbDriver struct { Driver @@ -91,7 +98,14 @@ func (ad *adbDriver) WindowSize() (size Size, err error) { return Size{Width: width, Height: height}, nil } } - + orientation, err := ad.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("window size get orientation failed, use default orientation") + orientation = OrientationPortrait + } + if orientation != OrientationPortrait { + size.Width, size.Height = size.Height, size.Width + } err = errors.New("physical window size not found by adb") return } @@ -185,6 +199,24 @@ func (ad *adbDriver) StopCamera() (err error) { return } +func (ad *adbDriver) Orientation() (orientation Orientation, err error) { + output, err := ad.adbClient.RunShellCommand("dumpsys", "input", "|", "grep", "'SurfaceOrientation'") + if err != nil { + return + } + re := regexp.MustCompile(`SurfaceOrientation: (\d)`) + matches := re.FindStringSubmatch(output) + if len(matches) > 1 { // 确保找到了匹配项 + if matches[1] == "0" || matches[1] == "2" { + return OrientationPortrait, nil + } else if matches[1] == "1" || matches[1] == "3" { + return OrientationLandscapeLeft, nil + } + } + err = fmt.Errorf("not found SurfaceOrientation value") + return +} + func (ad *adbDriver) Homescreen() (err error) { return ad.PressKeyCode(KCHome, KMEmpty) } @@ -326,6 +358,15 @@ func (ad *adbDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe } func (ad *adbDriver) SendKeys(text string, options ...ActionOption) (err error) { + err = ad.SendUnicodeKeys(text, options...) + if err == nil { + return + } + err = ad.InputText(text, options...) + return +} + +func (ad *adbDriver) InputText(text string, options ...ActionOption) (err error) { // adb shell input text _, err = ad.adbClient.RunShellCommand("input", "text", text) if err != nil { @@ -334,6 +375,36 @@ func (ad *adbDriver) SendKeys(text string, options ...ActionOption) (err error) return nil } +func (ad *adbDriver) SendUnicodeKeys(text string, options ...ActionOption) (err error) { + // If the Unicode IME is not installed, fall back to the old interface. + // There might be differences in the tracking schemes across different phones, and it is pending further verification. + // In release version: without the Unicode IME installed, the test cannot execute. + if !ad.IsUnicodeIMEInstalled() { + return fmt.Errorf("appium unicode ime not installed") + } + currentIme, err := ad.GetIme() + if err != nil { + return + } + if currentIme != UnicodeImePackageName { + defer func() { + _ = ad.SetIme(currentIme) + }() + err = ad.SetIme(UnicodeImePackageName) + if err != nil { + log.Warn().Err(err).Msgf("set Unicode Ime failed") + return + } + } + encodedStr, err := utf7.Encoding.NewEncoder().String(text) + if err != nil { + log.Warn().Err(err).Msgf("encode text with modified utf7 failed") + return + } + err = ad.InputText("\""+strings.ReplaceAll(encodedStr, "\"", "\\\"")+"\"", options...) + return +} + func (ad *adbDriver) IsAdbKeyBoardInstalled() bool { output, err := ad.adbClient.RunShellCommand("ime", "list", "-a") if err != nil { @@ -342,6 +413,14 @@ func (ad *adbDriver) IsAdbKeyBoardInstalled() bool { return strings.Contains(output, AdbKeyBoardPackageName) } +func (ad *adbDriver) IsUnicodeIMEInstalled() bool { + output, err := ad.adbClient.RunShellCommand("ime", "list", "-s") + if err != nil { + return false + } + return strings.Contains(output, UnicodeImePackageName) +} + func (ad *adbDriver) SendKeysByAdbKeyBoard(text string) (err error) { defer func() { // Reset to default, don't care which keyboard was chosen before switch: @@ -404,10 +483,103 @@ func (ad *adbDriver) Screenshot() (raw *bytes.Buffer, err error) { } func (ad *adbDriver) Source(srcOpt ...SourceOption) (source string, err error) { - err = errDriverNotImplemented + _, err = ad.adbClient.RunShellCommand("rm", "-rf", "/sdcard/window_dump.xml") + if err != nil { + return + } + // 高版本报错 ERROR: null root node returned by UiTestAutomationBridge. + _, err = ad.adbClient.RunShellCommand("uiautomator", "dump") + if err != nil { + return + } + source, err = ad.adbClient.RunShellCommand("cat", "/sdcard/window_dump.xml") + if err != nil { + return + } return } +func (ad *adbDriver) sourceTree(srcOpt ...SourceOption) (sourceTree *Hierarchy, err error) { + source, err := ad.Source() + if err != nil { + return + } + sourceTree = new(Hierarchy) + err = xml.Unmarshal([]byte(source), sourceTree) + if err != nil { + return + } + return +} + +func (ad *adbDriver) TapByText(text string, options ...ActionOption) error { + sourceTree, err := ad.sourceTree() + if err != nil { + return err + } + return ad.tapByTextUsingHierarchy(sourceTree, text, options...) +} + +func (ad *adbDriver) tapByTextUsingHierarchy(hierarchy *Hierarchy, text string, options ...ActionOption) error { + bounds := ad.searchNodes(hierarchy.Layout, text, options...) + actionOptions := NewActionOptions(options...) + if len(bounds) == 0 { + if actionOptions.IgnoreNotFoundError { + log.Info().Msg("not found element by text " + text) + return nil + } + return errors.New("not found element by text " + text) + } + for _, bound := range bounds { + width, height := bound.Center() + err := ad.TapFloat(width, height, options...) + if err != nil { + return err + } + } + return nil +} + +func (ad *adbDriver) TapByTexts(actions ...TapTextAction) error { + sourceTree, err := ad.sourceTree() + if err != nil { + return err + } + + for _, action := range actions { + err := ad.tapByTextUsingHierarchy(sourceTree, action.Text, action.Options...) + if err != nil { + return err + } + } + return nil +} + +func (ad *adbDriver) searchNodes(nodes []Layout, text string, options ...ActionOption) []Bounds { + actionOptions := NewActionOptions(options...) + var results []Bounds + for _, node := range nodes { + result := ad.searchNodes(node.Layout, text, options...) + results = append(results, result...) + if actionOptions.Regex { + // regex on, check if match regex + if !regexp.MustCompile(text).MatchString(node.Text) { + continue + } + } else { + // regex off, check if match exactly + if node.Text != text { + ad.searchNodes(node.Layout, text, options...) + continue + } + } + if node.Bounds != nil { + results = append(results, *node.Bounds) + } + } + return results +} + func (ad *adbDriver) AccessibleSource() (source string, err error) { err = errDriverNotImplemented return @@ -435,14 +607,8 @@ func (ad *adbDriver) IsHealthy() (healthy bool, err error) { func (ad *adbDriver) StartCaptureLog(identifier ...string) (err error) { log.Info().Msg("start adb log recording") - - // clear logcat - if _, err = ad.adbClient.RunShellCommand("logcat", "-c"); err != nil { - return err - } - // start logcat - err = ad.logcat.CatchLogcat() + err = ad.logcat.CatchLogcat("iesqaMonitor:V") if err != nil { err = errors.Wrap(code.AndroidCaptureLogError, fmt.Sprintf("start adb log recording failed: %v", err)) @@ -452,17 +618,18 @@ func (ad *adbDriver) StartCaptureLog(identifier ...string) (err error) { } func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) { - log.Info().Msg("stop adb log recording") - err = ad.logcat.Stop() + defer func() { + log.Info().Msg("stop adb log recording") + err = ad.logcat.Stop() + if err != nil { + log.Error().Err(err).Msg("failed to get adb log recording") + } + }() if err != nil { - log.Error().Err(err).Msg("failed to get adb log recording") - err = errors.Wrap(code.AndroidCaptureLogError, - fmt.Sprintf("get adb log recording failed: %v", err)) - return "", err + log.Error().Err(err).Msg("failed to close adb log writer") } - content := ad.logcat.logBuffer.String() - log.Info().Str("logcat content", content).Msg("display logcat content") - pointRes := ConvertPoints(content) + pointRes := ConvertPoints(ad.logcat.logs) + // 没有解析到打点日志,走兜底逻辑 if len(pointRes) == 0 { log.Info().Msg("action log is null, use action file >>>") @@ -476,7 +643,6 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) { } return nil }) - // 先保持原有状态码不变,这里不return error if err != nil { log.Error().Err(err).Msg("read log file fail") @@ -488,13 +654,28 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) { return pointRes, nil } - data, err := ioutil.ReadFile(files[0]) + reader, err := os.Open(files[0]) if err != nil { - log.Info().Msg("read File error") + log.Info().Msg("open File error") + return pointRes, nil + } + defer func() { + _ = reader.Close() + }() + + var lines []string // 创建一个空的字符串数组来存储文件的每一行 + + // 使用 bufio.NewScanner 读取文件 + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + lines = append(lines, scanner.Text()) // 将每行文本添加到字符串数组 + } + + if err := scanner.Err(); err != nil { return pointRes, nil } - pointRes = ConvertPoints(string(data)) + pointRes = ConvertPoints(lines) } return pointRes, nil } @@ -534,6 +715,30 @@ func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) { return AppInfo{}, errors.Wrap(code.MobileUIAssertForegroundAppError, "get foreground app failed") } +func (ad *adbDriver) SetIme(ime string) error { + _, err := ad.adbClient.RunShellCommand("ime", "set", ime) + if err != nil { + return err + } + // even if the shell command has returned, + // as there might be a situation where the input method has not been completely switched yet + // Listen to the following message. + // InputMethodManagerService: onServiceConnected, name:ComponentInfo{io.appium.settings/io.appium.settings.UnicodeIME}, token:android.os.Binder@44f825 + // But there is no such log on Vivo. + time.Sleep(3 * time.Second) + return nil +} + +func (ad *adbDriver) GetIme() (ime string, err error) { + currentIme, err := ad.adbClient.RunShellCommand("settings", "get", "secure", "default_input_method") + if err != nil { + log.Warn().Err(err).Msgf("get default ime failed") + return + } + currentIme = strings.TrimSpace(currentIme) + return currentIme, nil +} + func (ad *adbDriver) AssertForegroundApp(packageName string, activityType ...string) error { log.Debug().Str("package_name", packageName). Strs("activity_type", activityType). diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 84edf057..34764a70 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -1,6 +1,7 @@ package uixt import ( + "bufio" "bytes" "context" "fmt" @@ -22,7 +23,6 @@ var ( AdbServerPort = gadb.AdbServerPort // 5037 UIA2ServerHost = "localhost" UIA2ServerPort = 6790 - DeviceTempPath = "/data/local/tmp" ) const forwardToPrefix = "forward-to-" @@ -276,27 +276,43 @@ func getFreePort() (int, error) { return l.Addr().(*net.TCPAddr).Port, nil } +type LineCallback func(string) + type AdbLogcat struct { - serial string - logBuffer *bytes.Buffer - errs []error - stopping chan struct{} - done chan struct{} - cmd *exec.Cmd + serial string + // logBuffer *bytes.Buffer + errs []error + stopping chan struct{} + done chan struct{} + cmd *exec.Cmd + callback LineCallback + logs []string +} + +func NewAdbLogcatWithCallback(serial string, callback LineCallback) *AdbLogcat { + return &AdbLogcat{ + serial: serial, + // logBuffer: new(bytes.Buffer), + stopping: make(chan struct{}), + done: make(chan struct{}), + callback: callback, + logs: make([]string, 0), + } } func NewAdbLogcat(serial string) *AdbLogcat { return &AdbLogcat{ - serial: serial, - logBuffer: new(bytes.Buffer), - stopping: make(chan struct{}), - done: make(chan struct{}), + serial: serial, + // logBuffer: new(bytes.Buffer), + stopping: make(chan struct{}), + done: make(chan struct{}), + logs: make([]string, 0), } } // CatchLogcatContext starts logcat with timeout context func (l *AdbLogcat) CatchLogcatContext(timeoutCtx context.Context) (err error) { - if err = l.CatchLogcat(); err != nil { + if err = l.CatchLogcat(""); err != nil { return } go func() { @@ -331,7 +347,7 @@ func (l *AdbLogcat) Errors() (err error) { return } -func (l *AdbLogcat) CatchLogcat() (err error) { +func (l *AdbLogcat) CatchLogcat(filter string) (err error) { if l.cmd != nil { log.Warn().Msg("logcat already start") return nil @@ -341,33 +357,43 @@ func (l *AdbLogcat) CatchLogcat() (err error) { if err = myexec.RunCommand("adb", "-s", l.serial, "shell", "logcat", "-c"); err != nil { return } - + args := []string{"-s", l.serial, "logcat", "--format", "time"} + if filter != "" { + args = append(args, "-s", filter) + } // start logcat - l.cmd = myexec.Command("adb", "-s", l.serial, - "logcat", "--format", "time", "-s", "iesqaMonitor:V") - l.cmd.Stderr = l.logBuffer - l.cmd.Stdout = l.logBuffer + l.cmd = myexec.Command("adb", args...) + // l.cmd.Stderr = l.logBuffer + // l.cmd.Stdout = l.logBuffer + reader, err := l.cmd.StdoutPipe() + if err != nil { + return err + } if err = l.cmd.Start(); err != nil { return } + go func() { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := scanner.Text() + if l.callback != nil { + l.callback(line) // Process each line with callback + } else { + l.logs = append(l.logs, line) // Store line if no callback + } + } + }() go func() { <-l.stopping + if e := reader.Close(); e != nil { + log.Error().Err(e).Msg("close logcat reader failed") + } if e := myexec.KillProcessesByGpid(l.cmd); e != nil { log.Error().Err(e).Msg("kill logcat process failed") } l.done <- struct{}{} }() - return -} -func (l *AdbLogcat) BufferedLogcat() (err error) { - // -d: dump the current buffered logcat result and exits - cmd := myexec.Command("adb", "-s", l.serial, "logcat", "-d") - cmd.Stdout = l.logBuffer - cmd.Stderr = l.logBuffer - if err = cmd.Run(); err != nil { - return - } return } @@ -381,8 +407,8 @@ type ExportPoint struct { RunTime int `json:"run_time,omitempty" yaml:"run_time,omitempty"` } -func ConvertPoints(data string) (eps []ExportPoint) { - lines := strings.Split(data, "\n") +func ConvertPoints(lines []string) (eps []ExportPoint) { + log.Info().Msg("ConvertPoints") for _, line := range lines { if strings.Contains(line, "ext") { idx := strings.Index(line, "{") @@ -396,6 +422,7 @@ func ConvertPoints(data string) (eps []ExportPoint) { log.Error().Msg("failed to parse point data") continue } + log.Info().Msg(line) eps = append(eps, p) } } @@ -562,7 +589,7 @@ func (s UiSelectorHelper) Index(index int) UiSelectorHelper { // // For example, to simulate a user click on // the third image that is enabled in a UI screen, you -// could specify a a search criteria where the instance is +// could specify a search criteria where the instance is // 2, the `className(String)` matches the image // widget class, and `enabled(boolean)` is true. // The code would look like this: diff --git a/hrp/pkg/uixt/android_layout.go b/hrp/pkg/uixt/android_layout.go new file mode 100644 index 00000000..b036ee72 --- /dev/null +++ b/hrp/pkg/uixt/android_layout.go @@ -0,0 +1,62 @@ +package uixt + +import ( + "encoding/xml" + "fmt" + "regexp" + "strconv" +) + +type Attributes struct { + Index int `xml:"index,attr"` + Package string `xml:"package,attr"` + Class string `xml:"class,attr"` + Text string `xml:"text,attr"` + ResourceId string `xml:"resource-id,attr"` + Checkable bool `xml:"checkable,attr"` + Checked bool `xml:"checked,attr"` + Clickable bool `xml:"clickable,attr"` + Enabled bool `xml:"enabled,attr"` + Focusable bool `xml:"focusable,attr"` + Focused bool `xml:"focused,attr"` + LongClickable bool `xml:"long-clickable,attr"` + Password bool `xml:"password,attr"` + Scrollable bool `xml:"scrollable,attr"` + Selected bool `xml:"selected,attr"` + Bounds *Bounds `xml:"bounds,attr"` + Displayed bool `xml:"displayed,attr"` +} + +type Hierarchy struct { + XMLName xml.Name `xml:"hierarchy"` + Attributes + Layout []Layout `xml:",any"` +} + +type Layout struct { + Attributes + Layout []Layout `xml:",any"` +} + +type Bounds struct { + X1, Y1, X2, Y2 int +} + +func (b *Bounds) Center() (float64, float64) { + return float64(b.X1+b.X2) / 2, float64(b.Y1+b.Y2) / 2 +} + +func (b *Bounds) UnmarshalXMLAttr(attr xml.Attr) error { + // 正则表达式用于解析格式为"[x1,y1][x2,y2]" + re := regexp.MustCompile(`\[(\d+),(\d+)]\[(\d+),(\d+)]`) + matches := re.FindStringSubmatch(attr.Value) + if matches == nil { + return fmt.Errorf("bounds format is incorrect") + } + // 转换字符串为整数 + b.X1, _ = strconv.Atoi(matches[1]) + b.Y1, _ = strconv.Atoi(matches[2]) + b.X2, _ = strconv.Atoi(matches[3]) + b.Y2, _ = strconv.Atoi(matches[4]) + return nil +} diff --git a/hrp/pkg/uixt/android_test.go b/hrp/pkg/uixt/android_test.go index ffed7d4e..6284fb66 100644 --- a/hrp/pkg/uixt/android_test.go +++ b/hrp/pkg/uixt/android_test.go @@ -6,18 +6,21 @@ import ( "encoding/json" "fmt" "io/ioutil" + "os" + "strings" "testing" "time" ) var ( - uiaServerURL = "http://localhost:6790/wd/hub" + uiaServerURL = "http://forward-to-6790:6790/wd/hub" driverExt *DriverExt ) func setupAndroid(t *testing.T) { device, err := NewAndroidDevice() checkErr(t, err) + device.UIA2 = false driverExt, err = device.NewDriver() checkErr(t, err) } @@ -132,6 +135,18 @@ func TestDriver_Source(t *testing.T) { t.Log(source) } +func TestDriver_TapByText(t *testing.T) { + driver, err := NewUIADriver(nil, uiaServerURL) + if err != nil { + t.Fatal(err) + } + + err = driver.TapByText("安装") + if err != nil { + t.Fatal(err) + } +} + func TestDriver_BatteryInfo(t *testing.T) { driver, err := NewUIADriver(nil, uiaServerURL) if err != nil { @@ -204,7 +219,7 @@ func TestDriver_Swipe(t *testing.T) { t.Fatal(err) } - err = driver.Swipe(400, 1000, 400, 500) + err = driver.Swipe(400, 1000, 400, 500, WithPressDuration(2000)) if err != nil { t.Fatal(err) } @@ -215,6 +230,14 @@ func TestDriver_Swipe(t *testing.T) { } } +func TestDriver_Swipe_Relative(t *testing.T) { + setupAndroid(t) + err := driverExt.SwipeRelative(0.5, 0.7, 0.5, 0.5) + if err != nil { + t.Fatal(err) + } +} + func TestDriver_Drag(t *testing.T) { driver, err := NewUIADriver(nil, uiaServerURL) if err != nil { @@ -235,28 +258,26 @@ func TestDriver_Drag(t *testing.T) { } func TestDriver_SendKeys(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) + setupAndroid(t) + + err := driverExt.Driver.SendKeys("Android\"输入速度测试", WithIdentifier("test")) if err != nil { t.Fatal(err) } - err = driver.SendKeys("abc") - if err != nil { - t.Fatal(err) - } time.Sleep(time.Second * 2) - err = driver.SendKeys("def") - if err != nil { - t.Fatal(err) - } - time.Sleep(time.Second * 2) + //err = driver.SendKeys("def") + //if err != nil { + // t.Fatal(err) + //} + //time.Sleep(time.Second * 2) - err = driver.SendKeys("\\n") + //err = driver.SendKeys("\\n") // err = driver.SendKeys(`\n`, false) - if err != nil { - t.Fatal(err) - } + //if err != nil { + // t.Fatal(err) + //} } func TestDriver_PressBack(t *testing.T) { @@ -421,10 +442,44 @@ func TestDriver_AppTerminate(t *testing.T) { func TestConvertPoints(t *testing.T) { data := "10-09 20:16:48.216 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317808206,\"ext\":\"输入\",\"from\":{\"x\":0.0,\"y\":0.0},\"operation\":\"Gtf-SendKeys\",\"run_time\":627,\"start\":1665317807579,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":0.0,\"y\":0.0}}\n10-09 20:18:22.899 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317902898,\"ext\":\"进入直播间\",\"from\":{\"x\":717.0,\"y\":2117.5},\"operation\":\"Gtf-Tap\",\"run_time\":121,\"start\":1665317902777,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":717.0,\"y\":2117.5}}\n10-09 20:18:32.063 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317912062,\"ext\":\"第一次上划\",\"from\":{\"x\":1437.0,\"y\":2409.9},\"operation\":\"Gtf-Swipe\",\"run_time\":32,\"start\":1665317912030,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":1437.0,\"y\":2409.9}}" - eps := ConvertPoints(data) + + eps := ConvertPoints(strings.Split(data, "\n")) if len(eps) != 3 { t.Fatal() } jsons, _ := json.Marshal(eps) println(fmt.Sprintf("%v", string(jsons))) } + +func TestDriver_ShellInputUnicode(t *testing.T) { + device, _ := NewAndroidDevice() + driver, err := device.NewAdbDriver() + if err != nil { + t.Fatal(err) + } + + err = driver.SendKeys("test中文输入&") + if err != nil { + t.Fatal(err) + } + + raw, err := driver.Screenshot() + if err != nil { + t.Fatal(err) + } + + t.Log(os.WriteFile("s1.png", raw.Bytes(), 0o600)) +} + +func TestTapTexts(t *testing.T) { + setupAndroid(t) + actions := []TapTextAction{ + {Text: "^.*无视风险安装$", Options: []ActionOption{WithTapOffset(100, 0), WithRegex(true), WithIgnoreNotFoundError(true)}}, + {Text: "已了解此应用未经检测.*", Options: []ActionOption{WithTapOffset(-450, 0), WithRegex(true), WithIgnoreNotFoundError(true)}}, + {Text: "^(.*无视风险安装|确定|继续|完成|点击继续安装|继续安装旧版本|替换|安装|授权本次安装|继续安装|重新安装)$", Options: []ActionOption{WithRegex(true), WithIgnoreNotFoundError(true)}}, + } + err := driverExt.Driver.TapByTexts(actions...) + if err != nil { + t.Fatal(err) + } +} diff --git a/hrp/pkg/uixt/android_uia2_driver.go b/hrp/pkg/uixt/android_uia2_driver.go index 5f38556d..acc58b35 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/base64" "encoding/json" + "encoding/xml" "fmt" "net" "net/http" @@ -16,6 +17,7 @@ import ( "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v4/hrp/internal/code" + "github.com/httprunner/httprunner/v4/hrp/pkg/utf7" ) var errDriverNotImplemented = errors.New("driver method not implemented") @@ -224,6 +226,14 @@ func (ud *uiaDriver) WindowSize() (size Size, err error) { return Size{}, err } size = reply.Value.Size + orientation, err := ud.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("window size get orientation failed, use default orientation") + orientation = OrientationPortrait + } + if orientation != OrientationPortrait { + size.Width, size.Height = size.Height, size.Width + } return } @@ -253,6 +263,20 @@ func (ud *uiaDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...K return } +func (ud *uiaDriver) Orientation() (orientation Orientation, err error) { + // [[FBRoute GET:@"/orientation"] respondWithTarget:self action:@selector(handleGetOrientation:)] + var rawResp rawResponse + if rawResp, err = ud.httpGET("/session", ud.sessionId, "/orientation"); err != nil { + return "", err + } + reply := new(struct{ Value Orientation }) + if err = json.Unmarshal(rawResp, reply); err != nil { + return "", err + } + orientation = reply.Value + return +} + func (ud *uiaDriver) Tap(x, y int, options ...ActionOption) error { return ud.TapFloat(float64(x), float64(y), options...) } @@ -268,15 +292,31 @@ func (ud *uiaDriver) TapFloat(x, y float64, options ...ActionOption) (err error) x += actionOptions.getRandomOffset() y += actionOptions.getRandomOffset() - data := map[string]interface{}{ - "x": x, - "y": y, + duration := 100.0 + if actionOptions.PressDuration > 0 { + duration = actionOptions.PressDuration } + data := map[string]interface{}{ + "actions": []interface{}{ + map[string]interface{}{ + "type": "pointer", + "parameters": map[string]string{"pointerType": "touch"}, + "id": "touch", + "actions": []interface{}{ + map[string]interface{}{"type": "pointerMove", "duration": 0, "x": x, "y": y, "origin": "viewport"}, + map[string]interface{}{"type": "pointerDown", "duration": 0, "button": 0}, + map[string]interface{}{"type": "pause", "duration": duration}, + map[string]interface{}{"type": "pointerUp", "duration": 0, "button": 0}, + }, + }, + }, + } + // update data options in post data for extra uiautomator configurations actionOptions.updateData(data) - _, err = ud.httpPOST(data, "/session", ud.sessionId, "appium/tap") - return + _, err = ud.httpPOST(data, "/session", ud.sessionId, "actions/tap") + return err } func (ud *uiaDriver) TouchAndHold(x, y int, second ...float64) (err error) { @@ -358,17 +398,30 @@ func (ud *uiaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...Actio toX += actionOptions.getRandomOffset() toY += actionOptions.getRandomOffset() + duration := 200.0 + if actionOptions.PressDuration > 0 { + duration = actionOptions.PressDuration + } data := map[string]interface{}{ - "startX": fromX, - "startY": fromY, - "endX": toX, - "endY": toY, + "actions": []interface{}{ + map[string]interface{}{ + "type": "pointer", + "parameters": map[string]string{"pointerType": "touch"}, + "id": "touch", + "actions": []interface{}{ + map[string]interface{}{"type": "pointerMove", "duration": 0, "x": fromX, "y": fromY, "origin": "viewport"}, + map[string]interface{}{"type": "pointerDown", "duration": 0, "button": 0}, + map[string]interface{}{"type": "pointerMove", "duration": duration, "x": toX, "y": toY, "origin": "viewport"}, + map[string]interface{}{"type": "pointerUp", "duration": 0, "button": 0}, + }, + }, + }, } // update data options in post data for extra uiautomator configurations actionOptions.updateData(data) - _, err := ud.httpPOST(data, "/session", ud.sessionId, "touch/perform") + _, err := ud.httpPOST(data, "/session", ud.sessionId, "actions/swipe") return err } @@ -415,25 +468,79 @@ func (ud *uiaDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe return } +// SendKeys Android input does not support setting frequency. func (ud *uiaDriver) SendKeys(text string, options ...ActionOption) (err error) { // register(postHandler, new SendKeysToElement("/wd/hub/session/:sessionId/keys")) // https://github.com/appium/appium-uiautomator2-server/blob/master/app/src/main/java/io/appium/uiautomator2/handler/SendKeysToElement.java#L76-L85 actionOptions := NewActionOptions(options...) - data := map[string]interface{}{ - "text": text, - } - // new data options in post data for extra uiautomator configurations - actionOptions.updateData(data) - - _, err = ud.httpPOST(data, "/session", ud.sessionId, "keys") + err = ud.SendUnicodeKeys(text, options...) if err != nil { - // use com.android.adbkeyboard if existed - if ud.IsAdbKeyBoardInstalled() { - err = ud.SendKeysByAdbKeyBoard(text) - } else { - _, err = ud.adbClient.RunShellCommand("input", "text", text) + data := map[string]interface{}{ + "text": text, + } + + // new data options in post data for extra uiautomator configurations + actionOptions.updateData(data) + + _, err = ud.httpPOST(data, "/session", ud.sessionId, "/keys") + } + return +} + +func (ud *uiaDriver) SendUnicodeKeys(text string, options ...ActionOption) (err error) { + // If the Unicode IME is not installed, fall back to the old interface. + // There might be differences in the tracking schemes across different phones, and it is pending further verification. + // In release version: without the Unicode IME installed, the test cannot execute. + if !ud.IsUnicodeIMEInstalled() { + return fmt.Errorf("appium unicode ime not installed") + } + currentIme, err := ud.adbDriver.GetIme() + if err != nil { + return + } + if currentIme != UnicodeImePackageName { + defer func() { + _ = ud.adbDriver.SetIme(currentIme) + }() + err = ud.adbDriver.SetIme(UnicodeImePackageName) + if err != nil { + log.Warn().Err(err).Msgf("set Unicode Ime failed") + return } } + encodedStr, err := utf7.Encoding.NewEncoder().String(text) + if err != nil { + log.Warn().Err(err).Msgf("encode text with modified utf7 failed") + return + } + err = ud.SendActionKey(encodedStr, options...) + return +} + +func (ud *uiaDriver) SendActionKey(text string, options ...ActionOption) (err error) { + actionOptions := NewActionOptions(options...) + var actions []interface{} + for i, c := range text { + actions = append(actions, map[string]interface{}{"type": "keyDown", "value": string(c)}, + map[string]interface{}{"type": "keyUp", "value": string(c)}) + if i != len(text)-1 { + actions = append(actions, map[string]interface{}{"type": "pause", "duration": 40}) + } + } + + data := map[string]interface{}{ + "actions": []interface{}{ + map[string]interface{}{ + "type": "key", + "id": "key", + "actions": actions, + }, + }, + } + + // new data options in post data for extra uiautomator configurations + actionOptions.updateData(data) + _, err = ud.httpPOST(data, "/session", ud.sessionId, "/actions/keys") return } @@ -492,3 +599,39 @@ func (ud *uiaDriver) Source(srcOpt ...SourceOption) (source string, err error) { source = reply.Value return } + +func (ud *uiaDriver) sourceTree(srcOpt ...SourceOption) (sourceTree *Hierarchy, err error) { + source, err := ud.Source() + if err != nil { + return + } + sourceTree = new(Hierarchy) + err = xml.Unmarshal([]byte(source), sourceTree) + if err != nil { + return + } + return +} + +func (ud *uiaDriver) TapByText(text string, options ...ActionOption) error { + sourceTree, err := ud.sourceTree() + if err != nil { + return err + } + return ud.tapByTextUsingHierarchy(sourceTree, text, options...) +} + +func (ud *uiaDriver) TapByTexts(actions ...TapTextAction) error { + sourceTree, err := ud.sourceTree() + if err != nil { + return err + } + + for _, action := range actions { + err := ud.tapByTextUsingHierarchy(sourceTree, action.Text, action.Options...) + if err != nil { + return err + } + } + return nil +} diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index e292d93c..3fccf5ba 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -511,6 +511,10 @@ type WebDriver interface { // since the location service needs some time to update the location data. Location() (Location, error) BatteryInfo() (BatteryInfo, error) + + // WindowSize Return the width and height in portrait mode. + // when getting the window size in wda/ui2/adb, if the device is in landscape mode, + // the width and height will be reversed. WindowSize() (Size, error) Screen() (Screen, error) Scale() (float64, error) @@ -537,6 +541,8 @@ type WebDriver interface { // StopCamera Stops the camera for recording StopCamera() error + Orientation() (orientation Orientation, err error) + // Tap Sends a tap event at the coordinate. Tap(x, y int, options ...ActionOption) error TapFloat(x, y float64, options ...ActionOption) error @@ -583,6 +589,11 @@ type WebDriver interface { // Source Return application elements tree Source(srcOpt ...SourceOption) (string, error) + + TapByText(text string, options ...ActionOption) error + + TapByTexts(actions ...TapTextAction) error + // AccessibleSource Return application elements accessibility tree AccessibleSource() (string, error) diff --git a/hrp/pkg/uixt/ios_driver.go b/hrp/pkg/uixt/ios_driver.go index ea2d305f..9acc164c 100644 --- a/hrp/pkg/uixt/ios_driver.go +++ b/hrp/pkg/uixt/ios_driver.go @@ -207,6 +207,14 @@ func (wd *wdaDriver) WindowSize() (size Size, err error) { } size.Height = size.Height * int(scale) size.Width = size.Width * int(scale) + orientation, err := wd.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("window size get orientation failed, use default orientation") + orientation = OrientationPortrait + } + if orientation != OrientationPortrait { + size.Width, size.Height = size.Height, size.Width + } return } @@ -547,8 +555,8 @@ func (wd *wdaDriver) DragFloat(fromX, fromY, toX, toY float64, options ...Action // update data options in post data for extra WDA configurations actionOptions.updateData(data) - - _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/dragfromtoforduration") + // wda 43 version + _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/drag") return } @@ -751,6 +759,14 @@ func (wd *wdaDriver) Source(srcOpt ...SourceOption) (source string, err error) { return } +func (wd *wdaDriver) TapByText(text string, options ...ActionOption) error { + return errDriverNotImplemented +} + +func (wd *wdaDriver) TapByTexts(actions ...TapTextAction) error { + return errDriverNotImplemented +} + func (wd *wdaDriver) AccessibleSource() (source string, err error) { // [[FBRoute GET:@"/wda/accessibleSource"] respondWithTarget:self action:@selector(handleGetAccessibleSourceCommand:)] // [[FBRoute GET:@"/wda/accessibleSource"].withoutSession diff --git a/hrp/pkg/uixt/ios_test.go b/hrp/pkg/uixt/ios_test.go index 0e6a16f5..fdfdce70 100644 --- a/hrp/pkg/uixt/ios_test.go +++ b/hrp/pkg/uixt/ios_test.go @@ -10,17 +10,23 @@ import ( ) var ( - bundleId = "com.apple.Preferences" - driver WebDriver + bundleId = "com.apple.Preferences" + driver WebDriver + iOSDriverExt *DriverExt ) func setup(t *testing.T) { - device, err := NewIOSDevice() + device, err := NewIOSDevice(WithWDAPort(8700), WithWDAMjpegPort(8800), WithWDALogOn(true)) if err != nil { t.Fatal(err) } - - driver, err = device.NewUSBDriver(nil) + capabilities := NewCapabilities() + capabilities.WithDefaultAlertAction(AlertActionAccept) + driver, err = device.NewUSBDriver(capabilities) + if err != nil { + t.Fatal(err) + } + iOSDriverExt, err = newDriverExt(device, driver, nil) if err != nil { t.Fatal(err) } @@ -267,6 +273,16 @@ func Test_remoteWD_Drag(t *testing.T) { } } +func Test_Relative_Drag(t *testing.T) { + setup(t) + + // err := driver.Drag(200, 300, 200, 500, WithDataPressDuration(0.5)) + err := iOSDriverExt.SwipeRelative(0.5, 0.7, 0.5, 0.5) + if err != nil { + t.Fatal(err) + } +} + func Test_remoteWD_SetPasteboard(t *testing.T) { setup(t) @@ -305,12 +321,14 @@ func Test_remoteWD_GetPasteboard(t *testing.T) { func Test_remoteWD_SendKeys(t *testing.T) { setup(t) - - err := driver.SendKeys("App Store") + driver.StartCaptureLog("hrp_wda_log") + err := driver.SendKeys("", WithIdentifier("test")) + result, _ := driver.StopCaptureLog() // err := driver.SendKeys("App Store", WithFrequency(3)) if err != nil { t.Fatal(err) } + t.Log(result) } func Test_remoteWD_PressButton(t *testing.T) { @@ -374,7 +392,7 @@ func Test_remoteWD_Source(t *testing.T) { // t.Fatal(err) // } - source, err = driver.Source(NewSourceOption().WithScope("AppiumAUT")) + source, err = driver.Source() if err != nil { t.Fatal(err) } diff --git a/hrp/pkg/uixt/swipe.go b/hrp/pkg/uixt/swipe.go index 4c97d212..23d3490e 100644 --- a/hrp/pkg/uixt/swipe.go +++ b/hrp/pkg/uixt/swipe.go @@ -19,17 +19,30 @@ func assertRelative(p float64) bool { func (dExt *DriverExt) SwipeRelative(fromX, fromY, toX, toY float64, options ...ActionOption) error { width := dExt.windowSize.Width height := dExt.windowSize.Height + orientation, err := dExt.Driver.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("swipe from (%v, %v) to (%v, %v) get orientation failed, use default orientation", + fromX, fromY, toX, toY) + orientation = OrientationPortrait + } if !assertRelative(fromX) || !assertRelative(fromY) || !assertRelative(toX) || !assertRelative(toY) { return fmt.Errorf("fromX(%f), fromY(%f), toX(%f), toY(%f) must be less than 1", fromX, fromY, toX, toY) } - - fromX = float64(width) * fromX - fromY = float64(height) * fromY - toX = float64(width) * toX - toY = float64(height) * toY + // 左转和右转都是"LANDSCAPE" + if orientation == OrientationPortrait { + fromX = float64(width) * fromX + fromY = float64(height) * fromY + toX = float64(width) * toX + toY = float64(height) * toY + } else { + fromX = float64(height) * fromX + fromY = float64(width) * fromY + toX = float64(height) * toX + toY = float64(width) * toY + } return dExt.Driver.SwipeFloat(fromX, fromY, toX, toY, options...) } diff --git a/hrp/pkg/uixt/tap.go b/hrp/pkg/uixt/tap.go index a992c94e..f52b5785 100644 --- a/hrp/pkg/uixt/tap.go +++ b/hrp/pkg/uixt/tap.go @@ -2,6 +2,7 @@ package uixt import ( "fmt" + "github.com/rs/zerolog/log" ) func (dExt *DriverExt) TapAbsXY(x, y float64, options ...ActionOption) error { @@ -15,9 +16,19 @@ 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) } - x = x * float64(dExt.windowSize.Width) - y = y * float64(dExt.windowSize.Height) - + orientation, err := dExt.Driver.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("tap (%v, %v) get orientation failed, use default orientation", + x, y) + orientation = OrientationPortrait + } + if orientation == OrientationPortrait { + x = x * float64(dExt.windowSize.Width) + y = y * float64(dExt.windowSize.Height) + } else { + x = x * float64(dExt.windowSize.Height) + y = y * float64(dExt.windowSize.Width) + } return dExt.TapAbsXY(x, y, options...) } @@ -86,7 +97,19 @@ func (dExt *DriverExt) DoubleTapXY(x, y float64) error { if x > 1 || y > 1 { return fmt.Errorf("x, y percentage should be < 1, got x=%v, y=%v", x, y) } - + orientation, err := dExt.Driver.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("tap (%v, %v) get orientation failed, use default orientation", + x, y) + orientation = OrientationPortrait + } + if orientation == OrientationPortrait { + x = x * float64(dExt.windowSize.Width) + y = y * float64(dExt.windowSize.Height) + } else { + x = x * float64(dExt.windowSize.Height) + y = y * float64(dExt.windowSize.Width) + } x = x * float64(dExt.windowSize.Width) y = y * float64(dExt.windowSize.Height) return dExt.Driver.DoubleTapFloat(x, y) diff --git a/hrp/pkg/utf7/decoder.go b/hrp/pkg/utf7/decoder.go new file mode 100644 index 00000000..cfcba8c0 --- /dev/null +++ b/hrp/pkg/utf7/decoder.go @@ -0,0 +1,149 @@ +package utf7 + +import ( + "errors" + "unicode/utf16" + "unicode/utf8" + + "golang.org/x/text/transform" +) + +// ErrInvalidUTF7 means that a transformer encountered invalid UTF-7. +var ErrInvalidUTF7 = errors.New("utf7: invalid UTF-7") + +type decoder struct { + ascii bool +} + +func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + for i := 0; i < len(src); i++ { + ch := src[i] + + if ch < min || ch > max { // Illegal code point in ASCII mode + err = ErrInvalidUTF7 + return + } + + if ch != '&' { + if nDst+1 > len(dst) { + err = transform.ErrShortDst + return + } + + nSrc++ + + dst[nDst] = ch + nDst++ + + d.ascii = true + continue + } + + // Find the end of the Base64 or "&-" segment + start := i + 1 + for i++; i < len(src) && src[i] != '-'; i++ { + if src[i] == '\r' || src[i] == '\n' { // base64 package ignores CR and LF + err = ErrInvalidUTF7 + return + } + } + + if i == len(src) { // Implicit shift ("&...") + if atEOF { + err = ErrInvalidUTF7 + } else { + err = transform.ErrShortSrc + } + return + } + + var b []byte + if i == start { // Escape sequence "&-" + b = []byte{'&'} + d.ascii = true + } else { // Control or non-ASCII code points in base64 + if !d.ascii { // Null shift ("&...-&...-") + err = ErrInvalidUTF7 + return + } + + b = decode(src[start:i]) + d.ascii = false + } + + if len(b) == 0 { // Bad encoding + err = ErrInvalidUTF7 + return + } + + if nDst+len(b) > len(dst) { + d.ascii = true + err = transform.ErrShortDst + return + } + + nSrc = i + 1 + + for _, ch := range b { + dst[nDst] = ch + nDst++ + } + } + + if atEOF { + d.ascii = true + } + + return +} + +func (d *decoder) Reset() { + d.ascii = true +} + +// Extracts UTF-16-BE bytes from base64 data and converts them to UTF-8. +// A nil slice is returned if the encoding is invalid. +func decode(b64 []byte) []byte { + var b []byte + + // Allocate a single block of memory large enough to store the Base64 data + // (if padding is required), UTF-16-BE bytes, and decoded UTF-8 bytes. + // Since a 2-byte UTF-16 sequence may expand into a 3-byte UTF-8 sequence, + // double the space allocation for UTF-8. + if n := len(b64); b64[n-1] == '=' { + return nil + } else if n&3 == 0 { + b = make([]byte, b64Enc.DecodedLen(n)*3) + } else { + n += 4 - n&3 + b = make([]byte, n+b64Enc.DecodedLen(n)*3) + copy(b[copy(b, b64):n], []byte("==")) + b64, b = b[:n], b[n:] + } + + // Decode Base64 into the first 1/3rd of b + n, err := b64Enc.Decode(b, b64) + if err != nil || n&1 == 1 { + return nil + } + + // Decode UTF-16-BE into the remaining 2/3rds of b + b, s := b[:n], b[n:] + j := 0 + for i := 0; i < n; i += 2 { + r := rune(b[i])<<8 | rune(b[i+1]) + if utf16.IsSurrogate(r) { + if i += 2; i == n { + return nil + } + r2 := rune(b[i])<<8 | rune(b[i+1]) + if r = utf16.DecodeRune(r, r2); r == repl { + return nil + } + } else if min <= r && r <= max { + return nil + } + j += utf8.EncodeRune(s[j:], r) + } + return s[:j] +} diff --git a/hrp/pkg/utf7/encoder.go b/hrp/pkg/utf7/encoder.go new file mode 100644 index 00000000..8414d109 --- /dev/null +++ b/hrp/pkg/utf7/encoder.go @@ -0,0 +1,91 @@ +package utf7 + +import ( + "unicode/utf16" + "unicode/utf8" + + "golang.org/x/text/transform" +) + +type encoder struct{} + +func (e *encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + for i := 0; i < len(src); { + ch := src[i] + + var b []byte + if min <= ch && ch <= max { + b = []byte{ch} + if ch == '&' { + b = append(b, '-') + } + + i++ + } else { + start := i + + // Find the next printable ASCII code point + i++ + for i < len(src) && (src[i] < min || src[i] > max) { + i++ + } + + if !atEOF && i == len(src) { + err = transform.ErrShortSrc + return + } + + b = encode(src[start:i]) + } + + if nDst+len(b) > len(dst) { + err = transform.ErrShortDst + return + } + + nSrc = i + + for _, ch := range b { + dst[nDst] = ch + nDst++ + } + } + + return +} + +func (e *encoder) Reset() {} + +// Converts string s from UTF-8 to UTF-16-BE, encodes the result as base64, +// removes the padding, and adds UTF-7 shifts. +func encode(s []byte) []byte { + // len(s) is sufficient for UTF-8 to UTF-16 conversion if there are no + // control code points (see table below). + b := make([]byte, 0, len(s)+4) + for len(s) > 0 { + r, size := utf8.DecodeRune(s) + if r > utf8.MaxRune { + r, size = utf8.RuneError, 1 // Bug fix (issue 3785) + } + s = s[size:] + if r1, r2 := utf16.EncodeRune(r); r1 != repl { + b = append(b, byte(r1>>8), byte(r1)) + r = r2 + } + b = append(b, byte(r>>8), byte(r)) + } + + // Encode as base64 + n := b64Enc.EncodedLen(len(b)) + 2 + b64 := make([]byte, n) + b64Enc.Encode(b64[1:], b) + + // Strip padding + n -= 2 - (len(b)+2)%3 + b64 = b64[:n] + + // Add UTF-7 shifts + b64[0] = '&' + b64[n-1] = '-' + return b64 +} diff --git a/hrp/pkg/utf7/utf7.go b/hrp/pkg/utf7/utf7.go new file mode 100644 index 00000000..b9dd9623 --- /dev/null +++ b/hrp/pkg/utf7/utf7.go @@ -0,0 +1,34 @@ +// Package utf7 implements modified UTF-7 encoding defined in RFC 3501 section 5.1.3 +package utf7 + +import ( + "encoding/base64" + + "golang.org/x/text/encoding" +) + +const ( + min = 0x20 // Minimum self-representing UTF-7 value + max = 0x7E // Maximum self-representing UTF-7 value + + repl = '\uFFFD' // Unicode replacement code point +) + +var b64Enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,") + +type enc struct{} + +func (e enc) NewDecoder() *encoding.Decoder { + return &encoding.Decoder{ + Transformer: &decoder{true}, + } +} + +func (e enc) NewEncoder() *encoding.Encoder { + return &encoding.Encoder{ + Transformer: &encoder{}, + } +} + +// Encoding is the modified UTF-7 encoding. +var Encoding encoding.Encoding = enc{} From 118f9cc1faa6d32902fff38e214482746980ce4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Mon, 20 May 2024 14:33:48 +0800 Subject: [PATCH 43/60] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dforward=E4=B8=8D?= =?UTF-8?q?=E9=87=8A=E6=94=BE=EF=BC=8C=E4=B8=80=E7=9B=B4=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E6=96=B0=E7=9A=84forward=EF=BC=8C=E8=80=97=E5=B0=BD=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=8F=A5=E6=9F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hrp/internal/builtin/utils.go | 15 +++++++++++++++ hrp/internal/version/VERSION | 3 +-- hrp/pkg/gadb/device.go | 25 +++++++++++++++++++++---- hrp/pkg/gadb/device_test.go | 3 +-- hrp/pkg/uixt/android_device.go | 23 ++--------------------- hrp/pkg/uixt/android_test.go | 18 ++++++++++-------- hrp/pkg/uixt/android_uia2_driver.go | 23 +++-------------------- hrp/pkg/uixt/ios_device.go | 5 +++-- 8 files changed, 56 insertions(+), 59 deletions(-) diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 9f1b24d5..ba1c47dd 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -10,6 +10,7 @@ import ( "fmt" "math" "math/rand" + "net" "os" "path/filepath" "reflect" @@ -484,6 +485,20 @@ func ConvertToStringSlice(val interface{}) ([]string, error) { return nil, fmt.Errorf("invalid type for conversion to []string") } +func GetFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, errors.Wrap(err, "resolve tcp addr failed") + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, errors.Wrap(err, "listen tcp addr failed") + } + defer func() { _ = l.Close() }() + return l.Addr().(*net.TCPAddr).Port, nil +} + func GetCurrentDay() string { now := time.Now() // 格式化日期为 yyyyMMdd diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 42cdebb8..7422eeba 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1,2 +1 @@ -v4.5.0 - +v4.5.0 \ No newline at end of file diff --git a/hrp/pkg/gadb/device.go b/hrp/pkg/gadb/device.go index ead9227d..d388ad55 100644 --- a/hrp/pkg/gadb/device.go +++ b/hrp/pkg/gadb/device.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "strconv" "strings" "time" @@ -181,10 +182,8 @@ func (d *Device) DevicePath() (string, error) { return resp, err } -func (d *Device) Forward(localPort int, remoteInterface interface{}, noRebind ...bool) (err error) { - command := "" +func (d *Device) Forward(remoteInterface interface{}, noRebind ...bool) (port int, err error) { var remote string - local := fmt.Sprintf("tcp:%d", localPort) switch r := remoteInterface.(type) { // for unix sockets case string: @@ -193,6 +192,24 @@ func (d *Device) Forward(localPort int, remoteInterface interface{}, noRebind .. remote = fmt.Sprintf("tcp:%d", r) } + forwardList, err := d.ForwardList() + if err != nil { + return + } + for _, forwardItem := range forwardList { + if forwardItem.Remote == remote { + return strconv.Atoi(forwardItem.Local[4:]) + } + } + localPort, err := builtin.GetFreePort() + if err != nil { + return + } + + command := "" + + local := fmt.Sprintf("tcp:%d", localPort) + if len(noRebind) != 0 && noRebind[0] { command = fmt.Sprintf("host-serial:%s:forward:norebind:%s;%s", d.serial, local, remote) } else { @@ -200,7 +217,7 @@ func (d *Device) Forward(localPort int, remoteInterface interface{}, noRebind .. } _, err = d.adbClient.executeCommand(command, true) - return + return localPort, nil } func (d *Device) ForwardList() (deviceForwardList []DeviceForward, err error) { diff --git a/hrp/pkg/gadb/device_test.go b/hrp/pkg/gadb/device_test.go index e1279051..d6ff7d2e 100644 --- a/hrp/pkg/gadb/device_test.go +++ b/hrp/pkg/gadb/device_test.go @@ -124,8 +124,7 @@ func TestDevice_Forward(t *testing.T) { setupDevices(t) for _, device := range devices { - localPort := 61000 - err := device.Forward(localPort, 6790) + localPort, err := device.Forward(6790) if err != nil { t.Fatal(err) } diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 34764a70..29100163 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -5,7 +5,6 @@ import ( "bytes" "context" "fmt" - "net" "os/exec" "strings" @@ -198,12 +197,8 @@ func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverE // NewUSBDriver creates new client via USB connected device, this will also start a new session. func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver WebDriver, err error) { - var localPort int - if localPort, err = getFreePort(); err != nil { - return nil, errors.Wrap(code.AndroidDeviceConnectionError, - fmt.Sprintf("get free port failed: %v", err)) - } - if err = dev.d.Forward(localPort, UIA2ServerPort); err != nil { + localPort, err := dev.d.Forward(UIA2ServerPort) + if err != nil { return nil, errors.Wrap(code.AndroidDeviceConnectionError, fmt.Sprintf("forward port %d->%d failed: %v", localPort, UIA2ServerPort, err)) @@ -262,20 +257,6 @@ func (dev *AndroidDevice) StopPcap() string { return "" } -func getFreePort() (int, error) { - addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - if err != nil { - return 0, errors.Wrap(err, "resolve tcp addr failed") - } - - l, err := net.ListenTCP("tcp", addr) - if err != nil { - return 0, errors.Wrap(err, "listen tcp addr failed") - } - defer func() { _ = l.Close() }() - return l.Addr().(*net.TCPAddr).Port, nil -} - type LineCallback func(string) type AdbLogcat struct { diff --git a/hrp/pkg/uixt/android_test.go b/hrp/pkg/uixt/android_test.go index 6284fb66..3c20f741 100644 --- a/hrp/pkg/uixt/android_test.go +++ b/hrp/pkg/uixt/android_test.go @@ -10,6 +10,8 @@ import ( "strings" "testing" "time" + + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) var ( @@ -21,6 +23,7 @@ func setupAndroid(t *testing.T) { device, err := NewAndroidDevice() checkErr(t, err) device.UIA2 = false + device.LogOn = true driverExt, err = device.NewDriver() checkErr(t, err) } @@ -195,22 +198,21 @@ func TestDriver_DeviceInfo(t *testing.T) { } func TestDriver_Tap(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) - if err != nil { - t.Fatal(err) - } - - err = driver.Tap(150, 340) + setupAndroid(t) + driverExt.Driver.StartCaptureLog("") + err := driverExt.Driver.Tap(150, 340, WithIdentifier("test")) if err != nil { t.Fatal(err) } time.Sleep(time.Second) - err = driver.TapFloat(60.5, 125.5) + err = driverExt.Driver.TapFloat(60.5, 125.5, WithIdentifier("test")) if err != nil { t.Fatal(err) } time.Sleep(time.Second) + result, _ := driverExt.Driver.StopCaptureLog() + t.Log(result) } func TestDriver_Swipe(t *testing.T) { @@ -333,7 +335,7 @@ func TestUiSelectorHelper_NewUiSelectorHelper(t *testing.T) { } func Test_getFreePort(t *testing.T) { - freePort, err := getFreePort() + freePort, err := builtin.GetFreePort() if err != nil { t.Fatal(err) } diff --git a/hrp/pkg/uixt/android_uia2_driver.go b/hrp/pkg/uixt/android_uia2_driver.go index acc58b35..6606cd78 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -16,7 +16,6 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/pkg/utf7" ) @@ -564,25 +563,9 @@ func (ud *uiaDriver) Rotation() (rotation Rotation, err error) { } func (ud *uiaDriver) Screenshot() (raw *bytes.Buffer, err error) { - // register(getHandler, new CaptureScreenshot("/wd/hub/session/:sessionId/screenshot")) - var rawResp rawResponse - if rawResp, err = ud.httpGET("/session", ud.sessionId, "screenshot"); err != nil { - return nil, errors.Wrap(code.AndroidScreenShotError, - fmt.Sprintf("get UIA screenshot data failed: %v", err)) - } - reply := new(struct{ Value string }) - if err = json.Unmarshal(rawResp, reply); err != nil { - return nil, err - } - - var decodeStr []byte - if decodeStr, err = base64.StdEncoding.DecodeString(reply.Value); err != nil { - return nil, errors.Wrap(code.AndroidScreenShotError, - fmt.Sprintf("decode UIA screenshot data failed: %v", err)) - } - - raw = bytes.NewBuffer(decodeStr) - return + // https://bytedance.larkoffice.com/docx/C8qEdmSHnoRvMaxZauocMiYpnLh + // ui2截图受内存影响,改为adb截图 + return ud.adbDriver.Screenshot() } func (ud *uiaDriver) Source(srcOpt ...SourceOption) (source string, err error) { diff --git a/hrp/pkg/uixt/ios_device.go b/hrp/pkg/uixt/ios_device.go index b5283667..fea9f0f2 100644 --- a/hrp/pkg/uixt/ios_device.go +++ b/hrp/pkg/uixt/ios_device.go @@ -16,6 +16,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" "github.com/httprunner/httprunner/v4/hrp/internal/env" "github.com/httprunner/httprunner/v4/hrp/pkg/gidevice" @@ -592,7 +593,7 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver var localPort int localPort, err = strconv.Atoi(env.WDA_LOCAL_PORT) if err != nil { - localPort, err = getFreePort() + localPort, err = builtin.GetFreePort() if err != nil { return nil, errors.Wrap(code.IOSDeviceHTTPDriverError, fmt.Sprintf("get free port failed: %v", err)) @@ -609,7 +610,7 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver var localMjpegPort int localMjpegPort, err = strconv.Atoi(env.WDA_LOCAL_MJPEG_PORT) if err != nil { - localMjpegPort, err = getFreePort() + localMjpegPort, err = builtin.GetFreePort() if err != nil { return nil, errors.Wrap(code.IOSDeviceHTTPDriverError, fmt.Sprintf("get free port failed: %v", err)) From 6ec9b4a9a6bc3a6aa7d166dbf1650c20a20e82ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Mon, 20 May 2024 14:40:25 +0800 Subject: [PATCH 44/60] feat: update version 4.5.1 --- hrp/internal/version/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 7422eeba..dfe33e04 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.5.0 \ No newline at end of file +v4.5.1 \ No newline at end of file From 9985d926afb2f2207cb23e57b4cd34361b7a2431 Mon Sep 17 00:00:00 2001 From: cooscao Date: Wed, 22 May 2024 20:20:31 +0800 Subject: [PATCH 45/60] feat: add live popularity --- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/action.go | 25 +++++++++++++++++++------ hrp/pkg/uixt/service_vedem.go | 9 +++++---- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 42cdebb8..9fc5fe9c 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1,2 +1,2 @@ -v4.5.0 +v4.5.1 diff --git a/hrp/pkg/uixt/action.go b/hrp/pkg/uixt/action.go index 6ca6bcce..1a555f91 100644 --- a/hrp/pkg/uixt/action.go +++ b/hrp/pkg/uixt/action.go @@ -117,12 +117,13 @@ 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"` - ScreenShotWithClosePopups bool `json:"screenshot_with_close_popups,omitempty" yaml:"screenshot_with_close_popups,omitempty"` - ScreenShotWithOCRCluster string `json:"screenshot_with_ocr_cluster,omitempty" yaml:"screenshot_with_ocr_cluster,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"` + ScreenShotWithLivePopularity bool `json:"screenshot_with_live_popularity,omitempty" yaml:"screenshot_with_live_popularity,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"` + ScreenShotWithOCRCluster string `json:"screenshot_with_ocr_cluster,omitempty" yaml:"screenshot_with_ocr_cluster,omitempty"` } func (o *ActionOptions) Options() []ActionOption { @@ -226,6 +227,9 @@ func (o *ActionOptions) Options() []ActionOption { if o.ScreenShotWithLiveType { options = append(options, WithScreenShotLiveType(true)) } + if o.ScreenShotWithLivePopularity { + options = append(options, WithScreenShotLivePopularity(true)) + } if len(o.ScreenShotWithUITypes) > 0 { options = append(options, WithScreenShotUITypes(o.ScreenShotWithUITypes...)) } @@ -250,6 +254,9 @@ func (o *ActionOptions) screenshotActions() []string { if o.ScreenShotWithLiveType { actions = append(actions, "liveType") } + if o.ScreenShotWithLivePopularity { + actions = append(actions, "livePopularity") + } // UI detection if len(o.ScreenShotWithUITypes) > 0 { actions = append(actions, "ui") @@ -476,6 +483,12 @@ func WithScreenShotLiveType(liveTypeOn bool) ActionOption { } } +func WithScreenShotLivePopularity(livePopularityOn bool) ActionOption { + return func(o *ActionOptions) { + o.ScreenShotWithLivePopularity = livePopularityOn + } +} + func WithScreenShotUITypes(uiTypes ...string) ActionOption { return func(o *ActionOptions) { o.ScreenShotWithUITypes = uiTypes diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index 186296c3..60aac2a1 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -67,9 +67,10 @@ type ImageResult struct { // Media(媒体) // Chat(语音) // Event(赛事) - LiveType string `json:"liveType,omitempty"` // 直播间类型 - UIResult UIResultMap `json:"uiResult,omitempty"` // 图标检测 - CPResult *ClosePopupsResult `json:"closeResult,omitempty"` // 弹窗按钮检测 + LiveType string `json:"liveType,omitempty"` // 直播间类型 + LivePopularity int64 `json:"livePopularity,omitempty"` // 直播间热度 + UIResult UIResultMap `json:"uiResult,omitempty"` // 图标检测 + CPResult *ClosePopupsResult `json:"closeResult,omitempty"` // 弹窗按钮检测 } type APIResponseImage struct { @@ -417,7 +418,7 @@ func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *S screenResult.Texts = imageResult.OCRResult.ToOCRTexts() screenResult.UploadedURL = imageResult.URL screenResult.Icons = imageResult.UIResult - screenResult.Video = &Video{LiveType: imageResult.LiveType} + screenResult.Video = &Video{LiveType: imageResult.LiveType, ViewCount: imageResult.LivePopularity} if actionOptions.ScreenShotWithClosePopups && imageResult.CPResult != nil { screenResult.Popup = &PopupInfo{ From 33fb4637608abce2e6279db53007ce3460ab01cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Tue, 28 May 2024 15:05:57 +0800 Subject: [PATCH 46/60] feat: add log --- hrp/cmd/ios/apps.go | 6 +++--- hrp/pkg/uixt/android_device.go | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/hrp/cmd/ios/apps.go b/hrp/cmd/ios/apps.go index 2c41009e..864cc328 100644 --- a/hrp/cmd/ios/apps.go +++ b/hrp/cmd/ios/apps.go @@ -49,11 +49,11 @@ var listAppsCmd = &cobra.Command{ applicationType = gidevice.ApplicationTypeAny } - result, errList := device.InstallationProxyBrowse( + result, err := device.InstallationProxyBrowse( gidevice.WithApplicationType(applicationType), gidevice.WithReturnAttributes("CFBundleVersion", "CFBundleDisplayName", "CFBundleIdentifier")) - if errList != nil { - return fmt.Errorf("get app list failed") + if err != nil { + return fmt.Errorf("get app list failed %v", err) } for _, app := range result { diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 29100163..ffbfb4c2 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -390,6 +390,7 @@ type ExportPoint struct { func ConvertPoints(lines []string) (eps []ExportPoint) { log.Info().Msg("ConvertPoints") + log.Info().Msg(strings.Join(lines, "\n")) for _, line := range lines { if strings.Contains(line, "ext") { idx := strings.Index(line, "{") From a2cb0415f930df7db528b80622f0df93572856a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Tue, 28 May 2024 15:10:46 +0800 Subject: [PATCH 47/60] feat: add log --- hrp/internal/builtin/utils.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index ba1c47dd..5c32d5f7 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -495,7 +495,11 @@ func GetFreePort() (int, error) { if err != nil { return 0, errors.Wrap(err, "listen tcp addr failed") } - defer func() { _ = l.Close() }() + defer func() { + if err = l.Close(); err != nil { + log.Error().Err(err).Msg(fmt.Sprintf("close addr %s error", l.Addr().String())) + } + }() return l.Addr().(*net.TCPAddr).Port, nil } From 05c4bc9e084602036a05ca84e09852059c5acc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Tue, 28 May 2024 15:18:21 +0800 Subject: [PATCH 48/60] fix: code check error --- hrp/pkg/gadb/device.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/hrp/pkg/gadb/device.go b/hrp/pkg/gadb/device.go index d388ad55..f2d47e83 100644 --- a/hrp/pkg/gadb/device.go +++ b/hrp/pkg/gadb/device.go @@ -205,15 +205,11 @@ func (d *Device) Forward(remoteInterface interface{}, noRebind ...bool) (port in if err != nil { return } - - command := "" - local := fmt.Sprintf("tcp:%d", localPort) + command := fmt.Sprintf("host-serial:%s:forward:%s;%s", d.serial, local, remote) if len(noRebind) != 0 && noRebind[0] { command = fmt.Sprintf("host-serial:%s:forward:norebind:%s;%s", d.serial, local, remote) - } else { - command = fmt.Sprintf("host-serial:%s:forward:%s;%s", d.serial, local, remote) } _, err = d.adbClient.executeCommand(command, true) From a64237d6716176218cc85debc59eb3c58802704a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Fri, 15 Mar 2024 19:04:55 +0800 Subject: [PATCH 49/60] fix: add waiting time after pressing back --- examples/uitest/bili/cli.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/uitest/bili/cli.go b/examples/uitest/bili/cli.go index 9a000d8d..55566a91 100644 --- a/examples/uitest/bili/cli.go +++ b/examples/uitest/bili/cli.go @@ -67,6 +67,7 @@ func launchAppDriver(pkgName string) (driver *uixt.DriverExt, err error) { } func watchVideo(driver *uixt.DriverExt) (err error) { + time.Sleep(3 * time.Second) err = driver.SwipeUp() if err != nil { return err @@ -107,12 +108,13 @@ func watchVideo(driver *uixt.DriverExt) (err error) { return err } + time.Sleep(1 * time.Second) + // 返回推荐页 err = driver.Driver.PressBack() if err != nil { return err } - time.Sleep(1 * time.Second) return nil } From 2d1c6a024aaa2aa4207e32dcf7136e3b42f9e230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Fri, 15 Mar 2024 19:11:45 +0800 Subject: [PATCH 50/60] fix: increase scroll length --- examples/uitest/bili/cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/uitest/bili/cli.go b/examples/uitest/bili/cli.go index 55566a91..24b83749 100644 --- a/examples/uitest/bili/cli.go +++ b/examples/uitest/bili/cli.go @@ -68,7 +68,7 @@ func launchAppDriver(pkgName string) (driver *uixt.DriverExt, err error) { func watchVideo(driver *uixt.DriverExt) (err error) { time.Sleep(3 * time.Second) - err = driver.SwipeUp() + err = driver.SwipeRelative(0.7, 0.7, 0.7, 0.2) if err != nil { return err } From 1f64c734d05c06c01b7464324e1c95824ad9df8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Mon, 8 Apr 2024 20:22:02 +0800 Subject: [PATCH 51/60] fix: resolve issue of missing live consumption data when algorithm service is down --- hrp/pkg/uixt/service_vedem.go | 2 +- hrp/pkg/uixt/video_crawler.go | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/hrp/pkg/uixt/service_vedem.go b/hrp/pkg/uixt/service_vedem.go index 1b7ede88..e99bb5c3 100644 --- a/hrp/pkg/uixt/service_vedem.go +++ b/hrp/pkg/uixt/service_vedem.go @@ -405,7 +405,7 @@ func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *S imageResult, err := dExt.ImageService.GetImage(bufSource, options...) if err != nil { log.Error().Err(err).Msg("GetImage from ImageService failed") - return nil, err + return screenResult, err } if imageResult != nil { screenResult.imageResult = imageResult diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 17bcb630..c8561b29 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -265,8 +265,6 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { ) if err != nil { log.Error().Err(err).Msg("get screen result failed") - time.Sleep(3 * time.Second) - continue } // add live type From bc0925b48f6f906f5f6a1d6801635abea1616f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E5=B8=85?= Date: Tue, 16 Apr 2024 06:40:43 +0000 Subject: [PATCH 52/60] chore: change live preview prob * chore: change live preview prob * chore: change exit liveroom https://code.byted.org/iesqa/httprunner/merge_requests/33 --- hrp/pkg/uixt/android_device.go | 4 ++++ hrp/pkg/uixt/interface.go | 3 ++- hrp/pkg/uixt/ios_device.go | 4 ++++ hrp/pkg/uixt/video_crawler.go | 22 ++++++++++++++++------ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 42b4757e..c250b21d 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -154,6 +154,10 @@ type AndroidDevice struct { LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"` } +func (dev *AndroidDevice) System() string { + return "android" +} + func (dev *AndroidDevice) UUID() string { return dev.SerialNumber } diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index 71d8e177..ae0d04cc 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -466,7 +466,8 @@ func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption { // current implemeted device: IOSDevice, AndroidDevice type Device interface { - UUID() string // ios udid or android serial + System() string // ios or android + UUID() string // ios udid or android serial LogEnabled() bool NewDriver(...DriverOption) (driverExt *DriverExt, err error) diff --git a/hrp/pkg/uixt/ios_device.go b/hrp/pkg/uixt/ios_device.go index 843a34b3..2705ade6 100644 --- a/hrp/pkg/uixt/ios_device.go +++ b/hrp/pkg/uixt/ios_device.go @@ -289,6 +289,10 @@ type IOSDevice struct { pcapFile string // saved pcap file path } +func (dev *IOSDevice) System() string { + return "ios" +} + func (dev *IOSDevice) UUID() string { return dev.UDID } diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index c8561b29..05dad2c3 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -213,6 +213,9 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { continue } + // 直播预览流线上概率 + livePreviewProb := crawler.getLivePreviewProb() + switch currentVideo.Type { case VideoType_PreviewLive: isFeed = true @@ -222,9 +225,8 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { log.Info().Interface("video", currentVideo). Msg("live count achieved, skip entering live room") skipEnterLive = true - } else if rand.Float64() <= 0.50 { - // 50% chance skip entering live room - log.Info().Msg("skip entering preview live by 50% chance") + } else if rand.Float64() <= livePreviewProb { + log.Info().Interface("livePreviewProb", livePreviewProb).Msg("skip entering preview") skipEnterLive = true } @@ -288,9 +290,8 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { log.Info().Interface("live", currentVideo). Msg("live count achieved, exit live room") exitLive = true - } else if rand.Float64() <= 0.50 { - // 50% chance exit live room - log.Info().Msg("exit live room by 10% chance") + } else if rand.Float64() <= livePreviewProb { + log.Info().Interface("livePreviewProb", livePreviewProb).Msg("exit live room by preview live chance") exitLive = true } // isFeed:通过预览流进入内流失败的情况下,防止使用退出直播间逻辑,影响:首次进入内流,至少会消费两个直播间才能退出 @@ -450,3 +451,12 @@ func (vc *VideoCrawler) getCurrentVideo() (video *Video, err error) { Msg("get current video success") return video, nil } + +func (vc *VideoCrawler) getLivePreviewProb() float64 { + if vc.driverExt.Device.System() == "ios" { + return 0.5326 + } else if vc.driverExt.Device.System() == "android" { + return 0.3414 + } + return -1 +} From 46ea78eaf3863d577c895351523a4907e55e4568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Wed, 11 Oct 2023 14:38:52 +0800 Subject: [PATCH 53/60] fix: the recommended page slides to exit by mistake --- hrp/pkg/uixt/video_crawler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 05dad2c3..33b89059 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -294,7 +294,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { log.Info().Interface("livePreviewProb", livePreviewProb).Msg("exit live room by preview live chance") exitLive = true } - // isFeed:通过预览流进入内流失败的情况下,防止使用退出直播间逻辑,影响:首次进入内流,至少会消费两个直播间才能退出 + if (!isFeed) && exitLive && currentVideo.Type == VideoType_Live { err = crawler.exitLiveRoom() if err != nil { From 4599c47bcb469475068db17457131cc70068183d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Tue, 23 Apr 2024 11:28:02 +0800 Subject: [PATCH 54/60] fix: failed to swipe up --- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/swipe.go | 20 ++++++++++++++++++++ hrp/pkg/uixt/video_crawler.go | 17 ++--------------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 6abe9888..10fbe5fc 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.8.202401201310 \ No newline at end of file +v4.3.8.202404161520 \ No newline at end of file diff --git a/hrp/pkg/uixt/swipe.go b/hrp/pkg/uixt/swipe.go index 4388d9e8..60b84e2d 100644 --- a/hrp/pkg/uixt/swipe.go +++ b/hrp/pkg/uixt/swipe.go @@ -11,10 +11,30 @@ import ( "github.com/httprunner/httprunner/v4/hrp/internal/code" ) +var ( + directionSlice = [][]float64{ + {0.85, 0.83, 0.85, 0.1}, + {0.9, 0.75, 0.9, 0.1}, + {0.6, 0.5, 0.6, 0.1}, + } +) + func assertRelative(p float64) bool { return p >= 0 && p <= 1 } +func (dExt *DriverExt) SwipeUpUtil(count int64, options ...ActionOption) error { + width := dExt.windowSize.Width + height := dExt.windowSize.Height + + fromX := float64(width) * directionSlice[count%3][0] + fromY := float64(height) * directionSlice[count%3][1] + toX := float64(width) * directionSlice[count%3][2] + toY := float64(height) * directionSlice[count%3][3] + + return dExt.Driver.SwipeFloat(fromX, fromY, toX, toY, options...) +} + // SwipeRelative swipe from relative position [fromX, fromY] to relative position [toX, toY] func (dExt *DriverExt) SwipeRelative(fromX, fromY, toX, toY float64, options ...ActionOption) error { width := dExt.windowSize.Width diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 33b89059..5219684a 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -171,14 +171,14 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { // swipe to next feed video log.Info().Msg("swipe to next feed video") swipeStartTime := time.Now() - if err = dExt.SwipeRelative(0.85, 0.83-(float64(crawler.failedCount)*0.01), 0.85, 0.1, WithOffsetRandomRange(-10, 10)); err != nil { + if err = dExt.SwipeUpUtil(crawler.failedCount, WithOffsetRandomRange(-10, 10)); err != nil { log.Error().Err(err).Msg("feed swipe up failed") return err } swipeFinishTime := time.Now() // get app event trackings - // retry 10 times if get feed failed, abort if fail 10 consecutive times + // retry 3 times if get feed failed, abort if fail 3 consecutive times currentVideo, err := crawler.getCurrentVideo() if err != nil || currentVideo.Type == "" { crawler.failedCount++ @@ -196,19 +196,6 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { return err } - if crawler.failedCount > 1 && !isFeed { - // enter live room - entryPoint := PointF{ - X: float64(dExt.windowSize.Width / 2), - Y: float64(dExt.windowSize.Height / 2), - } - - log.Info().Msg("tap screen center to close edge popup") - if err := crawler.driverExt.TapAbsXY(entryPoint.X, entryPoint.Y, - WithOffsetRandomRange(-20, 20)); err != nil { - } - } - // retry continue } From 99757954c6aaa7b8320c051df23ae1262fa77f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Wed, 29 May 2024 17:32:43 +0800 Subject: [PATCH 55/60] feat: add bilibili ios script --- .../bili/{ => android}/bili_android.json | 0 examples/uitest/bili/{ => android}/cli.go | 0 .../uitest/bili/{ => android}/cli_test.go | 0 examples/uitest/bili/ios/bili_ios.json | 18 +++ examples/uitest/bili/ios/cli.go | 137 ++++++++++++++++++ go.mod | 2 +- go.sum | 2 - hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/video_crawler.go | 11 +- hrp/plugin.go | 3 +- hrp/runner.go | 9 +- 11 files changed, 176 insertions(+), 8 deletions(-) rename examples/uitest/bili/{ => android}/bili_android.json (100%) rename examples/uitest/bili/{ => android}/cli.go (100%) rename examples/uitest/bili/{ => android}/cli_test.go (100%) create mode 100644 examples/uitest/bili/ios/bili_ios.json create mode 100644 examples/uitest/bili/ios/cli.go diff --git a/examples/uitest/bili/bili_android.json b/examples/uitest/bili/android/bili_android.json similarity index 100% rename from examples/uitest/bili/bili_android.json rename to examples/uitest/bili/android/bili_android.json diff --git a/examples/uitest/bili/cli.go b/examples/uitest/bili/android/cli.go similarity index 100% rename from examples/uitest/bili/cli.go rename to examples/uitest/bili/android/cli.go diff --git a/examples/uitest/bili/cli_test.go b/examples/uitest/bili/android/cli_test.go similarity index 100% rename from examples/uitest/bili/cli_test.go rename to examples/uitest/bili/android/cli_test.go diff --git a/examples/uitest/bili/ios/bili_ios.json b/examples/uitest/bili/ios/bili_ios.json new file mode 100644 index 00000000..beb90d10 --- /dev/null +++ b/examples/uitest/bili/ios/bili_ios.json @@ -0,0 +1,18 @@ +{ + "config": { + "name": "run ui test on bili ios", + "variables": { + "RunTimes": 3, + "UDID": "${ENV(UDID)}" + } + }, + "teststeps": [ + { + "name": "run bili ios", + "shell": { + "string": "bili_ios", + "expect_exit_code": 0 + } + } + ] +} diff --git a/examples/uitest/bili/ios/cli.go b/examples/uitest/bili/ios/cli.go new file mode 100644 index 00000000..7288f4a1 --- /dev/null +++ b/examples/uitest/bili/ios/cli.go @@ -0,0 +1,137 @@ +package main + +import ( + "fmt" + "os" + "strconv" + "time" + + "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" +) + +var ( + serial string + runTimes int +) + +func init() { + serial = os.Getenv("UDID") + numStr := os.Getenv("RunTimes") + defaultNum := 20 + + var err error + runTimes, err = strconv.Atoi(numStr) + if err != nil { + runTimes = defaultNum + } + fmt.Printf("=== start running cases, serial=%s, runTimes=%d ===\n", serial, runTimes) +} + +func launchAppDriver(pkgName string) (driver *uixt.DriverExt, err error) { + device, _ := uixt.NewIOSDevice(uixt.WithUDID(serial)) + driver, err = device.NewDriver() + if err != nil { + return nil, err + } + + //_, err = driver.Driver.AppTerminate(pkgName) + //if err != nil { + // return nil, err + //} + // + //err = driver.Driver.Homescreen() + //if err != nil { + // return nil, err + //} + // + //err = driver.Driver.AppLaunch(pkgName) + //if err != nil { + // return nil, err + //} + + time.Sleep(15 * time.Second) + + // 处理弹窗 + err = driver.ClosePopupsHandler() + if err != nil { + return nil, err + } + + // 进入推荐页 + err = driver.TapByOCR("推荐", uixt.WithScope(0, 0, 1, 0.3)) + if err != nil { + return nil, err + } + + return driver, nil +} + +func watchVideo(driver *uixt.DriverExt) (err error) { + time.Sleep(3 * time.Second) + err = driver.SwipeRelative(0.7, 0.7, 0.7, 0.2) + if err != nil { + return err + } + time.Sleep(1 * time.Second) + + // 点击进入某视频 + err = driver.TapXY(0.3, 0.5) + if err != nil { + return err + } + + time.Sleep(5 * time.Second) + + // 点击播放区域,展现横屏图标 + err = driver.TapXY(0.5, 0.1) + if err != nil { + return err + } + time.Sleep(500 * time.Millisecond) + + // 切换横屏 + err = driver.TapByUIDetection( + uixt.WithScreenShotUITypes("fullScreen")) + if err != nil { + // 未找到横屏图标,该页面可能不是横版视频(直播|广告|Feed) + // 退出回到推荐页 + driver.Driver.PressBack() + return nil + } + + // 观播 10s + time.Sleep(10 * time.Second) + + // 返回视频页面 + err = driver.Driver.PressBack() + if err != nil { + return err + } + + time.Sleep(1 * time.Second) + + // 返回推荐页 + err = driver.Driver.PressBack() + if err != nil { + return err + } + + return nil +} + +// build shell command +// go build -o bili_android examples/uitest/bilibili/cli.go +func main() { + driver, err := launchAppDriver("tv.danmaku.bilianime") + if err != nil { + panic(err) + } + + // 重复采集 XX 次 + for i := 0; i < runTimes; i++ { + err = watchVideo(driver) + if err != nil { + panic(err) + } + } +} diff --git a/go.mod b/go.mod index 6cc068cb..4ed4276e 100644 --- a/go.mod +++ b/go.mod @@ -80,4 +80,4 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect ) -// replace github.com/httprunner/funplugin => ../funplugin +replace github.com/httprunner/funplugin v0.5.4 => /Users/bytedance/go/src/github.com/httprunner/funplugin diff --git a/go.sum b/go.sum index 8d9bc726..b4d32ede 100644 --- a/go.sum +++ b/go.sum @@ -172,8 +172,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= -github.com/httprunner/funplugin v0.5.4 h1:hlfNGcYw2Rv2Mdp1l2S1R6ufwzKgpB9lheFvAxI0LfM= -github.com/httprunner/funplugin v0.5.4/go.mod h1:YZzBBSOSdLZEpHZz0P2E5SOQ+o1+Fbn30oWS4RGHBz0= github.com/hybridgroup/mjpeg v0.0.0-20140228234708-4680f319790e/go.mod h1:eagM805MRKrioHYuU7iKLUyFPVKqVV6um5DAvCkUtXs= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 10fbe5fc..ff0d643e 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.8.202404161520 \ No newline at end of file +v4.3.8.202405262130 \ No newline at end of file diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 5219684a..28af0ec3 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -114,7 +114,12 @@ func (vc *VideoCrawler) isTargetAchieved() bool { func (vc *VideoCrawler) exitLiveRoom() error { log.Info().Msg("press back to exit live room") - return vc.driverExt.Driver.PressBack() + err := vc.driverExt.Driver.PressBack() + time.Sleep(time.Duration(3) * time.Second) + if vc.driverExt.TapByOCR("退出直播间") == nil { + log.Info().Msg("clicked the button to exit the live room successfully") + } + return err } const ( @@ -231,6 +236,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { log.Error().Err(err).Msg("tap live video failed") continue } + currentVideo.Type = VideoType_Live } else { // skip entering live room // only mock simulation play duration @@ -282,7 +288,8 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { exitLive = true } - if (!isFeed) && exitLive && currentVideo.Type == VideoType_Live { + // isFeed:通过预览流进入内流失败的情况下,防止使用退出直播间逻辑,影响:首次进入内流,至少会消费两个直播间才能退出 + if !isFeed && exitLive && currentVideo.Type == VideoType_Live { err = crawler.exitLiveRoom() if err != nil { if errors.Is(err, code.TimeoutError) || errors.Is(err, code.InterruptError) { diff --git a/hrp/plugin.go b/hrp/plugin.go index ea9afad9..54b1cab4 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -6,6 +6,8 @@ import ( "strings" "sync" + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" + "github.com/httprunner/funplugin" "github.com/httprunner/funplugin/myexec" "github.com/pkg/errors" @@ -13,7 +15,6 @@ import ( "github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/internal/env" - "github.com/httprunner/httprunner/v4/hrp/internal/sdk" ) const ( diff --git a/hrp/runner.go b/hrp/runner.go index b68a5208..afbff2e9 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -4,6 +4,7 @@ import ( "crypto/tls" _ "embed" "fmt" + "github.com/httprunner/funplugin" "net" "net/http" "net/http/cookiejar" @@ -17,7 +18,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/httprunner/funplugin" "github.com/jinzhu/copier" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -240,6 +240,13 @@ func (r *HRPRunner) Run(testcases ...ITestCase) (err error) { } }() + if caseRunner.parsedConfig.PluginSetting != nil { + if p, ok := pluginMap.Load(caseRunner.parsedConfig.PluginSetting.Path); ok { + log.Info().Msg(fmt.Sprintf("starting to keep live, path: %v, address: %v", caseRunner.parsedConfig.PluginSetting.Path, p)) + go p.(funplugin.IPlugin).StartHeartbeat() + } + } + for it := caseRunner.parametersIterator; it.HasNext(); { // case runner can run multiple times with different parameters // each run has its own session runner From 831419a25c14fe70142f146f602de79acc9897f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Fri, 19 Jul 2024 14:23:49 +0800 Subject: [PATCH 56/60] fix: increase the maximum simulation duration to two hours --- hrp/cmd/run.go | 2 +- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/video_crawler.go | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/hrp/cmd/run.go b/hrp/cmd/run.go index cb14a8c3..e042a5b7 100644 --- a/hrp/cmd/run.go +++ b/hrp/cmd/run.go @@ -42,7 +42,7 @@ func init() { runCmd.Flags().BoolVarP(&continueOnFailure, "continue-on-failure", "c", false, "continue running next step when failure occurs") runCmd.Flags().BoolVar(&requestsLogOff, "log-requests-off", false, "turn off request & response details logging") runCmd.Flags().BoolVar(&httpStatOn, "http-stat", false, "turn on HTTP latency stat (DNSLookup, TCP Connection, etc.)") - runCmd.Flags().BoolVar(&pluginLogOn, "log-plugin", true, "turn on plugin logging") + runCmd.Flags().BoolVar(&pluginLogOn, "log-plugin", false, "turn on plugin logging") runCmd.Flags().StringVarP(&proxyUrl, "proxy-url", "p", "", "set proxy url") runCmd.Flags().BoolVarP(&saveTests, "save-tests", "s", false, "save tests summary") runCmd.Flags().BoolVarP(&genHTMLReport, "gen-html-report", "g", false, "generate html report") diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index ff0d643e..e65344f9 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.8.202405262130 \ No newline at end of file +v4.3.8.202406252100 \ No newline at end of file diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 28af0ec3..68dfc183 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -188,7 +188,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { if err != nil || currentVideo.Type == "" { crawler.failedCount++ if crawler.failedCount >= 3 { - // failed 10 consecutive times + // failed 3 consecutive times return errors.Wrap(code.TrackingGetError, "get current feed video failed 3 consecutive times") } @@ -270,7 +270,8 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { } // simulation watch feed video - sleepStrict(swipeFinishTime, currentVideo.PlayDuration) + simulationPlayDuration := math.Min(float64(currentVideo.PlayDuration), 7200000) + sleepStrict(swipeFinishTime, int64(simulationPlayDuration)) screenResult.Video = currentVideo screenResult.Resolution = dExt.windowSize @@ -318,7 +319,8 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { dExt.cacheStepData.screenResults[time.Now().String()] = screenResult // simulation watch feed video - sleepStrict(swipeFinishTime, currentVideo.PlayDuration) + simulationPlayDuration := math.Min(float64(currentVideo.PlayDuration), 7200000) + sleepStrict(swipeFinishTime, int64(simulationPlayDuration)) screenResult.TotalElapsed = time.Since(swipeFinishTime).Milliseconds() } From 0c2244c5bc094f02edb0e2cf04684b1f7e741d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Wed, 14 Aug 2024 11:09:08 +0800 Subject: [PATCH 57/60] fix: resolve conflicts --- docs/CHANGELOG.md | 6 +----- go.mod | 5 ----- go.sum | 32 +++++++++++++++----------------- hrp/internal/version/VERSION | 2 +- hrp/pkg/gadb/device.go | 19 ------------------- hrp/pkg/uixt/ext.go | 7 +++++-- hrp/pkg/uixt/swipe.go | 2 +- hrp/pkg/uixt/video_crawler.go | 12 ++++++++++-- 8 files changed, 33 insertions(+), 52 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9bc931a7..58cb9b4f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,11 +1,8 @@ # Release History -<<<<<<< HEAD -## v4.3.8 (2024-01-18) +## v4.3.9 (2024-01-18) - feat: add Shell step type -======= -## v4.3.9 (2023-09-19) - fix: OCR calls use compressed image ## v4.3.8 (2023-09-19) @@ -17,7 +14,6 @@ - fix: fixed the problem that the new version of adb format fails to parse **go version** ->>>>>>> video-release ## v4.3.7 (2023-09-19) diff --git a/go.mod b/go.mod index 7aa5f9b2..06b43c76 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,6 @@ require ( github.com/hashicorp/go-plugin v1.4.10 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect - github.com/incu6us/goimports-reviser/v2 v2.5.3 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -76,14 +75,10 @@ require ( github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.5.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.16.0 // indirect - golang.org/x/tools v0.17.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - mvdan.cc/gofumpt v0.6.0 // indirect ) replace github.com/httprunner/funplugin v0.5.4 => /Users/bytedance/go/src/github.com/httprunner/funplugin diff --git a/go.sum b/go.sum index 57003ba7..363d01a0 100644 --- a/go.sum +++ b/go.sum @@ -48,7 +48,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -146,7 +145,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -166,12 +164,10 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= -github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-plugin v1.4.10 h1:xUbmA4jC6Dq163/fWcp8P3JuHilrHHMLNRxzGQJ9hNk= github.com/hashicorp/go-plugin v1.4.10/go.mod h1:6/1TEzT0eQznvI/gV2CM29DLSkAK/e58mUWKVsPaph0= -github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= -github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= @@ -181,10 +177,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/incu6us/goimports-reviser/v2 v2.5.3 h1:DzvFl1+qOIDukqN8vMM/10MQswFQywUdwXxsjuowxlc= -github.com/incu6us/goimports-reviser/v2 v2.5.3/go.mod h1:P18aXhQaED7izHIP9IPI9PqEs7Y7D9okq71Q8Y8yHN4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -293,8 +287,6 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -331,7 +323,6 @@ github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKo github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -347,7 +338,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -378,7 +368,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -410,6 +399,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -418,6 +408,7 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -428,7 +419,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -475,6 +465,7 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -485,6 +476,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -529,7 +521,6 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -556,6 +547,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -586,6 +578,7 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 h1:lv6/DhyiFFGsmzxbsUUTOkN29II+zeWHxvT8Lpdxsv0= google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -599,6 +592,7 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -612,12 +606,14 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -626,9 +622,11 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -637,8 +635,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= -mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index e65344f9..2b5b7c46 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.8.202406252100 \ No newline at end of file +v4.3.9 \ No newline at end of file diff --git a/hrp/pkg/gadb/device.go b/hrp/pkg/gadb/device.go index 15cbe51e..f2d47e83 100644 --- a/hrp/pkg/gadb/device.go +++ b/hrp/pkg/gadb/device.go @@ -108,39 +108,25 @@ func (d *Device) features() (features Features, err error) { return features, nil } -<<<<<<< HEAD -func (d Device) HasAttribute(key string) bool { -======= func (d *Device) HasAttribute(key string) bool { ->>>>>>> video-release _, ok := d.attrs[key] return ok } -<<<<<<< HEAD -func (d Device) Product() (string, error) { -======= func (d *Device) Product() (string, error) { ->>>>>>> video-release if d.HasAttribute("product") { return d.attrs["product"], nil } return "", errors.New("does not have attribute: product") } -<<<<<<< HEAD -func (d Device) Model() (string, error) { -======= func (d *Device) Model() (string, error) { ->>>>>>> video-release if d.HasAttribute("model") { return d.attrs["model"], nil } return "", errors.New("does not have attribute: model") } -<<<<<<< HEAD -======= func (d *Device) Brand() (string, error) { if d.HasAttribute("brand") { return d.attrs["brand"], nil @@ -154,7 +140,6 @@ func (d *Device) Brand() (string, error) { return brand, nil } ->>>>>>> video-release func (d *Device) Usb() (string, error) { if d.HasAttribute("usb") { return d.attrs["usb"], nil @@ -162,11 +147,7 @@ func (d *Device) Usb() (string, error) { return "", errors.New("does not have attribute: usb") } -<<<<<<< HEAD -func (d Device) transportId() (string, error) { -======= func (d *Device) transportId() (string, error) { ->>>>>>> video-release if d.HasAttribute("transport_id") { return d.attrs["transport_id"], nil } diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index b1855d80..d2301385 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -62,9 +62,12 @@ type ScreenResult struct { Video *Video `json:"video,omitempty"` Popup *PopupInfo `json:"popup,omitempty"` - SwipeStartTime int64 `json:"swipe_start_time"` // 滑动开始时间戳 - SwipeFinishTime int64 `json:"swipe_finish_time"` // 滑动结束时间戳 + SwipeStartTime int64 `json:"swipe_start_time"` // 滑动开始时间戳 + SwipeFinishTime int64 `json:"swipe_finish_time"` // 滑动结束时间戳 + FetchVideoStartTime int64 `json:"fetch_video_start_time"` // 抓取视频开始时间戳 + FetchVideoFinishTime int64 `json:"fetch_video_finish_time"` // 抓取视频结束时间戳 + FetchVideoElapsed int64 `json:"fetch_video_elapsed"` // 抓取视频耗时(ms) ScreenshotTakeElapsed int64 `json:"screenshot_take_elapsed"` // 设备截图耗时(ms) ScreenshotCVElapsed int64 `json:"screenshot_cv_elapsed"` // CV 识别耗时(ms) diff --git a/hrp/pkg/uixt/swipe.go b/hrp/pkg/uixt/swipe.go index cdd1d49f..bc536176 100644 --- a/hrp/pkg/uixt/swipe.go +++ b/hrp/pkg/uixt/swipe.go @@ -212,7 +212,7 @@ func (dExt *DriverExt) swipeToTapApp(appName string, options ...ActionOption) er } // automatic handling popups before swipe - if err := dExt.ClosePopups(); err != nil { + if err := dExt.ClosePopupsHandler(); err != nil { log.Error().Err(err).Msg("auto handle popup failed") } diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 68dfc183..8a835394 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -184,6 +184,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { // get app event trackings // retry 3 times if get feed failed, abort if fail 3 consecutive times + fetchVideoStartTime := time.Now() currentVideo, err := crawler.getCurrentVideo() if err != nil || currentVideo.Type == "" { crawler.failedCount++ @@ -204,6 +205,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { // retry continue } + fetchVideoFinishTime := time.Now() // 直播预览流线上概率 livePreviewProb := crawler.getLivePreviewProb() @@ -278,6 +280,9 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { screenResult.SwipeStartTime = swipeStartTime.UnixMilli() screenResult.SwipeFinishTime = swipeFinishTime.UnixMilli() screenResult.TotalElapsed = time.Since(swipeFinishTime).Milliseconds() + screenResult.FetchVideoStartTime = fetchVideoStartTime.UnixMilli() + screenResult.FetchVideoFinishTime = fetchVideoFinishTime.UnixMilli() + screenResult.FetchVideoElapsed = fetchVideoFinishTime.Sub(fetchVideoStartTime).Milliseconds() var exitLive bool if crawler.isLiveTargetAchieved() { @@ -313,8 +318,11 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { Video: currentVideo, // log swipe timelines - SwipeStartTime: swipeStartTime.UnixMilli(), - SwipeFinishTime: swipeFinishTime.UnixMilli(), + SwipeStartTime: swipeStartTime.UnixMilli(), + SwipeFinishTime: swipeFinishTime.UnixMilli(), + FetchVideoStartTime: fetchVideoStartTime.UnixMilli(), + FetchVideoFinishTime: fetchVideoFinishTime.UnixMilli(), + FetchVideoElapsed: fetchVideoFinishTime.Sub(fetchVideoStartTime).Milliseconds(), } dExt.cacheStepData.screenResults[time.Now().String()] = screenResult From bc265e87439b465277a29bf1a88303beab100f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Wed, 14 Aug 2024 11:44:12 +0800 Subject: [PATCH 58/60] fix: remove unnecessary code --- hrp/pkg/uixt/video_crawler.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 89b1a499..8a835394 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -210,9 +210,6 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { // 直播预览流线上概率 livePreviewProb := crawler.getLivePreviewProb() - // 直播预览流线上概率 - livePreviewProb := crawler.getLivePreviewProb() - switch currentVideo.Type { case VideoType_PreviewLive: isFeed = true From 37f3d6d689a3697ae9f263fe5f32569980023913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Wed, 21 Aug 2024 14:27:53 +0800 Subject: [PATCH 59/60] fix: decrease the maximum simulation duration to ten minutes --- hrp/pkg/uixt/video_crawler.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hrp/pkg/uixt/video_crawler.go b/hrp/pkg/uixt/video_crawler.go index 8a835394..8af4d8a7 100644 --- a/hrp/pkg/uixt/video_crawler.go +++ b/hrp/pkg/uixt/video_crawler.go @@ -271,8 +271,8 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { currentVideo.LiveType = screenResult.imageResult.LiveType } - // simulation watch feed video - simulationPlayDuration := math.Min(float64(currentVideo.PlayDuration), 7200000) + // simulation watch live video + simulationPlayDuration := math.Min(float64(currentVideo.PlayDuration), 300000) sleepStrict(swipeFinishTime, int64(simulationPlayDuration)) screenResult.Video = currentVideo @@ -327,7 +327,7 @@ func (dExt *DriverExt) VideoCrawler(configs *VideoCrawlerConfigs) (err error) { dExt.cacheStepData.screenResults[time.Now().String()] = screenResult // simulation watch feed video - simulationPlayDuration := math.Min(float64(currentVideo.PlayDuration), 7200000) + simulationPlayDuration := math.Min(float64(currentVideo.PlayDuration), 600000) sleepStrict(swipeFinishTime, int64(simulationPlayDuration)) screenResult.TotalElapsed = time.Since(swipeFinishTime).Milliseconds() } From 3ed72a8b2537c12cd430b44011163aaa0b848f29 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Wed, 21 Aug 2024 14:37:30 +0800 Subject: [PATCH 60/60] fix: duplicate assign --- hrp/pkg/uixt/tap.go | 3 +-- hrp/plugin.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/hrp/pkg/uixt/tap.go b/hrp/pkg/uixt/tap.go index f52b5785..30269fcf 100644 --- a/hrp/pkg/uixt/tap.go +++ b/hrp/pkg/uixt/tap.go @@ -2,6 +2,7 @@ package uixt import ( "fmt" + "github.com/rs/zerolog/log" ) @@ -110,8 +111,6 @@ func (dExt *DriverExt) DoubleTapXY(x, y float64) error { x = x * float64(dExt.windowSize.Height) y = y * float64(dExt.windowSize.Width) } - x = x * float64(dExt.windowSize.Width) - y = y * float64(dExt.windowSize.Height) return dExt.Driver.DoubleTapFloat(x, y) } diff --git a/hrp/plugin.go b/hrp/plugin.go index 54b1cab4..ea9afad9 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -6,8 +6,6 @@ import ( "strings" "sync" - "github.com/httprunner/httprunner/v4/hrp/internal/sdk" - "github.com/httprunner/funplugin" "github.com/httprunner/funplugin/myexec" "github.com/pkg/errors" @@ -15,6 +13,7 @@ import ( "github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/internal/env" + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" ) const (