mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-08 17:09:33 +08:00
change: convert case compatiblity
This commit is contained in:
+173
@@ -0,0 +1,173 @@
|
|||||||
|
package hrp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/code"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConvertCaseCompatibility converts TestCase compatible with Golang engine style
|
||||||
|
func ConvertCaseCompatibility(tc *TestCaseDef) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if p := recover(); p != nil {
|
||||||
|
err = fmt.Errorf("[MakeCompat] convert compat testcase error: %v", p)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for _, step := range tc.Steps {
|
||||||
|
// 1. deal with request body compatibility
|
||||||
|
convertCompatRequestBody(step.Request)
|
||||||
|
|
||||||
|
// 2. deal with validators compatibility
|
||||||
|
err = convertCompatValidator(step.Validators)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. deal with extract expr including hyphen
|
||||||
|
convertExtract(step.Extract)
|
||||||
|
|
||||||
|
// 4. deal with mobile step compatibility
|
||||||
|
if step.Android != nil {
|
||||||
|
convertCompatMobileStep(step.Android)
|
||||||
|
} else if step.IOS != nil {
|
||||||
|
convertCompatMobileStep(step.IOS)
|
||||||
|
} else if step.Harmony != nil {
|
||||||
|
convertCompatMobileStep(step.Harmony)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertCompatRequestBody(request *Request) {
|
||||||
|
if request != nil && request.Body == nil {
|
||||||
|
if request.Json != nil {
|
||||||
|
if request.Headers == nil {
|
||||||
|
request.Headers = make(map[string]string)
|
||||||
|
}
|
||||||
|
request.Headers["Content-Type"] = "application/json; charset=utf-8"
|
||||||
|
request.Body = request.Json
|
||||||
|
request.Json = nil
|
||||||
|
} else if request.Data != nil {
|
||||||
|
request.Body = request.Data
|
||||||
|
request.Data = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertCompatValidator(Validators []interface{}) (err error) {
|
||||||
|
for i, iValidator := range Validators {
|
||||||
|
if _, ok := iValidator.(Validator); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var validatorMap map[string]interface{}
|
||||||
|
if v, ok := iValidator.(map[string]interface{}); ok {
|
||||||
|
validatorMap = v
|
||||||
|
} else if v, ok := iValidator.(map[interface{}]interface{}); ok {
|
||||||
|
// convert map[interface{}]interface{} to map[string]interface{}
|
||||||
|
validatorMap = make(map[string]interface{})
|
||||||
|
for key, value := range v {
|
||||||
|
strKey := fmt.Sprintf("%v", key)
|
||||||
|
validatorMap[strKey] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.Wrap(code.InvalidCaseError,
|
||||||
|
fmt.Sprintf("unexpected validator format: %v", iValidator))
|
||||||
|
}
|
||||||
|
|
||||||
|
validator := Validator{}
|
||||||
|
iCheck, checkExisted := validatorMap["check"]
|
||||||
|
iAssert, assertExisted := validatorMap["assert"]
|
||||||
|
iExpect, expectExisted := validatorMap["expect"]
|
||||||
|
// validator check priority: Golang > Python engine style
|
||||||
|
if checkExisted && assertExisted && expectExisted {
|
||||||
|
// Golang engine style
|
||||||
|
validator.Check = iCheck.(string)
|
||||||
|
validator.Assert = iAssert.(string)
|
||||||
|
validator.Expect = iExpect
|
||||||
|
if iMsg, msgExisted := validatorMap["msg"]; msgExisted {
|
||||||
|
validator.Message = iMsg.(string)
|
||||||
|
}
|
||||||
|
validator.Check = convertJmespathExpr(validator.Check)
|
||||||
|
Validators[i] = validator
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(validatorMap) == 1 {
|
||||||
|
// Python engine style
|
||||||
|
for assertMethod, iValidatorContent := range validatorMap {
|
||||||
|
validatorContent := iValidatorContent.([]interface{})
|
||||||
|
if len(validatorContent) > 3 {
|
||||||
|
return errors.Wrap(code.InvalidCaseError,
|
||||||
|
fmt.Sprintf("unexpected validator format: %v", validatorMap))
|
||||||
|
}
|
||||||
|
validator.Check = validatorContent[0].(string)
|
||||||
|
validator.Assert = assertMethod
|
||||||
|
validator.Expect = validatorContent[1]
|
||||||
|
if len(validatorContent) == 3 {
|
||||||
|
validator.Message = validatorContent[2].(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validator.Check = convertJmespathExpr(validator.Check)
|
||||||
|
Validators[i] = validator
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return errors.Wrap(code.InvalidCaseError,
|
||||||
|
fmt.Sprintf("unexpected validator format: %v", validatorMap))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertExtract deals with extract expr including hyphen
|
||||||
|
func convertExtract(extract map[string]string) {
|
||||||
|
for key, value := range extract {
|
||||||
|
extract[key] = convertJmespathExpr(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertCompatMobileStep(mobileUI *MobileUI) {
|
||||||
|
if mobileUI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < len(mobileUI.Actions); i++ {
|
||||||
|
ma := mobileUI.Actions[i]
|
||||||
|
actionOptions := uixt.NewActionOptions(ma.GetOptions()...)
|
||||||
|
// append tap_cv params to screenshot_with_ui_types option
|
||||||
|
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 {
|
||||||
|
ma.ActionOptions.MaxRetryTimes = 10
|
||||||
|
}
|
||||||
|
// set default max_retry_times to 10 for swipe_to_tap_text
|
||||||
|
if ma.Method == uixt.ACTION_SwipeToTapText && actionOptions.MaxRetryTimes == 0 {
|
||||||
|
ma.ActionOptions.MaxRetryTimes = 10
|
||||||
|
}
|
||||||
|
if ma.Method == uixt.ACTION_Swipe {
|
||||||
|
ma.ActionOptions.Direction = ma.Params
|
||||||
|
}
|
||||||
|
mobileUI.Actions[i] = ma
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertJmespathExpr deals with limited jmespath expression conversion
|
||||||
|
func convertJmespathExpr(checkExpr string) string {
|
||||||
|
if strings.Contains(checkExpr, textExtractorSubRegexp) {
|
||||||
|
return checkExpr
|
||||||
|
}
|
||||||
|
checkItems := strings.Split(checkExpr, ".")
|
||||||
|
for i, checkItem := range checkItems {
|
||||||
|
checkItem = strings.Trim(checkItem, "\"")
|
||||||
|
lowerItem := strings.ToLower(checkItem)
|
||||||
|
if strings.HasPrefix(lowerItem, "content-") || lowerItem == "user-agent" {
|
||||||
|
checkItems[i] = fmt.Sprintf("\"%s\"", checkItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(checkItems, ".")
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
v5.0.0+2411092306
|
v5.0.0+2411092321
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func LoadCurlCase(path string) (*hrp.TestCaseDef, error) {
|
|||||||
}
|
}
|
||||||
tCase.Steps = append(tCase.Steps, tSteps...)
|
tCase.Steps = append(tCase.Steps, tSteps...)
|
||||||
}
|
}
|
||||||
err = tCase.MakeCompat()
|
err = hrp.ConvertCaseCompatibility(tCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ func (c *CaseHar) ToTestCase() (*hrp.TestCaseDef, error) {
|
|||||||
Config: c.prepareConfig(),
|
Config: c.prepareConfig(),
|
||||||
Steps: teststeps,
|
Steps: teststeps,
|
||||||
}
|
}
|
||||||
err = tCase.MakeCompat()
|
err = hrp.ConvertCaseCompatibility(tCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func LoadJSONCase(path string) (*hrp.TestCaseDef, error) {
|
|||||||
return nil, errors.New("invalid json case file, missing teststeps")
|
return nil, errors.New("invalid json case file, missing teststeps")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = caseJSON.MakeCompat()
|
err = hrp.ConvertCaseCompatibility(caseJSON)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func (c *CasePostman) ToTestCase() (*hrp.TestCaseDef, error) {
|
|||||||
Config: c.prepareConfig(),
|
Config: c.prepareConfig(),
|
||||||
Steps: teststeps,
|
Steps: teststeps,
|
||||||
}
|
}
|
||||||
err = tCase.MakeCompat()
|
err = hrp.ConvertCaseCompatibility(tCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func LoadYAMLCase(path string) (*hrp.TestCaseDef, error) {
|
|||||||
return nil, errors.New("invalid yaml file")
|
return nil, errors.New("invalid yaml file")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = caseJSON.MakeCompat()
|
err = hrp.ConvertCaseCompatibility(caseJSON)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-169
@@ -3,7 +3,6 @@ package hrp
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -11,15 +10,8 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/code"
|
"github.com/httprunner/httprunner/v4/hrp/code"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// define struct for testcase
|
|
||||||
type TestCaseDef struct {
|
|
||||||
Config *TConfig `json:"config" yaml:"config"`
|
|
||||||
Steps []*TStep `json:"teststeps" yaml:"teststeps"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ITestCase represents interface for testcases,
|
// ITestCase represents interface for testcases,
|
||||||
// includes TestCase and TestCasePath.
|
// includes TestCase and TestCasePath.
|
||||||
type ITestCase interface {
|
type ITestCase interface {
|
||||||
@@ -60,36 +52,6 @@ func (tc *TestCase) GetTestCase() (*TestCase, error) {
|
|||||||
return tc, nil
|
return tc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeCompat converts TestCase compatible with Golang engine style
|
|
||||||
func (tc *TestCaseDef) MakeCompat() (err error) {
|
|
||||||
defer func() {
|
|
||||||
if p := recover(); p != nil {
|
|
||||||
err = fmt.Errorf("[MakeCompat] convert compat testcase error: %v", p)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for _, step := range tc.Steps {
|
|
||||||
// 1. deal with request body compatibility
|
|
||||||
convertCompatRequestBody(step.Request)
|
|
||||||
|
|
||||||
// 2. deal with validators compatibility
|
|
||||||
err = convertCompatValidator(step.Validators)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. deal with extract expr including hyphen
|
|
||||||
convertExtract(step.Extract)
|
|
||||||
|
|
||||||
// 4. deal with mobile step compatibility
|
|
||||||
if step.Android != nil {
|
|
||||||
convertCompatMobileStep(step.Android)
|
|
||||||
} else if step.IOS != nil {
|
|
||||||
convertCompatMobileStep(step.IOS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc *TestCase) Dump2JSON(targetPath string) error {
|
func (tc *TestCase) Dump2JSON(targetPath string) error {
|
||||||
err := builtin.Dump2JSON(tc, targetPath)
|
err := builtin.Dump2JSON(tc, targetPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -106,13 +68,19 @@ func (tc *TestCase) Dump2YAML(targetPath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// define struct for testcase
|
||||||
|
type TestCaseDef struct {
|
||||||
|
Config *TConfig `json:"config" yaml:"config"`
|
||||||
|
Steps []*TStep `json:"teststeps" yaml:"teststeps"`
|
||||||
|
}
|
||||||
|
|
||||||
// loadISteps loads TestSteps([]IStep) from TSteps structs
|
// loadISteps loads TestSteps([]IStep) from TSteps structs
|
||||||
func (tc *TestCaseDef) loadISteps() (*TestCase, error) {
|
func (tc *TestCaseDef) loadISteps() (*TestCase, error) {
|
||||||
testCase := &TestCase{
|
testCase := &TestCase{
|
||||||
Config: tc.Config,
|
Config: tc.Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := tc.MakeCompat()
|
err := ConvertCaseCompatibility(tc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -280,133 +248,3 @@ func (tc *TestCaseDef) loadISteps() (*TestCase, error) {
|
|||||||
}
|
}
|
||||||
return testCase, nil
|
return testCase, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertCompatRequestBody(request *Request) {
|
|
||||||
if request != nil && request.Body == nil {
|
|
||||||
if request.Json != nil {
|
|
||||||
if request.Headers == nil {
|
|
||||||
request.Headers = make(map[string]string)
|
|
||||||
}
|
|
||||||
request.Headers["Content-Type"] = "application/json; charset=utf-8"
|
|
||||||
request.Body = request.Json
|
|
||||||
request.Json = nil
|
|
||||||
} else if request.Data != nil {
|
|
||||||
request.Body = request.Data
|
|
||||||
request.Data = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertCompatValidator(Validators []interface{}) (err error) {
|
|
||||||
for i, iValidator := range Validators {
|
|
||||||
if _, ok := iValidator.(Validator); ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var validatorMap map[string]interface{}
|
|
||||||
if v, ok := iValidator.(map[string]interface{}); ok {
|
|
||||||
validatorMap = v
|
|
||||||
} else if v, ok := iValidator.(map[interface{}]interface{}); ok {
|
|
||||||
// convert map[interface{}]interface{} to map[string]interface{}
|
|
||||||
validatorMap = make(map[string]interface{})
|
|
||||||
for key, value := range v {
|
|
||||||
strKey := fmt.Sprintf("%v", key)
|
|
||||||
validatorMap[strKey] = value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.Wrap(code.InvalidCaseError,
|
|
||||||
fmt.Sprintf("unexpected validator format: %v", iValidator))
|
|
||||||
}
|
|
||||||
|
|
||||||
validator := Validator{}
|
|
||||||
iCheck, checkExisted := validatorMap["check"]
|
|
||||||
iAssert, assertExisted := validatorMap["assert"]
|
|
||||||
iExpect, expectExisted := validatorMap["expect"]
|
|
||||||
// validator check priority: Golang > Python engine style
|
|
||||||
if checkExisted && assertExisted && expectExisted {
|
|
||||||
// Golang engine style
|
|
||||||
validator.Check = iCheck.(string)
|
|
||||||
validator.Assert = iAssert.(string)
|
|
||||||
validator.Expect = iExpect
|
|
||||||
if iMsg, msgExisted := validatorMap["msg"]; msgExisted {
|
|
||||||
validator.Message = iMsg.(string)
|
|
||||||
}
|
|
||||||
validator.Check = convertJmespathExpr(validator.Check)
|
|
||||||
Validators[i] = validator
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(validatorMap) == 1 {
|
|
||||||
// Python engine style
|
|
||||||
for assertMethod, iValidatorContent := range validatorMap {
|
|
||||||
validatorContent := iValidatorContent.([]interface{})
|
|
||||||
if len(validatorContent) > 3 {
|
|
||||||
return errors.Wrap(code.InvalidCaseError,
|
|
||||||
fmt.Sprintf("unexpected validator format: %v", validatorMap))
|
|
||||||
}
|
|
||||||
validator.Check = validatorContent[0].(string)
|
|
||||||
validator.Assert = assertMethod
|
|
||||||
validator.Expect = validatorContent[1]
|
|
||||||
if len(validatorContent) == 3 {
|
|
||||||
validator.Message = validatorContent[2].(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
validator.Check = convertJmespathExpr(validator.Check)
|
|
||||||
Validators[i] = validator
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return errors.Wrap(code.InvalidCaseError,
|
|
||||||
fmt.Sprintf("unexpected validator format: %v", validatorMap))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertExtract deals with extract expr including hyphen
|
|
||||||
func convertExtract(extract map[string]string) {
|
|
||||||
for key, value := range extract {
|
|
||||||
extract[key] = convertJmespathExpr(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertCompatMobileStep(mobileUI *MobileUI) {
|
|
||||||
if mobileUI == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := 0; i < len(mobileUI.Actions); i++ {
|
|
||||||
ma := mobileUI.Actions[i]
|
|
||||||
actionOptions := uixt.NewActionOptions(ma.GetOptions()...)
|
|
||||||
// append tap_cv params to screenshot_with_ui_types option
|
|
||||||
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 {
|
|
||||||
ma.ActionOptions.MaxRetryTimes = 10
|
|
||||||
}
|
|
||||||
// set default max_retry_times to 10 for swipe_to_tap_text
|
|
||||||
if ma.Method == uixt.ACTION_SwipeToTapText && actionOptions.MaxRetryTimes == 0 {
|
|
||||||
ma.ActionOptions.MaxRetryTimes = 10
|
|
||||||
}
|
|
||||||
if ma.Method == uixt.ACTION_Swipe {
|
|
||||||
ma.ActionOptions.Direction = ma.Params
|
|
||||||
}
|
|
||||||
mobileUI.Actions[i] = ma
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertJmespathExpr deals with limited jmespath expression conversion
|
|
||||||
func convertJmespathExpr(checkExpr string) string {
|
|
||||||
if strings.Contains(checkExpr, textExtractorSubRegexp) {
|
|
||||||
return checkExpr
|
|
||||||
}
|
|
||||||
checkItems := strings.Split(checkExpr, ".")
|
|
||||||
for i, checkItem := range checkItems {
|
|
||||||
checkItem = strings.Trim(checkItem, "\"")
|
|
||||||
lowerItem := strings.ToLower(checkItem)
|
|
||||||
if strings.HasPrefix(lowerItem, "content-") || lowerItem == "user-agent" {
|
|
||||||
checkItems[i] = fmt.Sprintf("\"%s\"", checkItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(checkItems, ".")
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user