mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-27 18:41:33 +08:00
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
This commit is contained in:
10
config.go
10
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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
v5.0.0-beta-2506132024
|
||||
v5.0.0-beta-2506141211
|
||||
|
||||
54
runner.go
54
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
|
||||
}
|
||||
|
||||
19
step.go
19
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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
31
step_ui.go
31
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{
|
||||
|
||||
Reference in New Issue
Block a user