From 1145f424b16494359e9b4260ef35313e6e9c6c51 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Sat, 14 Jun 2025 12:11:04 +0800 Subject: [PATCH] feat: implement two-level auto popup handler configuration - Add AutoPopupHandler field to both TConfig and StepConfig - Support testcase-level global configuration via TConfig.EnableAutoPopupHandler() - Support step-level specific configuration via StepMobile.EnableAutoPopupHandler() - Priority: testcase config > step config > default disabled - Simplify Loops field type from *types.IntOrString to int in StepConfig - Update documentation to reflect new structure --- config.go | 10 +++++--- docs/dev-instruct.md | 18 +++++++------- internal/version/VERSION | 2 +- runner.go | 54 +++++++++++----------------------------- step.go | 19 +++++++------- step_request.go | 5 +--- step_ui.go | 31 +++++++++++++++-------- 7 files changed, 61 insertions(+), 78 deletions(-) diff --git a/config.go b/config.go index 9abcc6a9..34399576 100644 --- a/config.go +++ b/config.go @@ -43,8 +43,8 @@ type TConfig struct { Path string `json:"path,omitempty" yaml:"path,omitempty"` // testcase file path PluginSetting *PluginConfig `json:"plugin,omitempty" yaml:"plugin,omitempty"` // plugin config MCPConfigPath string `json:"mcp_config_path,omitempty" yaml:"mcp_config_path,omitempty"` - AntiRisk bool `json:"anti_risk,omitempty" yaml:"anti_risk,omitempty"` // global anti-risk switch - IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"` + AntiRisk bool `json:"anti_risk,omitempty" yaml:"anti_risk,omitempty"` // global anti-risk switch + AutoPopupHandler bool `json:"auto_popup_handler,omitempty" yaml:"auto_popup_handler,omitempty"` // enable auto popup handler AIOptions *option.AIServiceOptions `json:"ai_options,omitempty" yaml:"ai_options,omitempty"` } @@ -230,8 +230,10 @@ func (c *TConfig) EnablePlugin() *TConfig { return c } -func (c *TConfig) DisableAutoPopupHandler() *TConfig { - c.IgnorePopup = true +// EnableAutoPopupHandler enables auto popup handler for current testcase. +// default to disable auto popup handler +func (c *TConfig) EnableAutoPopupHandler() *TConfig { + c.AutoPopupHandler = true return c } diff --git a/docs/dev-instruct.md b/docs/dev-instruct.md index 9d1eb771..c83b2453 100644 --- a/docs/dev-instruct.md +++ b/docs/dev-instruct.md @@ -220,15 +220,15 @@ v5 版本增加了完善的超时和中断处理机制: 步骤配置支持更多选项: ```go 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 *types.IntOrString `json:"loops,omitempty" yaml:"loops,omitempty"` - IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"` + 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 *types.IntOrString `json:"loops,omitempty" yaml:"loops,omitempty"` + AutoPopupHandler bool `json:"auto_popup_handler,omitempty" yaml:"auto_popup_handler,omitempty"` // enable auto popup handler for this step } ``` diff --git a/internal/version/VERSION b/internal/version/VERSION index cd56432d..bdf28d26 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-beta-2506132024 +v5.0.0-beta-2506141211 diff --git a/runner.go b/runner.go index 24c52e11..e565d628 100644 --- a/runner.go +++ b/runner.go @@ -11,7 +11,6 @@ import ( "os" "os/signal" "reflect" - "strconv" "strings" "syscall" "testing" @@ -752,9 +751,11 @@ func (r *SessionRunner) RunStep(step IStep) (stepResult *StepResult, err error) log.Info().Str("step", stepName).Str("type", stepType).Msg("run step start") // run times of step - loopTimes, err := r.getLoopTimes(step) - if err != nil { - return nil, errors.Wrap(err, "failed to get loop times") + loopTimes := step.Config().Loops + if loopTimes == 0 { + loopTimes = 1 // default run once + } else if loopTimes > 1 { + log.Info().Int("loops", loopTimes).Msg("set multiple loop times") } // run step with specified loop times @@ -804,6 +805,15 @@ func (r *SessionRunner) GetSummary() *TestCaseSummary { return r.summary } +// GenerateReport generates report for the testcase. +func (r *SessionRunner) GenerateReport() error { + summary := NewSummary() + caseSummary := r.GetSummary() + summary.AddCaseSummary(caseSummary) + summary.Time.Duration = time.Since(caseSummary.Time.StartAt).Seconds() + return summary.GenHTMLReport() +} + func (r *SessionRunner) ParseStep(step IStep) error { caseConfig := r.caseRunner.TestCase.Config.Get() stepConfig := step.Config() @@ -880,39 +890,3 @@ func (r *SessionRunner) GetSessionVariables() map[string]interface{} { func (r *SessionRunner) GetTransactions() map[string]map[TransactionType]time.Time { return r.transactions } - -func (r *SessionRunner) getLoopTimes(step IStep) (int, error) { - loops := step.Config().Loops - if loops == nil { - // default run once - return 1, nil - } - - loopTimes, err := loops.Value() - if err != nil { - parsed, err := r.caseRunner.parser.ParseString( - *loops.StringValue, step.Config().Variables) - if err != nil { - return 0, errors.Wrap(err, "failed to parse loop times") - } - switch v := parsed.(type) { - case int: - loopTimes = v - case string: - n, err := strconv.Atoi(v) - if err != nil { - return 0, errors.Wrap(err, "failed to parse loop times") - } - loopTimes = n - } - } - if loopTimes < 0 { - return 0, fmt.Errorf("loop times should be positive, got %d", loopTimes) - } else if loopTimes == 0 { - loopTimes = 1 - } else if loopTimes > 1 { - log.Info().Int("loops", loopTimes).Msg("set multiple loop times") - } - - return loopTimes, nil -} diff --git a/step.go b/step.go index 9b32ca58..6b232cea 100644 --- a/step.go +++ b/step.go @@ -3,7 +3,6 @@ package hrp import ( "github.com/httprunner/httprunner/v5/uixt" "github.com/httprunner/httprunner/v5/uixt/option" - "github.com/httprunner/httprunner/v5/uixt/types" ) type StepType string @@ -28,15 +27,15 @@ const ( ) 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 *types.IntOrString `json:"loops,omitempty" yaml:"loops,omitempty"` - IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"` + 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"` + AutoPopupHandler bool `json:"auto_popup_handler,omitempty" yaml:"auto_popup_handler,omitempty"` // enable auto popup handler for this step } // define struct for teststep diff --git a/step_request.go b/step_request.go index daeeace9..6976b06a 100644 --- a/step_request.go +++ b/step_request.go @@ -25,7 +25,6 @@ import ( "github.com/httprunner/httprunner/v5/internal/httpstat" "github.com/httprunner/httprunner/v5/internal/json" "github.com/httprunner/httprunner/v5/uixt/option" - "github.com/httprunner/httprunner/v5/uixt/types" ) type HTTPMethod string @@ -560,9 +559,7 @@ func (s *StepRequest) HTTP2() *StepRequest { // Loop specify running times for the current step func (s *StepRequest) Loop(times int) *StepRequest { - s.Loops = &types.IntOrString{ - IntValue: ×, - } + s.Loops = times return s } diff --git a/step_ui.go b/step_ui.go index 60adbf00..a7357ce9 100644 --- a/step_ui.go +++ b/step_ui.go @@ -458,11 +458,6 @@ func (s *StepMobile) ScreenShot(opts ...option.ActionOption) *StepMobile { return s } -func (s *StepMobile) DisableAutoPopupHandler() *StepMobile { - s.IgnorePopup = true - return s -} - func (s *StepMobile) ClosePopups(opts ...option.ActionOption) *StepMobile { s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_ClosePopups, @@ -472,6 +467,12 @@ func (s *StepMobile) ClosePopups(opts ...option.ActionOption) *StepMobile { return s } +// EnableAutoPopupHandler enables auto popup handler for this step. +func (s *StepMobile) EnableAutoPopupHandler() *StepMobile { + s.AutoPopupHandler = true + return s +} + func (s *StepMobile) Call(name string, fn func(), opts ...option.ActionOption) *StepMobile { s.obj().Actions = append(s.obj().Actions, option.MobileAction{ Method: option.ACTION_CallFunction, @@ -713,19 +714,19 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err var stepVariables map[string]interface{} var stepValidators []interface{} - var ignorePopup bool + var stepAutoPopupHandler bool var mobileStep *MobileUI switch stepMobile := step.(type) { case *StepMobile: mobileStep = stepMobile.obj() stepVariables = stepMobile.Variables - ignorePopup = stepMobile.IgnorePopup + stepAutoPopupHandler = stepMobile.AutoPopupHandler case *StepMobileUIValidation: mobileStep = stepMobile.obj() stepVariables = stepMobile.Variables stepValidators = stepMobile.Validators - ignorePopup = stepMobile.StepMobile.IgnorePopup + stepAutoPopupHandler = stepMobile.StepMobile.AutoPopupHandler default: return stepResult, errors.New("invalid mobile UI step type") } @@ -792,12 +793,22 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err stepResult.Actions = append(stepResult.Actions, actionResult) } - // automatic handling of pop-up windows on each step finished var config *TConfig if s.caseRunner != nil && s.caseRunner.Config != nil { config = s.caseRunner.Config.Get() } - if !ignorePopup && (config == nil || !config.IgnorePopup) && uiDriver != nil { + // automatic handling of pop-up windows on each step finished + // priority: testcase config > step config, default to disabled + shouldHandlePopup := false + if config != nil && config.AutoPopupHandler { + // testcase level config has higher priority + shouldHandlePopup = true + } else if stepAutoPopupHandler { + // step level config + shouldHandlePopup = true + } + + if shouldHandlePopup && uiDriver != nil { startTime := time.Now() actionResult := &ActionResult{ MobileAction: option.MobileAction{