mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-26 10:01:28 +08:00
fix: load testcase panic caused by config options
This commit is contained in:
68
config.go
68
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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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().
|
||||
|
||||
@@ -1 +1 @@
|
||||
v5.0.0-beta-2504251755
|
||||
v5.0.0-beta-2504271150
|
||||
|
||||
24
runner.go
24
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
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user