refactor: TestCase/TestCaseDef/StepConfig models

This commit is contained in:
lilong.129
2024-11-09 20:15:40 +08:00
parent 2587bbee82
commit 8846e84d19
31 changed files with 621 additions and 563 deletions

View File

@@ -357,7 +357,7 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
startTime := time.Now()
for _, step := range testcase.TestSteps {
// parse step struct
err = sessionRunner.parseStepStruct(step)
err = sessionRunner.parseStep(step)
if err != nil {
log.Error().Err(err).Msg("parse step struct failed")
}
@@ -406,8 +406,9 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
if stepResult.StepType == stepTypeTransaction {
// transaction
// FIXME: support nested transactions
if step.Struct().Transaction.Type == transactionEnd { // only record when transaction ends
b.RecordTransaction(step.Struct().Transaction.Name, transactionSuccess, stepResult.Elapsed, 0)
stepTransaction := step.(*StepTransaction)
if stepTransaction.Transaction.Type == transactionEnd { // only record when transaction ends
b.RecordTransaction(stepTransaction.Name(), transactionSuccess, stepResult.Elapsed, 0)
transactionSuccess = true // reset flag for next transaction
}
} else if stepResult.StepType == stepTypeRendezvous {

View File

@@ -16,8 +16,7 @@ func NewConfig(name string) *TConfig {
}
}
// TConfig represents config data structure for testcase.
// Each testcase should contain one config part.
// define struct for testcase config
type TConfig struct {
Name string `json:"name" yaml:"name"` // required
Verify bool `json:"verify,omitempty" yaml:"verify,omitempty"`

View File

@@ -1 +1 @@
v5.0.0+2411072100
v5.0.0+2411092015

View File

@@ -51,8 +51,8 @@ func TestLoadTestCases(t *testing.T) {
}
func TestLoadCase(t *testing.T) {
tcJSON := &TestCase{}
tcYAML := &TestCase{}
tcJSON := &TestCaseDef{}
tcYAML := &TestCaseDef{}
err := LoadFileObject(demoTestCaseWithPluginJSONPath, tcJSON)
if !assert.NoError(t, err) {
t.Fatal()
@@ -68,10 +68,10 @@ func TestLoadCase(t *testing.T) {
if !assert.Equal(t, tcJSON.Config.BaseURL, tcYAML.Config.BaseURL) {
t.Fatal()
}
if !assert.Equal(t, tcJSON.Steps[1].Name, tcYAML.Steps[1].Name) {
if !assert.Equal(t, tcJSON.Steps[1].StepName, tcYAML.Steps[1].StepName) {
t.Fatal()
}
if !assert.Equal(t, tcJSON.Steps[1].Request, tcYAML.Steps[1].Request) {
if !assert.Equal(t, tcJSON.Steps[1].Request, tcJSON.Steps[1].Request) {
t.Fatal()
}
}

70
hrp/models.go Normal file
View File

@@ -0,0 +1,70 @@
package hrp
// define struct for testcase
type TestCaseDef struct {
Config *TConfig `json:"config" yaml:"config"`
Steps []*TStep `json:"teststeps" yaml:"teststeps"`
}
type StepConfig struct {
StepName string `json:"name" yaml:"name"` // required
Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"`
SetupHooks []string `json:"setup_hooks,omitempty" yaml:"setup_hooks,omitempty"`
TeardownHooks []string `json:"teardown_hooks,omitempty" yaml:"teardown_hooks,omitempty"`
Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"`
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
StepExport []string `json:"export,omitempty" yaml:"export,omitempty"`
Loops int `json:"loops,omitempty" yaml:"loops,omitempty"`
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
}
// define struct for teststep
type TStep struct {
StepConfig `json:",inline" yaml:",inline"`
Request *Request `json:"request,omitempty" yaml:"request,omitempty"`
API interface{} `json:"api,omitempty" yaml:"api,omitempty"` // *APIPath or *API
TestCase interface{} `json:"testcase,omitempty" yaml:"testcase,omitempty"` // *TestCasePath or *TestCase
Transaction *Transaction `json:"transaction,omitempty" yaml:"transaction,omitempty"`
Rendezvous *Rendezvous `json:"rendezvous,omitempty" yaml:"rendezvous,omitempty"`
ThinkTime *ThinkTime `json:"think_time,omitempty" yaml:"think_time,omitempty"`
WebSocket *WebSocketAction `json:"websocket,omitempty" yaml:"websocket,omitempty"`
Android *MobileUI `json:"android,omitempty" yaml:"android,omitempty"`
Harmony *MobileUI `json:"harmony,omitempty" yaml:"harmony,omitempty"`
IOS *MobileUI `json:"ios,omitempty" yaml:"ios,omitempty"`
Shell *Shell `json:"shell,omitempty" yaml:"shell,omitempty"`
}
// one step contains one or multiple actions
type ActionResult struct {
Name string `json:"name"` // action name
StartTime int64 `json:"start_time"` // action start time
Elapsed int64 `json:"elapsed_ms"` // action elapsed time(ms)
Error error `json:"error"` // action execution result
}
// one testcase contains one or multiple steps
type StepResult struct {
Name string `json:"name" yaml:"name"` // step name
Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` // 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
Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms)
HttpStat map[string]int64 `json:"httpstat,omitempty" yaml:"httpstat,omitempty"` // httpstat in millisecond(ms)
Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // step data
ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length
ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables
Actions []*ActionResult `json:"actions,omitempty" yaml:"actions,omitempty"` // store action execution info
Attachments interface{} `json:"attachments,omitempty" yaml:"attachments,omitempty"` // store extra step information, such as error message or screenshots
}
// IStep represents interface for all types for teststeps, includes:
// StepRequest, StepRequestWithOptionalArgs, StepRequestValidation, StepRequestExtraction,
// StepTestCaseWithOptionalArgs,
// StepTransaction, StepRendezvous, StepWebSocket.
type IStep interface {
Name() string
Type() StepType
Config() *StepConfig
Run(*SessionRunner) (*StepResult, error)
}

View File

@@ -94,12 +94,12 @@ func init() {
}
// LoadCurlCase loads testcase from one or more curl commands in .txt file
func LoadCurlCase(path string) (*hrp.TestCase, error) {
func LoadCurlCase(path string) (*hrp.TestCaseDef, error) {
cmds, err := readFileLines(path)
if err != nil {
return nil, err
}
tCase := &hrp.TestCase{
tCase := &hrp.TestCaseDef{
Config: &hrp.TConfig{
Name: "testcase converted from curl command",
},
@@ -307,7 +307,7 @@ type stepFromCurl struct {
}
func (s *stepFromCurl) makeRequestName(c CaseCurl) error {
s.Name = c.Get(originCmdKey, 0)
s.StepName = c.Get(originCmdKey, 0)
return nil
}

View File

@@ -18,7 +18,7 @@ func TestLoadCurlCase(t *testing.T) {
}
// curl httpbin.org
if !assert.Equal(t, "curl httpbin.org", tCase.Steps[0].Name) {
if !assert.Equal(t, "curl httpbin.org", tCase.Steps[0].StepName) {
t.Fatal()
}
if !assert.EqualValues(t, "GET", tCase.Steps[0].Request.Method) {

View File

@@ -357,7 +357,7 @@ type TestResult struct {
// ==================== model definition ends here ====================
func LoadHARCase(path string) (*hrp.TestCase, error) {
func LoadHARCase(path string) (*hrp.TestCaseDef, error) {
// load har file
caseHAR, err := loadCaseHAR(path)
if err != nil {
@@ -381,13 +381,13 @@ func loadCaseHAR(path string) (*CaseHar, error) {
}
// convert CaseHar to TestCase format
func (c *CaseHar) ToTestCase() (*hrp.TestCase, error) {
func (c *CaseHar) ToTestCase() (*hrp.TestCaseDef, error) {
teststeps, err := c.prepareTestSteps()
if err != nil {
return nil, err
}
tCase := &hrp.TestCase{
tCase := &hrp.TestCaseDef{
Config: c.prepareConfig(),
Steps: teststeps,
}
@@ -424,8 +424,10 @@ func (c *CaseHar) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
step := &stepFromHAR{
TStep: hrp.TStep{
Request: &hrp.Request{},
Validators: make([]interface{}, 0),
Request: &hrp.Request{},
StepConfig: hrp.StepConfig{
Validators: make([]interface{}, 0),
},
},
}
if err := step.makeRequestMethod(entry); err != nil {

View File

@@ -7,9 +7,9 @@ import (
"github.com/httprunner/httprunner/v4/hrp"
)
func LoadJSONCase(path string) (*hrp.TestCase, error) {
func LoadJSONCase(path string) (*hrp.TestCaseDef, error) {
log.Info().Str("path", path).Msg("load json case file")
caseJSON := new(hrp.TestCase)
caseJSON := new(hrp.TestCaseDef)
err := hrp.LoadFileObject(path, caseJSON)
if err != nil {
return nil, errors.Wrap(err, "load json file failed")

View File

@@ -111,7 +111,7 @@ var contentTypeMap = map[string]string{
"xml": "application/xml",
}
func LoadPostmanCase(path string) (*hrp.TestCase, error) {
func LoadPostmanCase(path string) (*hrp.TestCaseDef, error) {
log.Info().Str("path", path).Msg("load postman case file")
casePostman, err := loadCasePostman(path)
if err != nil {
@@ -135,12 +135,12 @@ func loadCasePostman(path string) (*CasePostman, error) {
return casePostman, nil
}
func (c *CasePostman) ToTestCase() (*hrp.TestCase, error) {
func (c *CasePostman) ToTestCase() (*hrp.TestCaseDef, error) {
teststeps, err := c.prepareTestSteps()
if err != nil {
return nil, err
}
tCase := &hrp.TestCase{
tCase := &hrp.TestCaseDef{
Config: c.prepareConfig(),
Steps: teststeps,
}
@@ -199,8 +199,10 @@ func (c *CasePostman) prepareTestStep(item *TItem) (*hrp.TStep, error) {
step := &stepFromPostman{
TStep: hrp.TStep{
Request: &hrp.Request{},
Validators: make([]interface{}, 0),
Request: &hrp.Request{},
StepConfig: hrp.StepConfig{
Validators: make([]interface{}, 0),
},
},
}
if err := step.makeRequestName(item); err != nil {
@@ -233,7 +235,7 @@ type stepFromPostman struct {
// makeRequestName indicates the step name the same as item name
func (s *stepFromPostman) makeRequestName(item *TItem) error {
s.Name = item.Name
s.StepName = item.Name
return nil
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/httprunner/httprunner/v4/hrp"
)
func LoadSwaggerCase(path string) (*hrp.TestCase, error) {
func LoadSwaggerCase(path string) (*hrp.TestCaseDef, error) {
// load swagger file
caseSwagger := new(spec.Swagger)
err := hrp.LoadFileObject(path, caseSwagger)

View File

@@ -8,9 +8,9 @@ import (
"github.com/httprunner/httprunner/v4/hrp"
)
func LoadYAMLCase(path string) (*hrp.TestCase, error) {
func LoadYAMLCase(path string) (*hrp.TestCaseDef, error) {
// load yaml case file
caseJSON := new(hrp.TestCase)
caseJSON := new(hrp.TestCaseDef)
err := hrp.LoadFileObject(path, caseJSON)
if err != nil {
return nil, errors.Wrap(err, "load yaml file failed")

View File

@@ -114,7 +114,7 @@ type TCaseConverter struct {
fromFile string
profilePath string
outputDir string
tCase *hrp.TestCase
tCase *hrp.TestCaseDef
}
// LoadCase loads source file and convert to TCase type

View File

@@ -60,7 +60,7 @@ func TestLoadHARWithProfileOverride(t *testing.T) {
func TestMakeRequestWithProfile(t *testing.T) {
caseConverter := &TCaseConverter{
tCase: &hrp.TestCase{
tCase: &hrp.TestCaseDef{
Steps: []*hrp.TStep{
{
Request: &hrp.Request{
@@ -101,7 +101,7 @@ func TestMakeRequestWithProfile(t *testing.T) {
func TestMakeRequestWithProfileOverride(t *testing.T) {
caseConverter := &TCaseConverter{
tCase: &hrp.TestCase{
tCase: &hrp.TestCaseDef{
Steps: []*hrp.TStep{
{
Request: &hrp.Request{

View File

@@ -32,19 +32,6 @@ const (
ACTION_SetIme ActionMethod = "set_ime"
ACTION_GetSource ActionMethod = "get_source"
// UI validation
// selectors
SelectorName string = "ui_name"
SelectorLabel string = "ui_label"
SelectorOCR string = "ui_ocr"
SelectorImage string = "ui_image"
SelectorForegroundApp string = "ui_foreground_app"
// assertions
AssertionEqual string = "equal"
AssertionNotEqual string = "not_equal"
AssertionExists string = "exists"
AssertionNotExists string = "not_exists"
// UI handling
ACTION_Home ActionMethod = "home"
ACTION_TapXY ActionMethod = "tap_xy"
@@ -69,6 +56,21 @@ const (
ACTION_DownloadApp ActionMethod = "download_app"
)
const (
// UI validation
// selectors
SelectorName string = "ui_name"
SelectorLabel string = "ui_label"
SelectorOCR string = "ui_ocr"
SelectorImage string = "ui_image"
SelectorForegroundApp string = "ui_foreground_app"
// assertions
AssertionEqual string = "equal"
AssertionNotEqual string = "not_equal"
AssertionExists string = "exists"
AssertionNotExists string = "not_exists"
)
type MobileAction struct {
Method ActionMethod `json:"method,omitempty" yaml:"method,omitempty"`
Params interface{} `json:"params,omitempty" yaml:"params,omitempty"`
@@ -741,7 +743,7 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
case ACTION_ClosePopups:
return dExt.ClosePopupsHandler()
case ACTION_EndToEndDelay:
dExt.CollectEndToEndDelay(action.GetOptions()...)
CollectEndToEndDelay(dExt, action.GetOptions()...)
return nil
}
return nil

View File

@@ -476,7 +476,7 @@ func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption {
}
}
// current implemeted device: IOSDevice, AndroidDevice
// current implemeted device: IOSDevice, AndroidDevice, HarmonyDevice
type IDevice interface {
Init() error // init android device
UUID() string // ios udid or android serial

View File

@@ -28,7 +28,7 @@ type EndToEndDelay struct {
Timelines []timeLog `json:"timelines"`
}
func (dExt *DriverExt) CollectEndToEndDelay(options ...ActionOption) {
func CollectEndToEndDelay(dExt *DriverExt, options ...ActionOption) {
dataOptions := NewActionOptions(options...)
startTime := time.Now()

View File

@@ -600,7 +600,7 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCa
return summary, errors.Wrap(code.InterruptError, "session runner interrupted")
default:
// parse step struct
err = r.parseStepStruct(step)
err = r.parseStep(step)
if err != nil {
log.Error().Err(err).Msg("parse step struct failed")
if r.caseRunner.hrpRunner.failfast {
@@ -614,7 +614,7 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCa
stepStartTime := time.Now()
// run times of step
loopTimes := step.Struct().Loops
loopTimes := step.Config().Loops
if loopTimes < 0 {
log.Warn().Int("loops", loopTimes).Msg("loop times should be positive, set to 1")
loopTimes = 1
@@ -681,12 +681,12 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCa
return summary, nil
}
func (r *SessionRunner) parseStepStruct(step IStep) error {
stepStruct := step.Struct()
func (r *SessionRunner) parseStep(step IStep) error {
stepConfig := step.Config()
// update step variables: merges step variables with config variables and session variables
// variables priority: step variables > session variables (extracted variables from previous steps)
overrideVars := mergeVariables(stepStruct.Variables, r.sessionVariables)
overrideVars := mergeVariables(stepConfig.Variables, r.sessionVariables)
// step variables > testcase config variables
overrideVars = mergeVariables(overrideVars, r.caseRunner.Config.Variables)
@@ -697,19 +697,19 @@ func (r *SessionRunner) parseStepStruct(step IStep) error {
Err(err).Msg("parse step variables failed")
return errors.Wrap(err, "parse step variables failed")
}
stepStruct.Variables = parsedVariables
stepConfig.Variables = parsedVariables
// parse step name
parsedName, err := r.caseRunner.parser.ParseString(
stepStruct.Name, stepStruct.Variables)
stepConfig.StepName, stepConfig.Variables)
if err != nil {
parsedName = step.Name()
}
stepStruct.Name = convertString(parsedName)
stepConfig.StepName = convertString(parsedName)
// parse step validators
var parsedValidators []interface{}
for _, iValidator := range stepStruct.Validators {
for _, iValidator := range stepConfig.Validators {
validator, ok := iValidator.(Validator)
if !ok {
return errors.New("validator type error")
@@ -725,13 +725,13 @@ func (r *SessionRunner) parseStepStruct(step IStep) error {
// parse validator expect
validator.Expect, err = r.caseRunner.parser.Parse(
validator.Expect, stepStruct.Variables)
validator.Expect, stepConfig.Variables)
if err != nil {
return errors.Wrap(err, "failed to parse validator expect")
}
parsedValidators = append(parsedValidators, validator)
}
stepStruct.Validators = parsedValidators
stepConfig.Validators = parsedValidators
return nil
}

View File

@@ -250,15 +250,15 @@ func TestSessionRunner(t *testing.T) {
caseRunner, _ := NewRunner(t).NewCaseRunner(testcase)
sessionRunner := caseRunner.NewSession()
step := testcase.TestSteps[0]
if !assert.Equal(t, step.Struct().Variables["varFoo"], "${max($a, $b)}") {
if !assert.Equal(t, step.Config().Variables["varFoo"], "${max($a, $b)}") {
t.Fatal()
}
err := sessionRunner.parseStepStruct(step)
err := sessionRunner.parseStep(step)
if err != nil {
t.Fatal()
}
if !assert.Equal(t, step.Struct().Variables["varFoo"], 34.5) {
if !assert.Equal(t, step.Config().Variables["varFoo"], 34.5) {
t.Fatal()
}
}

View File

@@ -18,63 +18,3 @@ const (
stepTypeSuffixExtraction StepType = "_extraction"
stepTypeSuffixValidation StepType = "_validation"
)
// one step contains one or multiple actions
type ActionResult struct {
Name string `json:"name"` // action name
StartTime int64 `json:"start_time"` // action start time
Elapsed int64 `json:"elapsed_ms"` // action elapsed time(ms)
Error error `json:"error"` // action execution result
}
// one testcase contains one or multiple steps
type StepResult struct {
Name string `json:"name" yaml:"name"` // step name
Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` // 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
Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms)
HttpStat map[string]int64 `json:"httpstat,omitempty" yaml:"httpstat,omitempty"` // httpstat in millisecond(ms)
Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // step data
ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length
ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables
Actions []*ActionResult `json:"actions,omitempty" yaml:"actions,omitempty"` // store action execution info
Attachments interface{} `json:"attachments,omitempty" yaml:"attachments,omitempty"` // store extra step information, such as error message or screenshots
}
// TStep represents teststep data structure.
// Each step maybe three different types: make one request or reference another api/testcase.
type TStep struct {
Name string `json:"name" yaml:"name"` // required
Request *Request `json:"request,omitempty" yaml:"request,omitempty"`
API interface{} `json:"api,omitempty" yaml:"api,omitempty"` // *APIPath or *API
TestCase interface{} `json:"testcase,omitempty" yaml:"testcase,omitempty"` // *TestCasePath or *TestCase
Transaction *Transaction `json:"transaction,omitempty" yaml:"transaction,omitempty"`
Rendezvous *Rendezvous `json:"rendezvous,omitempty" yaml:"rendezvous,omitempty"`
ThinkTime *ThinkTime `json:"think_time,omitempty" yaml:"think_time,omitempty"`
WebSocket *WebSocketAction `json:"websocket,omitempty" yaml:"websocket,omitempty"`
Android *MobileUI `json:"android,omitempty" yaml:"android,omitempty"`
Harmony *MobileUI `json:"harmony,omitempty" yaml:"harmony,omitempty"`
IOS *MobileUI `json:"ios,omitempty" yaml:"ios,omitempty"`
Shell *Shell `json:"shell,omitempty" yaml:"shell,omitempty"`
Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"`
SetupHooks []string `json:"setup_hooks,omitempty" yaml:"setup_hooks,omitempty"`
TeardownHooks []string `json:"teardown_hooks,omitempty" yaml:"teardown_hooks,omitempty"`
Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"`
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
Export []string `json:"export,omitempty" yaml:"export,omitempty"`
Loops int `json:"loops,omitempty" yaml:"loops,omitempty"`
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
}
// IStep represents interface for all types for teststeps, includes:
// StepRequest, StepRequestWithOptionalArgs, StepRequestValidation, StepRequestExtraction,
// StepTestCaseWithOptionalArgs,
// StepTransaction, StepRendezvous, StepWebSocket.
type IStep interface {
Name() string
Type() StepType
Struct() *TStep
Run(*SessionRunner) (*StepResult, error)
}

View File

@@ -59,29 +59,30 @@ func (path *APIPath) ToAPI() (*API, error) {
// StepAPIWithOptionalArgs implements IStep interface.
type StepAPIWithOptionalArgs struct {
step *TStep
StepConfig
API interface{} `json:"api,omitempty" yaml:"api,omitempty"` // *APIPath or *API
}
// TeardownHook adds a teardown hook for current teststep.
func (s *StepAPIWithOptionalArgs) TeardownHook(hook string) *StepAPIWithOptionalArgs {
s.step.TeardownHooks = append(s.step.TeardownHooks, hook)
s.TeardownHooks = append(s.TeardownHooks, hook)
return s
}
// Export specifies variable names to export from referenced api for current step.
func (s *StepAPIWithOptionalArgs) Export(names ...string) *StepAPIWithOptionalArgs {
api, ok := s.step.API.(*API)
api, ok := s.API.(*API)
if ok {
s.step.Export = append(api.Export, names...)
s.StepExport = append(api.Export, names...)
}
return s
}
func (s *StepAPIWithOptionalArgs) Name() string {
if s.step.Name != "" {
return s.step.Name
if s.StepName != "" {
return s.StepName
}
api, ok := s.step.API.(*API)
api, ok := s.API.(*API)
if ok {
return api.Name
}
@@ -92,8 +93,8 @@ func (s *StepAPIWithOptionalArgs) Type() StepType {
return stepTypeAPI
}
func (s *StepAPIWithOptionalArgs) Struct() *TStep {
return s.step
func (s *StepAPIWithOptionalArgs) Config() *StepConfig {
return &s.StepConfig
}
func (s *StepAPIWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepResult, err error) {
@@ -101,10 +102,10 @@ func (s *StepAPIWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepResult,
stepResult.StepType = stepTypeAPI
}()
// extend request with referenced API
api, _ := s.step.API.(*API)
step := &TStep{}
api, _ := s.API.(*API)
step := &StepRequestWithOptionalArgs{}
// deep copy step to avoid data racing
if err = copier.Copy(step, s.step); err != nil {
if err = copier.Copy(step, s.StepConfig); err != nil {
log.Error().Err(err).Msg("copy step failed")
return
}
@@ -114,10 +115,10 @@ func (s *StepAPIWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepResult,
}
// extend teststep with api, teststep will merge and override referenced api
func extendWithAPI(testStep *TStep, overriddenStep *API) {
func extendWithAPI(testStep *StepRequestWithOptionalArgs, overriddenStep *API) {
// override api name
if testStep.Name == "" {
testStep.Name = overriddenStep.Name
if testStep.StepName == "" {
testStep.StepName = overriddenStep.Name
}
// merge & override request
testStep.Request = overriddenStep.Request
@@ -128,7 +129,7 @@ func extendWithAPI(testStep *TStep, overriddenStep *API) {
// merge & override variables
testStep.Variables = mergeVariables(testStep.Variables, overriddenStep.Variables)
// merge & override extractors
testStep.Extract = mergeMap(testStep.Extract, overriddenStep.Extract)
testStep.StepConfig.Extract = mergeMap(testStep.StepConfig.Extract, overriddenStep.Extract)
// merge & override validators
testStep.Validators = mergeValidators(testStep.Validators, overriddenStep.Validators)
// merge & override setupHooks

View File

@@ -63,6 +63,7 @@ func initUIClient(serial, osType string) (client *uixt.DriverExt, err error) {
}
type MobileUI struct {
OSType string `json:"-" yaml:"-"` // ios or harmony or android
Serial string `json:"serial,omitempty" yaml:"serial,omitempty"` // android serial or ios udid
uixt.MobileAction `yaml:",inline"`
Actions []uixt.MobileAction `json:"actions,omitempty" yaml:"actions,omitempty"`
@@ -70,22 +71,35 @@ type MobileUI struct {
// StepMobile implements IStep interface.
type StepMobile struct {
step *TStep
StepConfig
stepObj *MobileUI
Android *MobileUI `json:"android,omitempty" yaml:"android,omitempty"`
Harmony *MobileUI `json:"harmony,omitempty" yaml:"harmony,omitempty"`
IOS *MobileUI `json:"ios,omitempty" yaml:"ios,omitempty"`
}
// uniform interface for all types of mobile systems
func (s *StepMobile) obj() *MobileUI {
if s.step.IOS != nil {
return s.step.IOS
} else if s.step.Harmony != nil {
return s.step.Harmony
if s.stepObj != nil {
return s.stepObj
}
return s.step.Android
if s.IOS != nil {
s.stepObj = s.IOS
s.stepObj.OSType = string(stepTypeIOS)
return s.stepObj
} else if s.Harmony != nil {
s.stepObj = s.Harmony
s.stepObj.OSType = string(stepTypeHarmony)
return s.stepObj
}
s.stepObj = s.Android
s.stepObj.OSType = string(stepTypeAndroid)
return s.stepObj
}
func (s *StepMobile) Serial(serial string) *StepMobile {
s.obj().Serial = serial
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) InstallApp(path string) *StepMobile {
@@ -117,7 +131,7 @@ func (s *StepMobile) Home() *StepMobile {
Method: uixt.ACTION_Home,
Params: nil,
})
return &StepMobile{step: s.step}
return s
}
// TapXY taps the point {X,Y}, X & Y is percentage of coordinates
@@ -129,7 +143,7 @@ func (s *StepMobile) TapXY(x, y float64, options ...uixt.ActionOption) *StepMobi
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
// TapAbsXY taps the point {X,Y}, X & Y is absolute coordinates
@@ -141,7 +155,7 @@ func (s *StepMobile) TapAbsXY(x, y float64, options ...uixt.ActionOption) *StepM
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
// Tap taps on the target element
@@ -153,7 +167,7 @@ func (s *StepMobile) Tap(params string, options ...uixt.ActionOption) *StepMobil
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
// TapByOCR taps on the target element by OCR recognition
@@ -165,7 +179,7 @@ func (s *StepMobile) TapByOCR(ocrText string, options ...uixt.ActionOption) *Ste
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
// TapByCV taps on the target element by CV recognition
@@ -177,7 +191,7 @@ func (s *StepMobile) TapByCV(imagePath string, options ...uixt.ActionOption) *St
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
// TapByUITypes taps on the target element specified by uiTypes, the higher the uiTypes, the higher the priority
@@ -188,7 +202,7 @@ func (s *StepMobile) TapByUITypes(options ...uixt.ActionOption) *StepMobile {
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
// DoubleTapXY double taps the point {X,Y}, X & Y is percentage of coordinates
@@ -198,7 +212,7 @@ func (s *StepMobile) DoubleTapXY(x, y float64, options ...uixt.ActionOption) *St
Params: []float64{x, y},
Options: uixt.NewActionOptions(options...),
})
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) DoubleTap(params string, options ...uixt.ActionOption) *StepMobile {
@@ -209,7 +223,7 @@ func (s *StepMobile) DoubleTap(params string, options ...uixt.ActionOption) *Ste
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) Back(options ...uixt.ActionOption) *StepMobile {
@@ -220,7 +234,7 @@ func (s *StepMobile) Back(options ...uixt.ActionOption) *StepMobile {
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) Swipe(sx, sy, ex, ey float64, options ...uixt.ActionOption) *StepMobile {
@@ -231,7 +245,7 @@ func (s *StepMobile) Swipe(sx, sy, ex, ey float64, options ...uixt.ActionOption)
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) SwipeUp(options ...uixt.ActionOption) *StepMobile {
@@ -242,7 +256,7 @@ func (s *StepMobile) SwipeUp(options ...uixt.ActionOption) *StepMobile {
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) SwipeDown(options ...uixt.ActionOption) *StepMobile {
@@ -253,7 +267,7 @@ func (s *StepMobile) SwipeDown(options ...uixt.ActionOption) *StepMobile {
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) SwipeLeft(options ...uixt.ActionOption) *StepMobile {
@@ -264,7 +278,7 @@ func (s *StepMobile) SwipeLeft(options ...uixt.ActionOption) *StepMobile {
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) SwipeRight(options ...uixt.ActionOption) *StepMobile {
@@ -275,7 +289,7 @@ func (s *StepMobile) SwipeRight(options ...uixt.ActionOption) *StepMobile {
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) SwipeToTapApp(appName string, options ...uixt.ActionOption) *StepMobile {
@@ -286,7 +300,7 @@ func (s *StepMobile) SwipeToTapApp(appName string, options ...uixt.ActionOption)
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) SwipeToTapText(text string, options ...uixt.ActionOption) *StepMobile {
@@ -297,7 +311,7 @@ func (s *StepMobile) SwipeToTapText(text string, options ...uixt.ActionOption) *
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) SwipeToTapTexts(texts interface{}, options ...uixt.ActionOption) *StepMobile {
@@ -308,7 +322,7 @@ func (s *StepMobile) SwipeToTapTexts(texts interface{}, options ...uixt.ActionOp
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) Input(text string, options ...uixt.ActionOption) *StepMobile {
@@ -319,7 +333,7 @@ func (s *StepMobile) Input(text string, options ...uixt.ActionOption) *StepMobil
}
s.obj().Actions = append(s.obj().Actions, action)
return &StepMobile{step: s.step}
return s
}
// Sleep specify sleep seconds after last action
@@ -329,7 +343,7 @@ func (s *StepMobile) Sleep(n float64) *StepMobile {
Params: n,
Options: nil,
})
return &StepMobile{step: s.step}
return s
}
// SleepRandom specify random sleeping seconds after last action
@@ -342,7 +356,7 @@ func (s *StepMobile) SleepRandom(params ...float64) *StepMobile {
Params: params,
Options: nil,
})
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) EndToEndDelay(options ...uixt.ActionOption) *StepMobile {
@@ -351,7 +365,7 @@ func (s *StepMobile) EndToEndDelay(options ...uixt.ActionOption) *StepMobile {
Params: nil,
Options: uixt.NewActionOptions(options...),
})
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) ScreenShot(options ...uixt.ActionOption) *StepMobile {
@@ -360,7 +374,7 @@ func (s *StepMobile) ScreenShot(options ...uixt.ActionOption) *StepMobile {
Params: nil,
Options: uixt.NewActionOptions(options...),
})
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) StartCamera() *StepMobile {
@@ -369,7 +383,7 @@ func (s *StepMobile) StartCamera() *StepMobile {
Params: nil,
Options: nil,
})
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) StopCamera() *StepMobile {
@@ -378,7 +392,7 @@ func (s *StepMobile) StopCamera() *StepMobile {
Params: nil,
Options: nil,
})
return &StepMobile{step: s.step}
return s
}
func (s *StepMobile) ClosePopups(options ...uixt.ActionOption) *StepMobile {
@@ -387,40 +401,37 @@ func (s *StepMobile) ClosePopups(options ...uixt.ActionOption) *StepMobile {
Params: nil,
Options: uixt.NewActionOptions(options...),
})
return &StepMobile{step: s.step}
return s
}
// Validate switches to step validation.
func (s *StepMobile) Validate() *StepMobileUIValidation {
return &StepMobileUIValidation{
step: s.step,
StepMobile: s,
Validators: make([]interface{}, 0),
}
}
func (s *StepMobile) Name() string {
return s.step.Name
return s.StepName
}
func (s *StepMobile) Type() StepType {
if s.step.Android != nil {
return stepTypeAndroid
} else if s.step.Harmony != nil {
return stepTypeHarmony
}
return stepTypeIOS
return StepType(s.obj().OSType)
}
func (s *StepMobile) Struct() *TStep {
return s.step
func (s *StepMobile) Config() *StepConfig {
return &s.StepConfig
}
func (s *StepMobile) Run(r *SessionRunner) (*StepResult, error) {
return runStepMobileUI(r, s.step)
return runStepMobileUI(r, s)
}
// StepMobileUIValidation implements IStep interface.
type StepMobileUIValidation struct {
step *TStep
*StepMobile
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
}
func (s *StepMobileUIValidation) AssertNameExists(expectedName string, msg ...string) *StepMobileUIValidation {
@@ -434,7 +445,7 @@ func (s *StepMobileUIValidation) AssertNameExists(expectedName string, msg ...st
} else {
v.Message = fmt.Sprintf("attribute name [%s] not found", expectedName)
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -449,7 +460,7 @@ func (s *StepMobileUIValidation) AssertNameNotExists(expectedName string, msg ..
} else {
v.Message = fmt.Sprintf("attribute name [%s] should not exist", expectedName)
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -464,7 +475,7 @@ func (s *StepMobileUIValidation) AssertLabelExists(expectedLabel string, msg ...
} else {
v.Message = fmt.Sprintf("attribute label [%s] not found", expectedLabel)
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -479,7 +490,7 @@ func (s *StepMobileUIValidation) AssertLabelNotExists(expectedLabel string, msg
} else {
v.Message = fmt.Sprintf("attribute label [%s] should not exist", expectedLabel)
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -494,7 +505,7 @@ func (s *StepMobileUIValidation) AssertOCRExists(expectedText string, msg ...str
} else {
v.Message = fmt.Sprintf("ocr text [%s] not found", expectedText)
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -509,7 +520,7 @@ func (s *StepMobileUIValidation) AssertOCRNotExists(expectedText string, msg ...
} else {
v.Message = fmt.Sprintf("ocr text [%s] should not exist", expectedText)
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -524,7 +535,7 @@ func (s *StepMobileUIValidation) AssertImageExists(expectedImagePath string, msg
} else {
v.Message = fmt.Sprintf("cv image [%s] not found", expectedImagePath)
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -539,7 +550,7 @@ func (s *StepMobileUIValidation) AssertImageNotExists(expectedImagePath string,
} else {
v.Message = fmt.Sprintf("cv image [%s] should not exist", expectedImagePath)
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -554,7 +565,7 @@ func (s *StepMobileUIValidation) AssertAppInForeground(packageName string, msg .
} else {
v.Message = fmt.Sprintf("app [%s] should be in foreground", packageName)
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -569,49 +580,53 @@ func (s *StepMobileUIValidation) AssertAppNotInForeground(packageName string, ms
} else {
v.Message = fmt.Sprintf("app [%s] should not be in foreground", packageName)
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
func (s *StepMobileUIValidation) Name() string {
return s.step.Name
return s.StepName
}
func (s *StepMobileUIValidation) Type() StepType {
if s.step.Android != nil {
return stepTypeAndroid + stepTypeSuffixValidation
}
return stepTypeIOS + stepTypeSuffixValidation
return s.StepMobile.Type() + stepTypeSuffixValidation
}
func (s *StepMobileUIValidation) Struct() *TStep {
return s.step
func (s *StepMobileUIValidation) Config() *StepConfig {
return &StepConfig{
StepName: s.StepName,
Variables: s.Variables,
Validators: s.Validators,
}
}
func (s *StepMobileUIValidation) Run(r *SessionRunner) (*StepResult, error) {
return runStepMobileUI(r, s.step)
return runStepMobileUI(r, s)
}
func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err error) {
var osType string
func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err error) {
var stepVariables map[string]interface{}
var stepValidators []interface{}
var ignorePopup bool
var mobileStep *MobileUI
if step.IOS != nil {
// ios step
osType = "ios"
mobileStep = step.IOS
} else if step.Harmony != nil {
// harmony step
osType = "harmony"
mobileStep = step.Harmony
} else {
// android step
osType = "android"
mobileStep = step.Android
switch stepMobile := step.(type) {
case *StepMobile:
mobileStep = stepMobile.obj()
stepVariables = stepMobile.Variables
ignorePopup = stepMobile.IgnorePopup
case *StepMobileUIValidation:
mobileStep = stepMobile.obj()
stepVariables = stepMobile.Variables
stepValidators = stepMobile.Validators
ignorePopup = stepMobile.StepMobile.IgnorePopup
default:
return nil, errors.New("invalid mobile UI step type")
}
// report GA event
go sdk.SendGA4Event("hrp_run_ui", map[string]interface{}{
"osType": osType,
"osType": mobileStep.OSType,
})
identifier := mobileStep.Identifier
@@ -628,15 +643,15 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
}
stepResult = &StepResult{
Name: step.Name,
Name: step.Name(),
Identifier: identifier,
StepType: StepType(osType),
StepType: step.Type(),
Success: false,
ContentSize: 0,
}
// init wda/uia driver
uiDriver, err := initUIClient(mobileStep.Serial, osType)
uiDriver, err := initUIClient(mobileStep.Serial, mobileStep.OSType)
if err != nil {
return
}
@@ -655,9 +670,9 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
}
// automatic handling of pop-up windows on each step finished
if !step.IgnorePopup && !s.IgnorePopup() {
if !ignorePopup && !s.IgnorePopup() {
if err2 := uiDriver.ClosePopupsHandler(); err2 != nil {
log.Error().Err(err2).Str("step", step.Name).Msg("auto handle popup failed")
log.Error().Err(err2).Str("step", step.Name()).Msg("auto handle popup failed")
}
}
@@ -692,7 +707,7 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
log.Warn().Msg("interrupted in mobile UI runner")
return stepResult, errors.Wrap(code.InterruptError, "mobile UI runner interrupted")
default:
if action.Params, err = s.caseRunner.parser.Parse(action.Params, step.Variables); err != nil {
if action.Params, err = s.caseRunner.parser.Parse(action.Params, stepVariables); err != nil {
if !code.IsErrorPredefined(err) {
err = errors.Wrap(code.ParseError,
fmt.Sprintf("parse action params failed: %v", err))
@@ -709,7 +724,7 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
}
// validate
validateResults, err := validateUI(uiDriver, step.Validators)
validateResults, err := validateUI(uiDriver, stepValidators)
if err != nil {
if !code.IsErrorPredefined(err) {
err = errors.Wrap(code.MobileUIValidationError, err.Error())

View File

@@ -10,26 +10,30 @@ import (
// StepRendezvous implements IStep interface.
type StepRendezvous struct {
step *TStep
StepConfig
Rendezvous *Rendezvous `json:"rendezvous,omitempty" yaml:"rendezvous,omitempty"`
}
func (s *StepRendezvous) Name() string {
if s.step.Name != "" {
return s.step.Name
if s.StepName != "" {
return s.StepName
}
return s.step.Rendezvous.Name
return s.Rendezvous.Name
}
func (s *StepRendezvous) Type() StepType {
return stepTypeRendezvous
}
func (s *StepRendezvous) Struct() *TStep {
return s.step
func (s *StepRendezvous) Config() *StepConfig {
return &StepConfig{
StepName: s.StepName,
Variables: s.Variables,
}
}
func (s *StepRendezvous) Run(r *SessionRunner) (*StepResult, error) {
rendezvous := s.step.Rendezvous
rendezvous := s.Rendezvous
log.Info().
Str("name", rendezvous.Name).
Float32("percent", rendezvous.Percent).
@@ -70,8 +74,11 @@ func (s *StepRendezvous) Run(r *SessionRunner) (*StepResult, error) {
}
func isPreRendezvousAllReleased(rendezvous *Rendezvous, testCase *TestCase) bool {
for _, step := range testCase.Steps {
preRendezvous := step.Rendezvous
for _, step := range testCase.TestSteps {
if step.Type() != stepTypeRendezvous {
continue
}
preRendezvous := step.(*StepRendezvous).Rendezvous
if preRendezvous == nil {
continue
}
@@ -88,19 +95,19 @@ func isPreRendezvousAllReleased(rendezvous *Rendezvous, testCase *TestCase) bool
// WithUserNumber sets the user number needed to release the current rendezvous
func (s *StepRendezvous) WithUserNumber(number int64) *StepRendezvous {
s.step.Rendezvous.Number = number
s.Rendezvous.Number = number
return s
}
// WithUserPercent sets the user percent needed to release the current rendezvous
func (s *StepRendezvous) WithUserPercent(percent float32) *StepRendezvous {
s.step.Rendezvous.Percent = percent
s.Rendezvous.Percent = percent
return s
}
// WithTimeout sets the timeout of duration between each user arriving at the current rendezvous
func (s *StepRendezvous) WithTimeout(timeout int64) *StepRendezvous {
s.step.Rendezvous.Timeout = timeout
s.Rendezvous.Timeout = timeout
return s
}
@@ -157,7 +164,7 @@ func (r *Rendezvous) setReleased() {
func initRendezvous(testcase *TestCase, total int64) []*Rendezvous {
var rendezvousList []*Rendezvous
for _, s := range testcase.TestSteps {
step := s.Struct()
step := s.(*StepRendezvous)
if step.Rendezvous == nil {
continue
}

View File

@@ -251,7 +251,7 @@ func (r *requestBuilder) prepareBody(stepVariables map[string]interface{}) error
return nil
}
func initUpload(step *TStep) {
func initUpload(step *StepRequestWithOptionalArgs) {
if step.Request.Headers == nil {
step.Request.Headers = make(map[string]string)
}
@@ -259,7 +259,7 @@ func initUpload(step *TStep) {
step.Request.Body = "$m_encoder"
}
func prepareUpload(parser *Parser, step *TStep, stepVariables map[string]interface{}) (err error) {
func prepareUpload(parser *Parser, step *StepRequestWithOptionalArgs, stepVariables map[string]interface{}) (err error) {
if len(step.Request.Upload) == 0 {
return
}
@@ -276,9 +276,10 @@ func prepareUpload(parser *Parser, step *TStep, stepVariables map[string]interfa
return
}
func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err error) {
func runStepRequest(r *SessionRunner, step IStep) (stepResult *StepResult, err error) {
stepRequest := step.(*StepRequestWithOptionalArgs)
stepResult = &StepResult{
Name: step.Name,
Name: stepRequest.StepName,
StepType: stepTypeRequest,
Success: false,
ContentSize: 0,
@@ -291,7 +292,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
}
}()
err = prepareUpload(r.caseRunner.parser, step, step.Variables)
err = prepareUpload(r.caseRunner.parser, stepRequest, stepRequest.Variables)
if err != nil {
return
}
@@ -300,32 +301,32 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
parser := r.caseRunner.parser
config := r.caseRunner.Config
rb := newRequestBuilder(parser, config, step.Request)
rb.req.Method = strings.ToUpper(string(step.Request.Method))
rb := newRequestBuilder(parser, config, stepRequest.Request)
rb.req.Method = strings.ToUpper(string(stepRequest.Request.Method))
err = rb.prepareUrlParams(step.Variables)
err = rb.prepareUrlParams(stepRequest.Variables)
if err != nil {
return
}
err = rb.prepareHeaders(step.Variables)
err = rb.prepareHeaders(stepRequest.Variables)
if err != nil {
return
}
err = rb.prepareBody(step.Variables)
err = rb.prepareBody(stepRequest.Variables)
if err != nil {
return
}
// add request object to step variables, could be used in setup hooks
step.Variables["hrp_step_name"] = step.Name
step.Variables["hrp_step_request"] = rb.requestMap
step.Variables["request"] = rb.requestMap // setup hooks compatible with v3
stepRequest.Variables["hrp_step_name"] = step.Name
stepRequest.Variables["hrp_step_request"] = rb.requestMap
stepRequest.Variables["request"] = rb.requestMap // setup hooks compatible with v3
// deal with setup hooks
for _, setupHook := range step.SetupHooks {
_, err := parser.Parse(setupHook, step.Variables)
for _, setupHook := range stepRequest.SetupHooks {
_, err := parser.Parse(setupHook, stepRequest.Variables)
if err != nil {
return stepResult, errors.Wrap(err, "run setup hooks failed")
}
@@ -347,15 +348,15 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
// select HTTP client
var client *http.Client
if step.Request.HTTP2 {
if stepRequest.Request.HTTP2 {
client = r.caseRunner.hrpRunner.http2Client
} else {
client = r.caseRunner.hrpRunner.httpClient
}
// set step timeout
if step.Request.Timeout != 0 {
client.Timeout = time.Duration(step.Request.Timeout*1000) * time.Millisecond
if stepRequest.Request.Timeout != 0 {
client.Timeout = time.Duration(stepRequest.Request.Timeout*1000) * time.Millisecond
}
// do request action
@@ -398,12 +399,12 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
}
// add response object to step variables, could be used in teardown hooks
step.Variables["hrp_step_response"] = respObj.respObjMeta
step.Variables["response"] = respObj.respObjMeta
stepRequest.Variables["hrp_step_response"] = respObj.respObjMeta
stepRequest.Variables["response"] = respObj.respObjMeta
// deal with teardown hooks
for _, teardownHook := range step.TeardownHooks {
_, err := parser.Parse(teardownHook, step.Variables)
for _, teardownHook := range stepRequest.TeardownHooks {
_, err := parser.Parse(teardownHook, stepRequest.Variables)
if err != nil {
return stepResult, errors.Wrap(err, "run teardown hooks failed")
}
@@ -413,15 +414,15 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
sessionData.ReqResps.Response = builtin.FormatResponse(respObj.respObjMeta)
// extract variables from response
extractors := step.Extract
extractMapping := respObj.Extract(extractors, step.Variables)
extractors := stepRequest.StepRequest.Extract
extractMapping := respObj.Extract(extractors, stepRequest.Variables)
stepResult.ExportVars = extractMapping
// override step variables with extracted variables
step.Variables = mergeVariables(step.Variables, extractMapping)
stepRequest.Variables = mergeVariables(stepRequest.Variables, extractMapping)
// validate response
err = respObj.Validate(step.Validators, step.Variables)
err = respObj.Validate(stepRequest.Validators, stepRequest.Variables)
sessionData.Validators = respObj.validationResults
if err == nil {
sessionData.Success = true
@@ -521,32 +522,33 @@ func shouldPrintBody(contentType string) bool {
// NewStep returns a new constructed teststep with specified step name.
func NewStep(name string) *StepRequest {
return &StepRequest{
step: &TStep{
Name: name,
StepConfig: StepConfig{
StepName: name,
Variables: make(map[string]interface{}),
},
}
}
type StepRequest struct {
step *TStep
StepConfig
Request *Request `json:"request,omitempty" yaml:"request,omitempty"`
}
// WithVariables sets variables for current teststep.
func (s *StepRequest) WithVariables(variables map[string]interface{}) *StepRequest {
s.step.Variables = variables
s.Variables = variables
return s
}
// SetupHook adds a setup hook for current teststep.
func (s *StepRequest) SetupHook(hook string) *StepRequest {
s.step.SetupHooks = append(s.step.SetupHooks, hook)
s.SetupHooks = append(s.SetupHooks, hook)
return s
}
// HTTP2 enables HTTP/2 protocol
func (s *StepRequest) HTTP2() *StepRequest {
s.step.Request = &Request{
s.Request = &Request{
HTTP2: true,
}
return s
@@ -554,250 +556,248 @@ func (s *StepRequest) HTTP2() *StepRequest {
// Loop specify running times for the current step
func (s *StepRequest) Loop(times int) *StepRequest {
s.step.Loops = times
s.Loops = times
return s
}
// GET makes a HTTP GET request.
func (s *StepRequest) GET(url string) *StepRequestWithOptionalArgs {
if s.step.Request != nil {
s.step.Request.Method = httpGET
s.step.Request.URL = url
if s.Request != nil {
s.Request.Method = httpGET
s.Request.URL = url
} else {
s.step.Request = &Request{
s.Request = &Request{
Method: httpGET,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
StepRequest: s,
}
}
// HEAD makes a HTTP HEAD request.
func (s *StepRequest) HEAD(url string) *StepRequestWithOptionalArgs {
if s.step.Request != nil {
s.step.Request.Method = httpHEAD
s.step.Request.URL = url
if s.Request != nil {
s.Request.Method = httpHEAD
s.Request.URL = url
} else {
s.step.Request = &Request{
s.Request = &Request{
Method: httpHEAD,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
StepRequest: s,
}
}
// POST makes a HTTP POST request.
func (s *StepRequest) POST(url string) *StepRequestWithOptionalArgs {
if s.step.Request != nil {
s.step.Request.Method = httpPOST
s.step.Request.URL = url
if s.Request != nil {
s.Request.Method = httpPOST
s.Request.URL = url
} else {
s.step.Request = &Request{
s.Request = &Request{
Method: httpPOST,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
StepRequest: s,
}
}
// PUT makes a HTTP PUT request.
func (s *StepRequest) PUT(url string) *StepRequestWithOptionalArgs {
if s.step.Request != nil {
s.step.Request.Method = httpPUT
s.step.Request.URL = url
if s.Request != nil {
s.Request.Method = httpPUT
s.Request.URL = url
} else {
s.step.Request = &Request{
s.Request = &Request{
Method: httpPUT,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
StepRequest: s,
}
}
// DELETE makes a HTTP DELETE request.
func (s *StepRequest) DELETE(url string) *StepRequestWithOptionalArgs {
if s.step.Request != nil {
s.step.Request.Method = httpDELETE
s.step.Request.URL = url
if s.Request != nil {
s.Request.Method = httpDELETE
s.Request.URL = url
} else {
s.step.Request = &Request{
s.Request = &Request{
Method: httpDELETE,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
StepRequest: s,
}
}
// OPTIONS makes a HTTP OPTIONS request.
func (s *StepRequest) OPTIONS(url string) *StepRequestWithOptionalArgs {
if s.step.Request != nil {
s.step.Request.Method = httpOPTIONS
s.step.Request.URL = url
if s.Request != nil {
s.Request.Method = httpOPTIONS
s.Request.URL = url
} else {
s.step.Request = &Request{
s.Request = &Request{
Method: httpOPTIONS,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
StepRequest: s,
}
}
// PATCH makes a HTTP PATCH request.
func (s *StepRequest) PATCH(url string) *StepRequestWithOptionalArgs {
if s.step.Request != nil {
s.step.Request.Method = httpPATCH
s.step.Request.URL = url
if s.Request != nil {
s.Request.Method = httpPATCH
s.Request.URL = url
} else {
s.step.Request = &Request{
s.Request = &Request{
Method: httpPATCH,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
StepRequest: s,
}
}
// CallRefCase calls a referenced testcase.
func (s *StepRequest) CallRefCase(tc ITestCase) *StepTestCaseWithOptionalArgs {
var err error
s.step.TestCase, err = tc.GetTestCase()
testCase, err := tc.GetTestCase()
if err != nil {
log.Error().Err(err).Msg("failed to load testcase")
os.Exit(code.GetErrorCode(err))
}
return &StepTestCaseWithOptionalArgs{
step: s.step,
StepConfig: s.StepConfig,
TestCase: testCase,
}
}
// CallRefAPI calls a referenced api.
func (s *StepRequest) CallRefAPI(api IAPI) *StepAPIWithOptionalArgs {
var err error
s.step.API, err = api.ToAPI()
api, err := api.ToAPI()
if err != nil {
log.Error().Err(err).Msg("failed to load api")
os.Exit(code.GetErrorCode(err))
}
return &StepAPIWithOptionalArgs{
step: s.step,
StepConfig: s.StepConfig,
API: api,
}
}
// StartTransaction starts a transaction.
func (s *StepRequest) StartTransaction(name string) *StepTransaction {
s.step.Transaction = &Transaction{
Name: name,
Type: transactionStart,
}
return &StepTransaction{
step: s.step,
StepConfig: s.StepConfig,
Transaction: &Transaction{
Name: name,
Type: transactionStart,
},
}
}
// EndTransaction ends a transaction.
func (s *StepRequest) EndTransaction(name string) *StepTransaction {
s.step.Transaction = &Transaction{
Name: name,
Type: transactionEnd,
}
return &StepTransaction{
step: s.step,
StepConfig: s.StepConfig,
Transaction: &Transaction{
Name: name,
Type: transactionEnd,
},
}
}
// SetThinkTime sets think time.
func (s *StepRequest) SetThinkTime(time float64) *StepThinkTime {
s.step.ThinkTime = &ThinkTime{
Time: time,
}
return &StepThinkTime{
step: s.step,
StepConfig: s.StepConfig,
ThinkTime: &ThinkTime{
Time: time,
},
}
}
// SetRendezvous creates a new rendezvous
func (s *StepRequest) SetRendezvous(name string) *StepRendezvous {
s.step.Rendezvous = &Rendezvous{
Name: name,
}
return &StepRendezvous{
step: s.step,
Rendezvous: &Rendezvous{
Name: name,
},
}
}
// WebSocket creates a new websocket action
func (s *StepRequest) WebSocket() *StepWebSocket {
s.step.WebSocket = &WebSocketAction{}
return &StepWebSocket{
step: s.step,
StepConfig: s.StepConfig,
WebSocket: &WebSocketAction{},
}
}
// Android creates a new android action
func (s *StepRequest) Android() *StepMobile {
s.step.Android = &MobileUI{}
return &StepMobile{
step: s.step,
StepConfig: s.StepConfig,
Android: &MobileUI{},
}
}
// IOS creates a new ios action
func (s *StepRequest) IOS() *StepMobile {
s.step.IOS = &MobileUI{}
return &StepMobile{
step: s.step,
StepConfig: s.StepConfig,
IOS: &MobileUI{},
}
}
// Harmony creates a new harmony action
func (s *StepRequest) Harmony() *StepMobile {
s.step.Harmony = &MobileUI{}
return &StepMobile{
step: s.step,
StepConfig: s.StepConfig,
Harmony: &MobileUI{},
}
}
// Shell creates a new shell action
func (s *StepRequest) Shell(content string) *StepShell {
s.step.Shell = &Shell{
String: content,
ExpectExitCode: 0,
}
return &StepShell{
step: s.step,
StepConfig: s.StepConfig,
Shell: &Shell{
String: content,
ExpectExitCode: 0,
},
}
}
// StepRequestWithOptionalArgs implements IStep interface.
type StepRequestWithOptionalArgs struct {
step *TStep
*StepRequest
}
// SetVerify sets whether to verify SSL for current HTTP request.
func (s *StepRequestWithOptionalArgs) SetVerify(verify bool) *StepRequestWithOptionalArgs {
log.Info().Bool("verify", verify).Msg("set step request verify")
s.step.Request.Verify = verify
s.Request.Verify = verify
return s
}
// SetTimeout sets timeout for current HTTP request.
func (s *StepRequestWithOptionalArgs) SetTimeout(timeout time.Duration) *StepRequestWithOptionalArgs {
log.Info().Float64("timeout(seconds)", timeout.Seconds()).Msg("set step request timeout")
s.step.Request.Timeout = timeout.Seconds()
s.Request.Timeout = timeout.Seconds()
return s
}
@@ -811,7 +811,7 @@ func (s *StepRequestWithOptionalArgs) SetProxies(proxies map[string]string) *Ste
// SetAllowRedirects sets whether to allow redirects for current HTTP request.
func (s *StepRequestWithOptionalArgs) SetAllowRedirects(allowRedirects bool) *StepRequestWithOptionalArgs {
log.Info().Bool("allowRedirects", allowRedirects).Msg("set step request allowRedirects")
s.step.Request.AllowRedirects = allowRedirects
s.Request.AllowRedirects = allowRedirects
return s
}
@@ -824,154 +824,142 @@ func (s *StepRequestWithOptionalArgs) SetAuth(auth map[string]string) *StepReque
// WithParams sets HTTP request params for current step.
func (s *StepRequestWithOptionalArgs) WithParams(params map[string]interface{}) *StepRequestWithOptionalArgs {
s.step.Request.Params = params
s.Request.Params = params
return s
}
// WithHeaders sets HTTP request headers for current step.
func (s *StepRequestWithOptionalArgs) WithHeaders(headers map[string]string) *StepRequestWithOptionalArgs {
s.step.Request.Headers = headers
s.Request.Headers = headers
return s
}
// WithCookies sets HTTP request cookies for current step.
func (s *StepRequestWithOptionalArgs) WithCookies(cookies map[string]string) *StepRequestWithOptionalArgs {
s.step.Request.Cookies = cookies
s.Request.Cookies = cookies
return s
}
// WithBody sets HTTP request body for current step.
func (s *StepRequestWithOptionalArgs) WithBody(body interface{}) *StepRequestWithOptionalArgs {
s.step.Request.Body = body
s.Request.Body = body
return s
}
// WithUpload sets HTTP request body for uploading file(s).
func (s *StepRequestWithOptionalArgs) WithUpload(upload map[string]interface{}) *StepRequestWithOptionalArgs {
// init upload
initUpload(s.step)
s.step.Request.Upload = upload
initUpload(s)
s.Request.Upload = upload
return s
}
// TeardownHook adds a teardown hook for current teststep.
func (s *StepRequestWithOptionalArgs) TeardownHook(hook string) *StepRequestWithOptionalArgs {
s.step.TeardownHooks = append(s.step.TeardownHooks, hook)
s.TeardownHooks = append(s.TeardownHooks, hook)
return s
}
// Validate switches to step validation.
func (s *StepRequestWithOptionalArgs) Validate() *StepRequestValidation {
return &StepRequestValidation{
step: s.step,
StepRequestWithOptionalArgs: s,
}
}
// Extract switches to step extraction.
func (s *StepRequestWithOptionalArgs) Extract() *StepRequestExtraction {
s.step.Extract = make(map[string]string)
s.StepConfig.Extract = make(map[string]string)
return &StepRequestExtraction{
step: s.step,
StepRequestWithOptionalArgs: s,
}
}
func (s *StepRequestWithOptionalArgs) Name() string {
if s.step.Name != "" {
return s.step.Name
if s.StepName != "" {
return s.StepName
}
return fmt.Sprintf("%v %s", s.step.Request.Method, s.step.Request.URL)
return fmt.Sprintf("%v %s", s.Request.Method, s.Request.URL)
}
func (s *StepRequestWithOptionalArgs) Type() StepType {
return StepType(fmt.Sprintf("request-%v", s.step.Request.Method))
return StepType(fmt.Sprintf("request-%v", s.Request.Method))
}
func (s *StepRequestWithOptionalArgs) Struct() *TStep {
return s.step
func (s *StepRequestWithOptionalArgs) Config() *StepConfig {
return &s.StepConfig
}
func (s *StepRequestWithOptionalArgs) Run(r *SessionRunner) (*StepResult, error) {
return runStepRequest(r, s.step)
return runStepRequest(r, s)
}
// StepRequestExtraction implements IStep interface.
type StepRequestExtraction struct {
step *TStep
*StepRequestWithOptionalArgs
}
// WithJmesPath sets the JMESPath expression to extract from the response.
func (s *StepRequestExtraction) WithJmesPath(jmesPath string, varName string) *StepRequestExtraction {
s.step.Extract[varName] = jmesPath
s.StepConfig.Extract[varName] = jmesPath
return s
}
// Validate switches to step validation.
func (s *StepRequestExtraction) Validate() *StepRequestValidation {
return &StepRequestValidation{
step: s.step,
StepRequestWithOptionalArgs: &StepRequestWithOptionalArgs{
StepRequest: &StepRequest{
StepConfig: s.StepConfig,
},
},
}
}
func (s *StepRequestExtraction) Name() string {
return s.step.Name
return s.StepName
}
func (s *StepRequestExtraction) Type() StepType {
var stepType StepType
if s.step.WebSocket != nil {
stepType = StepType(fmt.Sprintf("websocket-%v", s.step.WebSocket.Type))
} else {
stepType = StepType(fmt.Sprintf("request-%v", s.step.Request.Method))
}
stepType := StepType(fmt.Sprintf("request-%v", s.Request.Method))
return stepType + stepTypeSuffixExtraction
}
func (s *StepRequestExtraction) Struct() *TStep {
return s.step
func (s *StepRequestExtraction) Struct() *StepConfig {
return &s.StepConfig
}
func (s *StepRequestExtraction) Run(r *SessionRunner) (*StepResult, error) {
if s.step.Request != nil {
return runStepRequest(r, s.step)
}
if s.step.WebSocket != nil {
return runStepWebSocket(r, s.step)
if s.StepRequestWithOptionalArgs != nil {
return runStepRequest(r, s.StepRequestWithOptionalArgs)
}
return nil, errors.New("unexpected protocol type")
}
// StepRequestValidation implements IStep interface.
type StepRequestValidation struct {
step *TStep
*StepRequestWithOptionalArgs
}
func (s *StepRequestValidation) Name() string {
if s.step.Name != "" {
return s.step.Name
if s.StepName != "" {
return s.StepName
}
return fmt.Sprintf("%s %s", s.step.Request.Method, s.step.Request.URL)
return fmt.Sprintf("%s %s", s.Request.Method, s.Request.URL)
}
func (s *StepRequestValidation) Type() StepType {
var stepType StepType
if s.step.WebSocket != nil {
stepType = StepType(fmt.Sprintf("websocket-%v", s.step.WebSocket.Type))
} else {
stepType = StepType(fmt.Sprintf("request-%v", s.step.Request.Method))
}
stepType := StepType(fmt.Sprintf("request-%v", s.Request.Method))
return stepType + stepTypeSuffixValidation
}
func (s *StepRequestValidation) Struct() *TStep {
return s.step
func (s *StepRequestValidation) Config() *StepConfig {
return &s.StepConfig
}
func (s *StepRequestValidation) Run(r *SessionRunner) (*StepResult, error) {
if s.step.Request != nil {
return runStepRequest(r, s.step)
}
if s.step.WebSocket != nil {
return runStepWebSocket(r, s.step)
if s.StepRequestWithOptionalArgs != nil {
return runStepRequest(r, s.StepRequestWithOptionalArgs)
}
return nil, errors.New("unexpected protocol type")
}
@@ -983,7 +971,7 @@ func (s *StepRequestValidation) AssertEqual(jmesPath string, expected interface{
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -994,7 +982,7 @@ func (s *StepRequestValidation) AssertGreater(jmesPath string, expected interfac
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1005,7 +993,7 @@ func (s *StepRequestValidation) AssertLess(jmesPath string, expected interface{}
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1016,7 +1004,7 @@ func (s *StepRequestValidation) AssertGreaterOrEqual(jmesPath string, expected i
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1027,7 +1015,7 @@ func (s *StepRequestValidation) AssertLessOrEqual(jmesPath string, expected inte
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1038,7 +1026,7 @@ func (s *StepRequestValidation) AssertNotEqual(jmesPath string, expected interfa
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1049,7 +1037,7 @@ func (s *StepRequestValidation) AssertContains(jmesPath string, expected interfa
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1060,7 +1048,7 @@ func (s *StepRequestValidation) AssertTypeMatch(jmesPath string, expected interf
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1071,7 +1059,7 @@ func (s *StepRequestValidation) AssertRegexp(jmesPath string, expected interface
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1082,7 +1070,7 @@ func (s *StepRequestValidation) AssertStartsWith(jmesPath string, expected inter
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1093,7 +1081,7 @@ func (s *StepRequestValidation) AssertEndsWith(jmesPath string, expected interfa
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1104,7 +1092,7 @@ func (s *StepRequestValidation) AssertLengthEqual(jmesPath string, expected inte
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1115,7 +1103,7 @@ func (s *StepRequestValidation) AssertContainedBy(jmesPath string, expected inte
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1126,7 +1114,7 @@ func (s *StepRequestValidation) AssertLengthLessThan(jmesPath string, expected i
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1137,7 +1125,7 @@ func (s *StepRequestValidation) AssertStringEqual(jmesPath string, expected inte
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1148,7 +1136,7 @@ func (s *StepRequestValidation) AssertEqualFold(jmesPath string, expected interf
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1159,7 +1147,7 @@ func (s *StepRequestValidation) AssertLengthLessOrEquals(jmesPath string, expect
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1170,7 +1158,7 @@ func (s *StepRequestValidation) AssertLengthGreaterThan(jmesPath string, expecte
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}
@@ -1181,7 +1169,7 @@ func (s *StepRequestValidation) AssertLengthGreaterOrEquals(jmesPath string, exp
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, v)
s.Validators = append(s.Validators, v)
return s
}

View File

@@ -29,7 +29,7 @@ var (
)
func TestRunRequestGetToStruct(t *testing.T) {
tStep := stepGET.step
tStep := stepGET
if tStep.Request.Method != httpGET {
t.Fatalf("tStep.Request.Method != GET")
}
@@ -52,7 +52,7 @@ func TestRunRequestGetToStruct(t *testing.T) {
}
func TestRunRequestPostDataToStruct(t *testing.T) {
tStep := stepPOSTData.step
tStep := stepPOSTData
if tStep.Request.Method != httpPOST {
t.Fatalf("tStep.Request.Method != POST")
}

View File

@@ -17,68 +17,86 @@ type Shell struct {
// StepShell implements IStep interface.
type StepShell struct {
step *TStep
StepConfig
Shell *Shell `json:"shell,omitempty" yaml:"shell,omitempty"`
}
func (s *StepShell) Name() string {
return s.step.Name
return s.StepName
}
func (s *StepShell) Type() StepType {
return stepTypeShell
}
func (s *StepShell) Struct() *TStep {
return s.step
func (s *StepShell) Config() *StepConfig {
return &StepConfig{
StepName: s.StepName,
Variables: s.Variables,
}
}
func (s *StepShell) Run(r *SessionRunner) (*StepResult, error) {
return runStepShell(r, s.step)
return runStepShell(r, s)
}
// Validate switches to step validation.
func (s *StepShell) Validate() *StepShellValidation {
return &StepShellValidation{
step: s.step,
StepConfig: s.StepConfig,
Shell: s.Shell,
}
}
// StepShellValidation implements IStep interface.
type StepShellValidation struct {
step *TStep
StepConfig
Shell *Shell `json:"shell,omitempty" yaml:"shell,omitempty"`
}
func (s *StepShellValidation) Name() string {
return s.step.Name
return s.StepName
}
func (s *StepShellValidation) Type() StepType {
return stepTypeShell + stepTypeSuffixValidation
}
func (s *StepShellValidation) Struct() *TStep {
return s.step
func (s *StepShellValidation) Config() *StepConfig {
return &StepConfig{
StepName: s.StepName,
Variables: s.Variables,
}
}
func (s *StepShellValidation) Run(r *SessionRunner) (*StepResult, error) {
return runStepShell(r, s.step)
return runStepShell(r, s)
}
func (s *StepShellValidation) AssertExitCode(expected int) *StepShellValidation {
s.step.Shell.ExpectExitCode = expected
s.Shell.ExpectExitCode = expected
return s
}
func runStepShell(r *SessionRunner, step *TStep) (stepResult *StepResult, err error) {
shell := step.Shell
func runStepShell(r *SessionRunner, step IStep) (stepResult *StepResult, err error) {
var shell *Shell
switch stepShell := step.(type) {
case *StepShell:
shell = stepShell.Shell
case *StepShellValidation:
shell = stepShell.Shell
default:
return nil, errors.New("invalid shell step type")
}
log.Info().
Str("name", step.Name).
Str("name", step.Name()).
Str("type", string(stepTypeShell)).
Str("content", shell.String).
Msg("run shell string")
stepResult = &StepResult{
Name: step.Name,
Name: step.Name(),
StepType: stepTypeShell,
Success: false,
Elapsed: 0,

View File

@@ -10,26 +10,27 @@ import (
// StepTestCaseWithOptionalArgs implements IStep interface.
type StepTestCaseWithOptionalArgs struct {
step *TStep
StepConfig
TestCase interface{} `json:"testcase,omitempty" yaml:"testcase,omitempty"` // *TestCasePath or *TestCase
}
// TeardownHook adds a teardown hook for current teststep.
func (s *StepTestCaseWithOptionalArgs) TeardownHook(hook string) *StepTestCaseWithOptionalArgs {
s.step.TeardownHooks = append(s.step.TeardownHooks, hook)
s.TeardownHooks = append(s.TeardownHooks, hook)
return s
}
// Export specifies variable names to export from referenced testcase for current step.
func (s *StepTestCaseWithOptionalArgs) Export(names ...string) *StepTestCaseWithOptionalArgs {
s.step.Export = append(s.step.Export, names...)
s.StepExport = append(s.StepExport, names...)
return s
}
func (s *StepTestCaseWithOptionalArgs) Name() string {
if s.step.Name != "" {
return s.step.Name
if s.StepName != "" {
return s.StepName
}
ts, ok := s.step.TestCase.(*TestCase)
ts, ok := s.TestCase.(*TestCase)
if ok {
return ts.Config.Name
}
@@ -40,13 +41,13 @@ func (s *StepTestCaseWithOptionalArgs) Type() StepType {
return stepTypeTestCase
}
func (s *StepTestCaseWithOptionalArgs) Struct() *TStep {
return s.step
func (s *StepTestCaseWithOptionalArgs) Config() *StepConfig {
return &s.StepConfig
}
func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepResult, err error) {
stepResult = &StepResult{
Name: s.step.Name,
Name: s.StepName,
StepType: stepTypeTestCase,
Success: false,
}
@@ -58,7 +59,7 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepRe
}
}()
stepTestCase := s.step.TestCase.(*TestCase)
stepTestCase := s.TestCase.(*TestCase)
// copy testcase to avoid data racing
copiedTestCase := &TestCase{}
@@ -69,11 +70,11 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepRe
// override testcase config
// override testcase name
if s.step.Name != "" {
copiedTestCase.Config.Name = s.step.Name
if s.StepName != "" {
copiedTestCase.Config.Name = s.StepName
}
// merge & override extractors
copiedTestCase.Config.Export = mergeSlices(s.step.Export, copiedTestCase.Config.Export)
copiedTestCase.Config.Export = mergeSlices(s.StepExport, copiedTestCase.Config.Export)
caseRunner, err := r.caseRunner.hrpRunner.NewCaseRunner(*copiedTestCase)
if err != nil {
@@ -85,7 +86,7 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepRe
start := time.Now()
var summary *TestCaseSummary
// run referenced testcase with step variables
summary, err = sessionRunner.Start(s.step.Variables)
summary, err = sessionRunner.Start(s.Variables)
stepResult.Elapsed = time.Since(start).Milliseconds()
// update step names

View File

@@ -14,27 +14,28 @@ type ThinkTime struct {
// StepThinkTime implements IStep interface.
type StepThinkTime struct {
step *TStep
StepConfig
ThinkTime *ThinkTime `json:"think_time,omitempty" yaml:"think_time,omitempty"`
}
func (s *StepThinkTime) Name() string {
return s.step.Name
return s.StepName
}
func (s *StepThinkTime) Type() StepType {
return stepTypeThinkTime
}
func (s *StepThinkTime) Struct() *TStep {
return s.step
func (s *StepThinkTime) Config() *StepConfig {
return &s.StepConfig
}
func (s *StepThinkTime) Run(r *SessionRunner) (*StepResult, error) {
thinkTime := s.step.ThinkTime
thinkTime := s.ThinkTime
log.Info().Float64("time", thinkTime.Time).Msg("think time")
stepResult := &StepResult{
Name: s.step.Name,
Name: s.StepName,
StepType: stepTypeThinkTime,
Success: true,
}

View File

@@ -21,26 +21,27 @@ const (
// StepTransaction implements IStep interface.
type StepTransaction struct {
step *TStep
StepConfig
Transaction *Transaction `json:"transaction,omitempty" yaml:"transaction,omitempty"`
}
func (s *StepTransaction) Name() string {
if s.step.Name != "" {
return s.step.Name
if s.StepName != "" {
return s.StepName
}
return fmt.Sprintf("transaction %s %s", s.step.Transaction.Name, s.step.Transaction.Type)
return fmt.Sprintf("transaction %s %s", s.Transaction.Name, s.Transaction.Type)
}
func (s *StepTransaction) Type() StepType {
return stepTypeTransaction
}
func (s *StepTransaction) Struct() *TStep {
return s.step
func (s *StepTransaction) Config() *StepConfig {
return &s.StepConfig
}
func (s *StepTransaction) Run(r *SessionRunner) (*StepResult, error) {
transaction := s.step.Transaction
transaction := s.Transaction
log.Info().
Str("name", transaction.Name).
Str("type", string(transaction.Type)).

View File

@@ -120,26 +120,27 @@ func (wsConfig *WebSocketConfig) checkWebSocket() {
// StepWebSocket implements IStep interface.
type StepWebSocket struct {
step *TStep
StepConfig
WebSocket *WebSocketAction `json:"websocket,omitempty" yaml:"websocket,omitempty"`
}
func (s *StepWebSocket) Name() string {
if s.step.Name != "" {
return s.step.Name
if s.StepName != "" {
return s.StepName
}
return fmt.Sprintf("%s %s", s.step.WebSocket.Type, s.step.WebSocket.URL)
return fmt.Sprintf("%s %s", s.WebSocket.Type, s.WebSocket.URL)
}
func (s *StepWebSocket) Type() StepType {
return StepType(fmt.Sprintf("websocket-%v", s.step.WebSocket.Type))
return StepType(fmt.Sprintf("websocket-%v", s.WebSocket.Type))
}
func (s *StepWebSocket) Struct() *TStep {
return s.step
func (s *StepWebSocket) Config() *StepConfig {
return &s.StepConfig
}
func (s *StepWebSocket) Run(r *SessionRunner) (*StepResult, error) {
return runStepWebSocket(r, s.step)
return runStepWebSocket(r, s)
}
func (s *StepWebSocket) withUrl(url ...string) *StepWebSocket {
@@ -149,87 +150,95 @@ func (s *StepWebSocket) withUrl(url ...string) *StepWebSocket {
if len(url) > 1 {
log.Warn().Msg("too many WebSocket step URL specified, using first URL")
}
s.step.WebSocket.URL = url[0]
s.WebSocket.URL = url[0]
return s
}
func (s *StepWebSocket) OpenConnection(url ...string) *StepWebSocket {
s.step.WebSocket.Type = wsOpen
s.WebSocket.Type = wsOpen
return s.withUrl(url...)
}
func (s *StepWebSocket) PingPong(url ...string) *StepWebSocket {
s.step.WebSocket.Type = wsPing
s.WebSocket.Type = wsPing
return s.withUrl(url...)
}
func (s *StepWebSocket) WriteAndRead(url ...string) *StepWebSocket {
s.step.WebSocket.Type = wsWriteAndRead
s.WebSocket.Type = wsWriteAndRead
return s.withUrl(url...)
}
func (s *StepWebSocket) Read(url ...string) *StepWebSocket {
s.step.WebSocket.Type = wsRead
s.WebSocket.Type = wsRead
return s.withUrl(url...)
}
func (s *StepWebSocket) Write(url ...string) *StepWebSocket {
s.step.WebSocket.Type = wsWrite
s.WebSocket.Type = wsWrite
return s.withUrl(url...)
}
func (s *StepWebSocket) CloseConnection(url ...string) *StepWebSocket {
s.step.WebSocket.Type = wsClose
s.WebSocket.Type = wsClose
return s.withUrl(url...)
}
func (s *StepWebSocket) WithParams(params map[string]interface{}) *StepWebSocket {
s.step.WebSocket.Params = params
s.WebSocket.Params = params
return s
}
func (s *StepWebSocket) WithHeaders(headers map[string]string) *StepWebSocket {
s.step.WebSocket.Headers = headers
s.WebSocket.Headers = headers
return s
}
func (s *StepWebSocket) NewConnection() *StepWebSocket {
s.step.WebSocket.NewConnection = true
s.WebSocket.NewConnection = true
return s
}
func (s *StepWebSocket) WithTextMessage(message interface{}) *StepWebSocket {
s.step.WebSocket.TextMessage = message
s.WebSocket.TextMessage = message
return s
}
func (s *StepWebSocket) WithBinaryMessage(message interface{}) *StepWebSocket {
s.step.WebSocket.BinaryMessage = message
s.WebSocket.BinaryMessage = message
return s
}
func (s *StepWebSocket) WithTimeout(timeout int64) *StepWebSocket {
s.step.WebSocket.Timeout = timeout
s.WebSocket.Timeout = timeout
return s
}
func (s *StepWebSocket) WithCloseStatus(closeStatus int64) *StepWebSocket {
s.step.WebSocket.CloseStatusCode = closeStatus
s.WebSocket.CloseStatusCode = closeStatus
return s
}
// Validate switches to step validation.
func (s *StepWebSocket) Validate() *StepRequestValidation {
return &StepRequestValidation{
step: s.step,
StepRequestWithOptionalArgs: &StepRequestWithOptionalArgs{
StepRequest: &StepRequest{
StepConfig: s.StepConfig,
},
},
}
}
// Extract switches to step extraction.
func (s *StepWebSocket) Extract() *StepRequestExtraction {
s.step.Extract = make(map[string]string)
s.StepConfig.Extract = make(map[string]string)
return &StepRequestExtraction{
step: s.step,
StepRequestWithOptionalArgs: &StepRequestWithOptionalArgs{
StepRequest: &StepRequest{
StepConfig: s.StepConfig,
},
},
}
}
@@ -260,9 +269,12 @@ func (w *WebSocketAction) GetCloseStatusCode() int64 {
return w.CloseStatusCode
}
func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, err error) {
func runStepWebSocket(r *SessionRunner, step IStep) (stepResult *StepResult, err error) {
stepWebSocket := step.(*StepWebSocket)
webSocket := stepWebSocket.WebSocket
variables := stepWebSocket.Variables
stepResult = &StepResult{
Name: step.Name,
Name: step.Name(),
StepType: stepTypeWebSocket,
Success: false,
ContentSize: 0,
@@ -280,18 +292,18 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
config := r.caseRunner.Config
dummyReq := &Request{
URL: step.WebSocket.URL,
Params: step.WebSocket.Params,
Headers: step.WebSocket.Headers,
URL: webSocket.URL,
Params: webSocket.Params,
Headers: webSocket.Headers,
}
rb := newRequestBuilder(parser, config, dummyReq)
err = rb.prepareUrlParams(step.Variables)
err = rb.prepareUrlParams(variables)
if err != nil {
return
}
err = rb.prepareHeaders(step.Variables)
err = rb.prepareHeaders(variables)
if err != nil {
return
}
@@ -299,12 +311,12 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
parsedHeader := rb.req.Header
// add request object to step variables, could be used in setup hooks
step.Variables["hrp_step_name"] = step.Name
step.Variables["hrp_step_request"] = rb.requestMap
variables["hrp_step_name"] = step.Name
variables["hrp_step_request"] = rb.requestMap
// deal with setup hooks
for _, setupHook := range step.SetupHooks {
_, err = parser.Parse(setupHook, step.Variables)
for _, setupHook := range stepWebSocket.SetupHooks {
_, err = parser.Parse(setupHook, variables)
if err != nil {
return stepResult, errors.Wrap(err, "run setup hooks failed")
}
@@ -315,26 +327,26 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
// do websocket action
if r.caseRunner.hrpRunner.requestsLogOn {
fmt.Printf("-------------------- websocket action: %v --------------------\n", step.WebSocket.Type.toString())
fmt.Printf("-------------------- websocket action: %v --------------------\n", webSocket.Type.toString())
}
switch step.WebSocket.Type {
switch webSocket.Type {
case wsOpen:
log.Info().Int64("timeout(ms)", step.WebSocket.GetTimeout()).Str("url", parsedURL).Msg("open websocket connection")
log.Info().Int64("timeout(ms)", webSocket.GetTimeout()).Str("url", parsedURL).Msg("open websocket connection")
// use the current websocket connection if existed
if getWsClient(r, parsedURL) != nil {
break
}
resp, err = openWithTimeout(parsedURL, parsedHeader, r, step)
resp, err = openWithTimeout(parsedURL, parsedHeader, r, stepWebSocket)
if err != nil {
return stepResult, errors.Wrap(err, "open connection failed")
}
case wsPing:
log.Info().Int64("timeout(ms)", step.WebSocket.GetTimeout()).Str("url", parsedURL).Msg("send ping and expect pong")
err = writeWebSocket(parsedURL, r, step, step.Variables)
log.Info().Int64("timeout(ms)", webSocket.GetTimeout()).Str("url", parsedURL).Msg("send ping and expect pong")
err = writeWebSocket(parsedURL, r, stepWebSocket, stepWebSocket.Variables)
if err != nil {
return stepResult, errors.Wrap(err, "send ping message failed")
}
timer := time.NewTimer(time.Duration(step.WebSocket.GetTimeout()) * time.Millisecond)
timer := time.NewTimer(time.Duration(webSocket.GetTimeout()) * time.Millisecond)
// asynchronous receiving pong message with timeout
go func() {
select {
@@ -347,35 +359,35 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
}
}()
case wsWriteAndRead:
log.Info().Int64("timeout(ms)", step.WebSocket.GetTimeout()).Str("url", parsedURL).Msg("write a message and read response")
err = writeWebSocket(parsedURL, r, step, step.Variables)
log.Info().Int64("timeout(ms)", webSocket.GetTimeout()).Str("url", parsedURL).Msg("write a message and read response")
err = writeWebSocket(parsedURL, r, stepWebSocket, variables)
if err != nil {
return stepResult, errors.Wrap(err, "write message failed")
}
resp, err = readMessageWithTimeout(parsedURL, r, step)
resp, err = readMessageWithTimeout(parsedURL, r, stepWebSocket)
if err != nil {
return stepResult, errors.Wrap(err, "read message failed")
}
case wsRead:
log.Info().Int64("timeout(ms)", step.WebSocket.GetTimeout()).Str("url", parsedURL).Msg("read only")
resp, err = readMessageWithTimeout(parsedURL, r, step)
log.Info().Int64("timeout(ms)", webSocket.GetTimeout()).Str("url", parsedURL).Msg("read only")
resp, err = readMessageWithTimeout(parsedURL, r, stepWebSocket)
if err != nil {
return stepResult, errors.Wrap(err, "read message failed")
}
case wsWrite:
log.Info().Str("url", parsedURL).Msg("write only")
err = writeWebSocket(parsedURL, r, step, step.Variables)
err = writeWebSocket(parsedURL, r, stepWebSocket, variables)
if err != nil {
return stepResult, errors.Wrap(err, "write message failed")
}
case wsClose:
log.Info().Int64("timeout(ms)", step.WebSocket.GetTimeout()).Str("url", parsedURL).Msg("close webSocket connection")
resp, err = closeWithTimeout(parsedURL, r, step, step.Variables)
log.Info().Int64("timeout(ms)", webSocket.GetTimeout()).Str("url", parsedURL).Msg("close webSocket connection")
resp, err = closeWithTimeout(parsedURL, r, stepWebSocket, variables)
if err != nil {
return stepResult, errors.Wrap(err, "close connection failed")
}
default:
return stepResult, errors.Errorf("unexpected websocket frame type: %v", step.WebSocket.Type)
return stepResult, errors.Errorf("unexpected websocket frame type: %v", webSocket.Type)
}
if r.caseRunner.hrpRunner.requestsLogOn {
err = printWebSocketResponse(resp)
@@ -393,12 +405,12 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
if respObj != nil {
// add response object to step variables, could be used in teardown hooks
step.Variables["hrp_step_response"] = respObj.respObjMeta
variables["hrp_step_response"] = respObj.respObjMeta
}
// deal with teardown hooks
for _, teardownHook := range step.TeardownHooks {
_, err = parser.Parse(teardownHook, step.Variables)
for _, teardownHook := range stepWebSocket.TeardownHooks {
_, err = parser.Parse(teardownHook, variables)
if err != nil {
return stepResult, errors.Wrap(err, "run teardown hooks failed")
}
@@ -409,15 +421,15 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
sessionData.ReqResps.Response = builtin.FormatResponse(respObj.respObjMeta)
// extract variables from response
extractors := step.Extract
extractMapping := respObj.Extract(extractors, step.Variables)
extractors := stepWebSocket.StepConfig.Extract
extractMapping := respObj.Extract(extractors, variables)
stepResult.ExportVars = extractMapping
// override step variables with extracted variables
step.Variables = mergeVariables(step.Variables, extractMapping)
variables = mergeVariables(variables, extractMapping)
// validate response
err = respObj.Validate(step.Validators, step.Variables)
err = respObj.Validate(stepWebSocket.Validators, variables)
sessionData.Validators = respObj.validationResults
if err == nil {
sessionData.Success = true
@@ -471,7 +483,7 @@ func printWebSocketResponse(resp interface{}) error {
return nil
}
func openWithTimeout(urlStr string, requestHeader http.Header, r *SessionRunner, step *TStep) (*http.Response, error) {
func openWithTimeout(urlStr string, requestHeader http.Header, r *SessionRunner, step *StepWebSocket) (*http.Response, error) {
openResponseChan := make(chan *http.Response)
errorChan := make(chan error)
go func() {
@@ -519,7 +531,7 @@ func openWithTimeout(urlStr string, requestHeader http.Header, r *SessionRunner,
}
}
func readMessageWithTimeout(urlString string, r *SessionRunner, step *TStep) (*wsReadRespObject, error) {
func readMessageWithTimeout(urlString string, r *SessionRunner, step *StepWebSocket) (*wsReadRespObject, error) {
wsConn := getWsClient(r, urlString)
if wsConn == nil {
return nil, errors.New("try to use existing connection, but there is no connection")
@@ -549,7 +561,7 @@ func readMessageWithTimeout(urlString string, r *SessionRunner, step *TStep) (*w
}
}
func writeWebSocket(urlString string, r *SessionRunner, step *TStep, stepVariables map[string]interface{}) error {
func writeWebSocket(urlString string, r *SessionRunner, step *StepWebSocket, stepVariables map[string]interface{}) error {
wsConn := getWsClient(r, urlString)
if wsConn == nil {
return errors.New("try to use existing connection, but there is no connection")
@@ -583,7 +595,7 @@ func writeWebSocket(urlString string, r *SessionRunner, step *TStep, stepVariabl
return nil
}
func writeWithType(c *websocket.Conn, step *TStep, messageType int, message interface{}) error {
func writeWithType(c *websocket.Conn, step *StepWebSocket, messageType int, message interface{}) error {
if message == nil {
return nil
}
@@ -603,7 +615,7 @@ func writeWithType(c *websocket.Conn, step *TStep, messageType int, message inte
}
}
func writeWithAction(c *websocket.Conn, step *TStep, messageType int, message []byte) error {
func writeWithAction(c *websocket.Conn, step *StepWebSocket, messageType int, message []byte) error {
switch step.WebSocket.Type {
case wsPing:
return c.WriteControl(websocket.PingMessage, message, time.Now().Add(defaultWriteWait))
@@ -615,7 +627,7 @@ func writeWithAction(c *websocket.Conn, step *TStep, messageType int, message []
}
}
func closeWithTimeout(urlString string, r *SessionRunner, step *TStep, stepVariables map[string]interface{}) (*wsCloseRespObject, error) {
func closeWithTimeout(urlString string, r *SessionRunner, step *StepWebSocket, stepVariables map[string]interface{}) (*wsCloseRespObject, error) {
wsConn := getWsClient(r, urlString)
if wsConn == nil {
return nil, errors.New("no connection needs to be closed")

View File

@@ -25,7 +25,7 @@ type TestCasePath string
// GetTestCase loads testcase path and convert to *TestCase
func (path *TestCasePath) GetTestCase() (*TestCase, error) {
tc := &TestCase{}
tc := &TestCaseDef{}
casePath := string(*path)
err := LoadFileObject(casePath, tc)
if err != nil {
@@ -47,8 +47,7 @@ func (path *TestCasePath) GetTestCase() (*TestCase, error) {
// TestCase implements ITestCase interface.
type TestCase struct {
Config *TConfig `json:"config" yaml:"config"`
Steps []*TStep `json:"teststeps" yaml:"teststeps"`
TestSteps []IStep `json:"-" yaml:"-"`
TestSteps []IStep `json:"teststeps" yaml:"teststeps"`
}
func (tc *TestCase) GetTestCase() (*TestCase, error) {
@@ -56,7 +55,7 @@ func (tc *TestCase) GetTestCase() (*TestCase, error) {
}
// MakeCompat converts TestCase compatible with Golang engine style
func (tc *TestCase) MakeCompat() (err error) {
func (tc *TestCaseDef) MakeCompat() (err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("[MakeCompat] convert compat testcase error: %v", p)
@@ -86,7 +85,6 @@ func (tc *TestCase) MakeCompat() (err error) {
}
func (tc *TestCase) Dump2JSON(targetPath string) error {
tc.loadTSteps()
err := builtin.Dump2JSON(tc, targetPath)
if err != nil {
return errors.Wrap(err, "dump testcase to json failed")
@@ -95,7 +93,6 @@ func (tc *TestCase) Dump2JSON(targetPath string) error {
}
func (tc *TestCase) Dump2YAML(targetPath string) error {
tc.loadTSteps()
err := builtin.Dump2YAML(tc, targetPath)
if err != nil {
return errors.Wrap(err, "dump testcase to yaml failed")
@@ -103,21 +100,8 @@ func (tc *TestCase) Dump2YAML(targetPath string) error {
return nil
}
// loadTSteps loads TSteps structs from TestSteps([]IStep)
func (tc *TestCase) loadTSteps() {
tc.Steps = make([]*TStep, 0)
for _, step := range tc.TestSteps {
if step.Type() == stepTypeTestCase {
if testcase, ok := step.Struct().TestCase.(*TestCase); ok {
step.Struct().TestCase = testcase
}
}
tc.Steps = append(tc.Steps, step.Struct())
}
}
// loadTSteps loads TestSteps([]IStep) from TSteps structs
func (tc *TestCase) loadISteps() (*TestCase, error) {
// loadISteps loads TestSteps([]IStep) from TSteps structs
func (tc *TestCaseDef) loadISteps() (*TestCase, error) {
testCase := &TestCase{
Config: tc.Config,
}
@@ -187,7 +171,8 @@ func (tc *TestCase) loadISteps() (*TestCase, error) {
fmt.Sprintf("failed to handle referenced API, got %v", step.TestCase))
}
testCase.TestSteps = append(testCase.TestSteps, &StepAPIWithOptionalArgs{
step: step,
StepConfig: step.StepConfig,
API: step.API,
})
} else if step.TestCase != nil {
casePath, ok := step.TestCase.(string)
@@ -210,7 +195,7 @@ func (tc *TestCase) loadISteps() (*TestCase, error) {
return nil, errors.Wrap(code.InvalidCaseError,
fmt.Sprintf("referenced testcase should be map or path(string), got %v", step.TestCase))
}
tCase := &TestCase{}
tCase := &TestCaseDef{}
err = mapstructure.Decode(testCaseMap, tCase)
if err != nil {
return nil, err
@@ -227,47 +212,60 @@ func (tc *TestCase) loadISteps() (*TestCase, error) {
fmt.Sprintf("failed to handle referenced testcase, got %v", step.TestCase))
}
testCase.TestSteps = append(testCase.TestSteps, &StepTestCaseWithOptionalArgs{
step: step,
StepConfig: step.StepConfig,
TestCase: step.TestCase,
})
} else if step.ThinkTime != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepThinkTime{
step: step,
StepConfig: step.StepConfig,
ThinkTime: step.ThinkTime,
})
} else if step.Request != nil {
stepRequest := &StepRequestWithOptionalArgs{
StepRequest: &StepRequest{
StepConfig: step.StepConfig,
Request: step.Request,
},
}
// init upload
if len(step.Request.Upload) != 0 {
initUpload(step)
initUpload(stepRequest)
}
testCase.TestSteps = append(testCase.TestSteps, &StepRequestWithOptionalArgs{
step: step,
})
testCase.TestSteps = append(testCase.TestSteps, stepRequest)
} else if step.Transaction != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepTransaction{
step: step,
StepConfig: step.StepConfig,
Transaction: step.Transaction,
})
} else if step.Rendezvous != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepRendezvous{
step: step,
StepConfig: step.StepConfig,
Rendezvous: step.Rendezvous,
})
} else if step.WebSocket != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepWebSocket{
step: step,
StepConfig: step.StepConfig,
WebSocket: step.WebSocket,
})
} else if step.IOS != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepMobile{
step: step,
StepConfig: step.StepConfig,
IOS: step.IOS,
})
} else if step.Harmony != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepMobile{
step: step,
StepConfig: step.StepConfig,
Harmony: step.Harmony,
})
} else if step.Android != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepMobile{
step: step,
StepConfig: step.StepConfig,
Android: step.Android,
})
} else if step.Shell != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepShell{
step: step,
StepConfig: step.StepConfig,
Shell: step.Shell,
})
} else {
log.Warn().Interface("step", step).Msg("[convertTestCase] unexpected step")