From d2976844fcfa0ef994d47f54df888441c6ade384 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Sun, 27 Apr 2025 11:50:50 +0800 Subject: [PATCH] fix: load testcase panic caused by config options --- config.go | 68 ++--- examples/uitest/demo_android_feed_swipe.json | 253 ++++++------------ .../uitest/demo_android_feed_swipe_test.go | 3 +- internal/version/VERSION | 2 +- runner.go | 24 +- step_ui.go | 4 +- uixt/option/action.go | 2 +- 7 files changed, 134 insertions(+), 222 deletions(-) diff --git a/config.go b/config.go index d5dd7139..3eaf08a6 100644 --- a/config.go +++ b/config.go @@ -4,7 +4,6 @@ import ( "reflect" "github.com/httprunner/httprunner/v5/internal/builtin" - "github.com/httprunner/httprunner/v5/uixt" "github.com/httprunner/httprunner/v5/uixt/option" ) @@ -23,26 +22,26 @@ func NewConfig(name string) *TConfig { // define struct for testcase config type TConfig struct { - Name string `json:"name" yaml:"name"` // required - Verify bool `json:"verify,omitempty" yaml:"verify,omitempty"` - BaseURL string `json:"base_url,omitempty" yaml:"base_url,omitempty"` // deprecated in v4.1, moved to env - Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"` // public request headers - Environs map[string]string `json:"environs,omitempty" yaml:"environs,omitempty"` // environment variables - Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"` // global variables - Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` - ParametersSetting *TParamsConfig `json:"parameters_setting,omitempty" yaml:"parameters_setting,omitempty"` - ThinkTimeSetting *ThinkTimeConfig `json:"think_time,omitempty" yaml:"think_time,omitempty"` - WebSocketSetting *WebSocketConfig `json:"websocket,omitempty" yaml:"websocket,omitempty"` - IOS []*uixt.IOSDevice `json:"ios,omitempty" yaml:"ios,omitempty"` - Android []*uixt.AndroidDevice `json:"android,omitempty" yaml:"android,omitempty"` - Harmony []*uixt.HarmonyDevice `json:"harmony,omitempty" yaml:"harmony,omitempty"` - RequestTimeout float32 `json:"request_timeout,omitempty" yaml:"request_timeout,omitempty"` // request timeout in seconds - CaseTimeout float32 `json:"case_timeout,omitempty" yaml:"case_timeout,omitempty"` // testcase timeout in seconds - Export []string `json:"export,omitempty" yaml:"export,omitempty"` - Weight int `json:"weight,omitempty" yaml:"weight,omitempty"` - Path string `json:"path,omitempty" yaml:"path,omitempty"` // testcase file path - PluginSetting *PluginConfig `json:"plugin,omitempty" yaml:"plugin,omitempty"` // plugin config - IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"` + Name string `json:"name" yaml:"name"` // required + Verify bool `json:"verify,omitempty" yaml:"verify,omitempty"` + BaseURL string `json:"base_url,omitempty" yaml:"base_url,omitempty"` // deprecated in v4.1, moved to env + Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"` // public request headers + Environs map[string]string `json:"environs,omitempty" yaml:"environs,omitempty"` // environment variables + Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"` // global variables + Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` + ParametersSetting *TParamsConfig `json:"parameters_setting,omitempty" yaml:"parameters_setting,omitempty"` + ThinkTimeSetting *ThinkTimeConfig `json:"think_time,omitempty" yaml:"think_time,omitempty"` + WebSocketSetting *WebSocketConfig `json:"websocket,omitempty" yaml:"websocket,omitempty"` + IOS []*option.IOSDeviceOptions `json:"ios,omitempty" yaml:"ios,omitempty"` + Android []*option.AndroidDeviceOptions `json:"android,omitempty" yaml:"android,omitempty"` + Harmony []*option.HarmonyDeviceOptions `json:"harmony,omitempty" yaml:"harmony,omitempty"` + RequestTimeout float32 `json:"request_timeout,omitempty" yaml:"request_timeout,omitempty"` // request timeout in seconds + CaseTimeout float32 `json:"case_timeout,omitempty" yaml:"case_timeout,omitempty"` // testcase timeout in seconds + Export []string `json:"export,omitempty" yaml:"export,omitempty"` + Weight int `json:"weight,omitempty" yaml:"weight,omitempty"` + Path string `json:"path,omitempty" yaml:"path,omitempty"` // testcase file path + PluginSetting *PluginConfig `json:"plugin,omitempty" yaml:"plugin,omitempty"` // plugin config + IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"` } func (c *TConfig) Get() *TConfig { @@ -120,63 +119,54 @@ func (c *TConfig) SetWebSocket(times, interval, timeout, size int64) *TConfig { func (c *TConfig) SetIOS(opts ...option.IOSDeviceOption) *TConfig { iosOptions := option.NewIOSDeviceOptions(opts...) - device := &uixt.IOSDevice{ - Options: iosOptions, - } // each device can have its own settings if iosOptions.UDID != "" { - c.IOS = append(c.IOS, device) + c.IOS = append(c.IOS, iosOptions) return c } // device UDID is not specified, settings will be shared if len(c.IOS) == 0 { - c.IOS = append(c.IOS, device) + c.IOS = append(c.IOS, iosOptions) } else { - c.IOS[0] = device + c.IOS[0] = iosOptions } return c } func (c *TConfig) SetHarmony(opts ...option.HarmonyDeviceOption) *TConfig { harmonyOptions := option.NewHarmonyDeviceOptions(opts...) - device := &uixt.HarmonyDevice{ - Options: harmonyOptions, - } // each device can have its own settings if harmonyOptions.ConnectKey != "" { - c.Harmony = append(c.Harmony, device) + c.Harmony = append(c.Harmony, harmonyOptions) return c } // device UDID is not specified, settings will be shared if len(c.Harmony) == 0 { - c.Harmony = append(c.Harmony, device) + c.Harmony = append(c.Harmony, harmonyOptions) } else { - c.Harmony[0] = device + c.Harmony[0] = harmonyOptions } return c } func (c *TConfig) SetAndroid(opts ...option.AndroidDeviceOption) *TConfig { uiaOptions := option.NewAndroidDeviceOptions(opts...) - device := &uixt.AndroidDevice{ - Options: uiaOptions, - } // each device can have its own settings if uiaOptions.SerialNumber != "" { - c.Android = append(c.Android, device) + c.Android = append(c.Android, uiaOptions) return c } // device UDID is not specified, settings will be shared if len(c.Android) == 0 { - c.Android = append(c.Android, device) + c.Android = append(c.Android, uiaOptions) } else { - c.Android[0] = device + c.Android[0] = uiaOptions } return c } diff --git a/examples/uitest/demo_android_feed_swipe.json b/examples/uitest/demo_android_feed_swipe.json index 7f8fe3bc..de33d6f4 100644 --- a/examples/uitest/demo_android_feed_swipe.json +++ b/examples/uitest/demo_android_feed_swipe.json @@ -1,6 +1,6 @@ { "config": { - "name": "直播_抖极_feed卡片_android", + "name": "点播_抖音_滑动场景_随机间隔_android", "variables": { "device": "${ENV(SerialNumber)}" }, @@ -8,91 +8,32 @@ { "serial": "$device", "log_on": true, - "close_popup": true + "adb_server_host": "localhost", + "adb_server_port": 5037, + "uia2_ip": "localhost", + "uia2_port": 6790, + "uia2_server_package_name": "io.appium.uiautomator2.server", + "uia2_server_test_package_name": "io.appium.uiautomator2.server.test" } ] }, "teststeps": [ { - "name": "清理android无关进程", + "name": "启动抖音", "android": { + "os_type": "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.lite" + "params": "com.ss.android.ugc.aweme" }, { "method": "sleep", "params": 10 - }, - { - "method": "close_popups", - "options": { - "max_retry_times": 2 - } } ] }, @@ -100,139 +41,119 @@ { "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" + "expect": "com.ss.android.ugc.aweme", + "msg": "app [com.ss.android.ugc.aweme] should be in foreground" } ] }, - { - "name": "处理通讯录弹窗", - "android": { - "actions": [ - { - "method": "tap_ocr", - "params": "拒绝", - "ignore_NotFoundError": true - } - ] - } - }, { "name": "处理青少年弹窗", "android": { + "os_type": "android", "actions": [ - { - "method": "sleep", - "params": 5 - }, { "method": "tap_ocr", "params": "我知道了", - "ignore_NotFoundError": true + "options": { + "max_retry_times": 1, + "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": "下滑进入下一个直播间", + "name": "滑动 Feed 3 次,随机间隔 0-5s", + "loops": 3, "android": { + "os_type": "android", "actions": [ { "method": "swipe", - "params": [ - 0.5, - 0.7, - 0.5, - 0.1 - ], - "identifier": "slide_in_live_new" + "params": "up", + "options": { + "max_retry_times": 1 + } }, { - "method": "sleep", - "params": 30 + "method": "sleep_random", + "params": [ + 0, + 5 + ] } ] } }, { - "name": "返回主界面,并打开本地时间戳", + "name": "滑动 Feed 1 次,随机间隔 5-10s", + "loops": 1, "android": { + "os_type": "android", + "actions": [ + { + "method": "swipe", + "params": "up", + "options": { + "max_retry_times": 1 + } + }, + { + "method": "sleep_random", + "params": [ + 5, + 10 + ] + } + ] + } + }, + { + "name": "滑动 Feed 10 次,70% 随机间隔 0-5s,30% 随机间隔 5-10s", + "loops": 10, + "android": { + "os_type": "android", + "actions": [ + { + "method": "swipe", + "params": "up", + "options": { + "max_retry_times": 1 + } + }, + { + "method": "sleep_random", + "params": [ + 0, + 5, + 0.7, + 5, + 10, + 0.3 + ] + } + ] + } + }, + { + "name": "exit", + "android": { + "os_type": "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", - "params": 10 + "params": "com.ss.android.ugc.aweme" } ] }, "validate": [ { - "check": "ui_ocr", - "assert": "exists", - "expect": "17", - "msg": "打开本地时间戳失败" + "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" } ] } ] -} \ No newline at end of file +} diff --git a/examples/uitest/demo_android_feed_swipe_test.go b/examples/uitest/demo_android_feed_swipe_test.go index a4080c40..40d881c0 100644 --- a/examples/uitest/demo_android_feed_swipe_test.go +++ b/examples/uitest/demo_android_feed_swipe_test.go @@ -15,7 +15,8 @@ func TestAndroidDouyinFeedTest(t *testing.T) { WithVariables(map[string]interface{}{ "device": "${ENV(SerialNumber)}", }). - SetAndroid(option.WithSerialNumber("$device")), + SetAndroid(option.WithSerialNumber("$device"), + option.WithAdbLogOn(true)), TestSteps: []hrp.IStep{ hrp.NewStep("启动抖音"). Android(). diff --git a/internal/version/VERSION b/internal/version/VERSION index cba4e290..ee7d2483 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-beta-2504251755 +v5.0.0-beta-2504271150 diff --git a/runner.go b/runner.go index f850ebae..69d3bf18 100644 --- a/runner.go +++ b/runner.go @@ -419,14 +419,14 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) { r.parametersIterator = parametersIterator // parse android devices config - for _, androidDevice := range parsedConfig.Android { - err := r.parseDeviceConfig(androidDevice, parsedConfig.Variables) + for _, androidDeviceOptions := range parsedConfig.Android { + err := r.parseDeviceConfig(androidDeviceOptions, parsedConfig.Variables) if err != nil { return nil, errors.Wrap(code.InvalidCaseError, fmt.Sprintf("parse android config failed: %v", err)) } - device, err := uixt.NewAndroidDevice(androidDevice.Options.Options()...) + device, err := uixt.NewAndroidDevice(androidDeviceOptions.Options()...) if err != nil { return nil, errors.Wrap(err, "init android device failed") } @@ -436,17 +436,17 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) { } driverExt := uixt.NewXTDriver(driver, ai.WithCVService(ai.CVServiceTypeVEDEM)) - r.uixtDrivers[androidDevice.Options.SerialNumber] = driverExt + r.uixtDrivers[androidDeviceOptions.SerialNumber] = driverExt } // parse iOS devices config - for _, iosDevice := range parsedConfig.IOS { - err := r.parseDeviceConfig(iosDevice, parsedConfig.Variables) + for _, iosDeviceOptions := range parsedConfig.IOS { + err := r.parseDeviceConfig(iosDeviceOptions, parsedConfig.Variables) if err != nil { return nil, errors.Wrap(code.InvalidCaseError, fmt.Sprintf("parse ios config failed: %v", err)) } - device, err := uixt.NewIOSDevice(iosDevice.Options.Options()...) + device, err := uixt.NewIOSDevice(iosDeviceOptions.Options()...) if err != nil { return nil, errors.Wrap(err, "init ios device failed") } @@ -456,17 +456,17 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) { } driverExt := uixt.NewXTDriver(driver, ai.WithCVService(ai.CVServiceTypeVEDEM)) - r.uixtDrivers[iosDevice.Options.UDID] = driverExt + r.uixtDrivers[iosDeviceOptions.UDID] = driverExt } // parse harmony devices config - for _, harmonyDevice := range parsedConfig.Harmony { - err := r.parseDeviceConfig(harmonyDevice, parsedConfig.Variables) + for _, harmonyDeviceOptions := range parsedConfig.Harmony { + err := r.parseDeviceConfig(harmonyDeviceOptions, parsedConfig.Variables) if err != nil { return nil, errors.Wrap(code.InvalidCaseError, fmt.Sprintf("parse harmony config failed: %v", err)) } - device, err := uixt.NewHarmonyDevice(harmonyDevice.Options.Options()...) + device, err := uixt.NewHarmonyDevice(harmonyDeviceOptions.Options()...) if err != nil { return nil, errors.Wrap(err, "init harmony device failed") } @@ -476,7 +476,7 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) { } driverExt := uixt.NewXTDriver(driver, ai.WithCVService(ai.CVServiceTypeVEDEM)) - r.uixtDrivers[harmonyDevice.Options.ConnectKey] = driverExt + r.uixtDrivers[harmonyDeviceOptions.ConnectKey] = driverExt } return parsedConfig, nil diff --git a/step_ui.go b/step_ui.go index 6dc5fc0b..dd0ae042 100644 --- a/step_ui.go +++ b/step_ui.go @@ -15,8 +15,8 @@ import ( ) type MobileUI struct { - OSType string `json:"os_type,omitempty" yaml:"os_type,omitempty"` // ios or harmony or android - Serial string `json:"serial,omitempty" yaml:"serial,omitempty"` // android serial or ios udid + OSType string `json:"os_type,omitempty" yaml:"os_type,omitempty"` // mobile device os type + Serial string `json:"serial,omitempty" yaml:"serial,omitempty"` // mobile device serial number uixt.MobileAction `yaml:",inline"` Actions []uixt.MobileAction `json:"actions,omitempty" yaml:"actions,omitempty"` } diff --git a/uixt/option/action.go b/uixt/option/action.go index 72f04c18..702e9062 100644 --- a/uixt/option/action.go +++ b/uixt/option/action.go @@ -8,7 +8,7 @@ import ( ) type ActionOptions struct { - Context context.Context + Context context.Context `json:"-" yaml:"-"` // log Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` // used to identify the action in log