diff --git a/hrp/boomer.go b/hrp/boomer.go index 2a520955..f4e2c899 100644 --- a/hrp/boomer.go +++ b/hrp/boomer.go @@ -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 { diff --git a/hrp/config.go b/hrp/config.go index b5f9cbc7..b90f18a7 100644 --- a/hrp/config.go +++ b/hrp/config.go @@ -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"` diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 364b5ca2..c1d48e7f 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v5.0.0+2411072100 +v5.0.0+2411092015 diff --git a/hrp/loader_test.go b/hrp/loader_test.go index 75f452ba..9cb29d81 100644 --- a/hrp/loader_test.go +++ b/hrp/loader_test.go @@ -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() } } diff --git a/hrp/models.go b/hrp/models.go new file mode 100644 index 00000000..15191bc8 --- /dev/null +++ b/hrp/models.go @@ -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) +} diff --git a/hrp/pkg/convert/from_curl.go b/hrp/pkg/convert/from_curl.go index a71d0393..c18a6a9c 100644 --- a/hrp/pkg/convert/from_curl.go +++ b/hrp/pkg/convert/from_curl.go @@ -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 } diff --git a/hrp/pkg/convert/from_curl_test.go b/hrp/pkg/convert/from_curl_test.go index eaaacf09..499628f2 100644 --- a/hrp/pkg/convert/from_curl_test.go +++ b/hrp/pkg/convert/from_curl_test.go @@ -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) { diff --git a/hrp/pkg/convert/from_har.go b/hrp/pkg/convert/from_har.go index 60ebed27..89696eae 100644 --- a/hrp/pkg/convert/from_har.go +++ b/hrp/pkg/convert/from_har.go @@ -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 { diff --git a/hrp/pkg/convert/from_json.go b/hrp/pkg/convert/from_json.go index 4331d007..0cf69573 100644 --- a/hrp/pkg/convert/from_json.go +++ b/hrp/pkg/convert/from_json.go @@ -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") diff --git a/hrp/pkg/convert/from_postman.go b/hrp/pkg/convert/from_postman.go index 6a091765..89774aa2 100644 --- a/hrp/pkg/convert/from_postman.go +++ b/hrp/pkg/convert/from_postman.go @@ -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 } diff --git a/hrp/pkg/convert/from_swagger.go b/hrp/pkg/convert/from_swagger.go index f5c3fb8b..f353ca87 100644 --- a/hrp/pkg/convert/from_swagger.go +++ b/hrp/pkg/convert/from_swagger.go @@ -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) diff --git a/hrp/pkg/convert/from_yaml.go b/hrp/pkg/convert/from_yaml.go index c160d44b..02b04f34 100644 --- a/hrp/pkg/convert/from_yaml.go +++ b/hrp/pkg/convert/from_yaml.go @@ -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") diff --git a/hrp/pkg/convert/main.go b/hrp/pkg/convert/main.go index fcd3fac7..3573cb18 100644 --- a/hrp/pkg/convert/main.go +++ b/hrp/pkg/convert/main.go @@ -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 diff --git a/hrp/pkg/convert/main_test.go b/hrp/pkg/convert/main_test.go index 23b91fef..dc1faa47 100644 --- a/hrp/pkg/convert/main_test.go +++ b/hrp/pkg/convert/main_test.go @@ -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{ diff --git a/hrp/pkg/uixt/action.go b/hrp/pkg/uixt/action.go index 27438c61..f401132e 100644 --- a/hrp/pkg/uixt/action.go +++ b/hrp/pkg/uixt/action.go @@ -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 diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index 32ebf6f4..857ed757 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -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 diff --git a/hrp/pkg/uixt/live_e2e.go b/hrp/pkg/uixt/live_e2e.go index 4befdaf9..f0118913 100644 --- a/hrp/pkg/uixt/live_e2e.go +++ b/hrp/pkg/uixt/live_e2e.go @@ -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() diff --git a/hrp/runner.go b/hrp/runner.go index 82742981..6d379ee9 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -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 } diff --git a/hrp/runner_test.go b/hrp/runner_test.go index dc18e8c4..2adc1496 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -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() } } diff --git a/hrp/step.go b/hrp/step.go index f0c69f35..958d2c7c 100644 --- a/hrp/step.go +++ b/hrp/step.go @@ -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) -} diff --git a/hrp/step_api.go b/hrp/step_api.go index fabb63fe..1f756cf5 100644 --- a/hrp/step_api.go +++ b/hrp/step_api.go @@ -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 diff --git a/hrp/step_mobile_ui.go b/hrp/step_mobile_ui.go index ea1dd828..31e47a40 100644 --- a/hrp/step_mobile_ui.go +++ b/hrp/step_mobile_ui.go @@ -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()) diff --git a/hrp/step_rendezvous.go b/hrp/step_rendezvous.go index c16d6bba..13664def 100644 --- a/hrp/step_rendezvous.go +++ b/hrp/step_rendezvous.go @@ -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 } diff --git a/hrp/step_request.go b/hrp/step_request.go index bd1fe8c1..56719f9a 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -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 } diff --git a/hrp/step_request_test.go b/hrp/step_request_test.go index 6f6fa458..8795ee1a 100644 --- a/hrp/step_request_test.go +++ b/hrp/step_request_test.go @@ -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") } diff --git a/hrp/step_shell.go b/hrp/step_shell.go index 1de6bee8..5a3ba692 100644 --- a/hrp/step_shell.go +++ b/hrp/step_shell.go @@ -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, diff --git a/hrp/step_testcase.go b/hrp/step_testcase.go index a28a4e35..dce4f7e5 100644 --- a/hrp/step_testcase.go +++ b/hrp/step_testcase.go @@ -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 diff --git a/hrp/step_thinktime.go b/hrp/step_thinktime.go index 63136933..01c21395 100644 --- a/hrp/step_thinktime.go +++ b/hrp/step_thinktime.go @@ -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, } diff --git a/hrp/step_transaction.go b/hrp/step_transaction.go index ac6cf1d8..a6666503 100644 --- a/hrp/step_transaction.go +++ b/hrp/step_transaction.go @@ -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)). diff --git a/hrp/step_websocket.go b/hrp/step_websocket.go index 23e371c1..ebd5cc0a 100644 --- a/hrp/step_websocket.go +++ b/hrp/step_websocket.go @@ -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") diff --git a/hrp/testcase.go b/hrp/testcase.go index e8ebe08d..93693f30 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -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")