mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 23:51:25 +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:
@@ -43,8 +43,8 @@ type TConfig struct {
|
|||||||
Path string `json:"path,omitempty" yaml:"path,omitempty"` // testcase file path
|
Path string `json:"path,omitempty" yaml:"path,omitempty"` // testcase file path
|
||||||
PluginSetting *PluginConfig `json:"plugin,omitempty" yaml:"plugin,omitempty"` // plugin config
|
PluginSetting *PluginConfig `json:"plugin,omitempty" yaml:"plugin,omitempty"` // plugin config
|
||||||
MCPConfigPath string `json:"mcp_config_path,omitempty" yaml:"mcp_config_path,omitempty"`
|
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
|
AntiRisk bool `json:"anti_risk,omitempty" yaml:"anti_risk,omitempty"` // global anti-risk switch
|
||||||
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
|
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"`
|
AIOptions *option.AIServiceOptions `json:"ai_options,omitempty" yaml:"ai_options,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,8 +230,10 @@ func (c *TConfig) EnablePlugin() *TConfig {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TConfig) DisableAutoPopupHandler() *TConfig {
|
// EnableAutoPopupHandler enables auto popup handler for current testcase.
|
||||||
c.IgnorePopup = true
|
// default to disable auto popup handler
|
||||||
|
func (c *TConfig) EnableAutoPopupHandler() *TConfig {
|
||||||
|
c.AutoPopupHandler = true
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -220,15 +220,15 @@ v5 版本增加了完善的超时和中断处理机制:
|
|||||||
步骤配置支持更多选项:
|
步骤配置支持更多选项:
|
||||||
```go
|
```go
|
||||||
type StepConfig struct {
|
type StepConfig struct {
|
||||||
StepName string `json:"name" yaml:"name"` // required
|
StepName string `json:"name" yaml:"name"` // required
|
||||||
Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"`
|
Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"`
|
||||||
SetupHooks []string `json:"setup_hooks,omitempty" yaml:"setup_hooks,omitempty"`
|
SetupHooks []string `json:"setup_hooks,omitempty" yaml:"setup_hooks,omitempty"`
|
||||||
TeardownHooks []string `json:"teardown_hooks,omitempty" yaml:"teardown_hooks,omitempty"`
|
TeardownHooks []string `json:"teardown_hooks,omitempty" yaml:"teardown_hooks,omitempty"`
|
||||||
Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"`
|
Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"`
|
||||||
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
|
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
|
||||||
StepExport []string `json:"export,omitempty" yaml:"export,omitempty"`
|
StepExport []string `json:"export,omitempty" yaml:"export,omitempty"`
|
||||||
Loops *types.IntOrString `json:"loops,omitempty" yaml:"loops,omitempty"`
|
Loops *types.IntOrString `json:"loops,omitempty" yaml:"loops,omitempty"`
|
||||||
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,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
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"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")
|
log.Info().Str("step", stepName).Str("type", stepType).Msg("run step start")
|
||||||
|
|
||||||
// run times of step
|
// run times of step
|
||||||
loopTimes, err := r.getLoopTimes(step)
|
loopTimes := step.Config().Loops
|
||||||
if err != nil {
|
if loopTimes == 0 {
|
||||||
return nil, errors.Wrap(err, "failed to get loop times")
|
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
|
// run step with specified loop times
|
||||||
@@ -804,6 +805,15 @@ func (r *SessionRunner) GetSummary() *TestCaseSummary {
|
|||||||
return r.summary
|
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 {
|
func (r *SessionRunner) ParseStep(step IStep) error {
|
||||||
caseConfig := r.caseRunner.TestCase.Config.Get()
|
caseConfig := r.caseRunner.TestCase.Config.Get()
|
||||||
stepConfig := step.Config()
|
stepConfig := step.Config()
|
||||||
@@ -880,39 +890,3 @@ func (r *SessionRunner) GetSessionVariables() map[string]interface{} {
|
|||||||
func (r *SessionRunner) GetTransactions() map[string]map[TransactionType]time.Time {
|
func (r *SessionRunner) GetTransactions() map[string]map[TransactionType]time.Time {
|
||||||
return r.transactions
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package hrp
|
|||||||
import (
|
import (
|
||||||
"github.com/httprunner/httprunner/v5/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
"github.com/httprunner/httprunner/v5/uixt/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type StepType string
|
type StepType string
|
||||||
@@ -28,15 +27,15 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StepConfig struct {
|
type StepConfig struct {
|
||||||
StepName string `json:"name" yaml:"name"` // required
|
StepName string `json:"name" yaml:"name"` // required
|
||||||
Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"`
|
Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"`
|
||||||
SetupHooks []string `json:"setup_hooks,omitempty" yaml:"setup_hooks,omitempty"`
|
SetupHooks []string `json:"setup_hooks,omitempty" yaml:"setup_hooks,omitempty"`
|
||||||
TeardownHooks []string `json:"teardown_hooks,omitempty" yaml:"teardown_hooks,omitempty"`
|
TeardownHooks []string `json:"teardown_hooks,omitempty" yaml:"teardown_hooks,omitempty"`
|
||||||
Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"`
|
Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"`
|
||||||
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
|
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
|
||||||
StepExport []string `json:"export,omitempty" yaml:"export,omitempty"`
|
StepExport []string `json:"export,omitempty" yaml:"export,omitempty"`
|
||||||
Loops *types.IntOrString `json:"loops,omitempty" yaml:"loops,omitempty"`
|
Loops int `json:"loops,omitempty" yaml:"loops,omitempty"`
|
||||||
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
|
AutoPopupHandler bool `json:"auto_popup_handler,omitempty" yaml:"auto_popup_handler,omitempty"` // enable auto popup handler for this step
|
||||||
}
|
}
|
||||||
|
|
||||||
// define struct for teststep
|
// define struct for teststep
|
||||||
|
|||||||
+1
-4
@@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/httprunner/httprunner/v5/internal/httpstat"
|
"github.com/httprunner/httprunner/v5/internal/httpstat"
|
||||||
"github.com/httprunner/httprunner/v5/internal/json"
|
"github.com/httprunner/httprunner/v5/internal/json"
|
||||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
"github.com/httprunner/httprunner/v5/uixt/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPMethod string
|
type HTTPMethod string
|
||||||
@@ -560,9 +559,7 @@ func (s *StepRequest) HTTP2() *StepRequest {
|
|||||||
|
|
||||||
// Loop specify running times for the current step
|
// Loop specify running times for the current step
|
||||||
func (s *StepRequest) Loop(times int) *StepRequest {
|
func (s *StepRequest) Loop(times int) *StepRequest {
|
||||||
s.Loops = &types.IntOrString{
|
s.Loops = times
|
||||||
IntValue: ×,
|
|
||||||
}
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
-10
@@ -458,11 +458,6 @@ func (s *StepMobile) ScreenShot(opts ...option.ActionOption) *StepMobile {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepMobile) DisableAutoPopupHandler() *StepMobile {
|
|
||||||
s.IgnorePopup = true
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepMobile) ClosePopups(opts ...option.ActionOption) *StepMobile {
|
func (s *StepMobile) ClosePopups(opts ...option.ActionOption) *StepMobile {
|
||||||
s.obj().Actions = append(s.obj().Actions, option.MobileAction{
|
s.obj().Actions = append(s.obj().Actions, option.MobileAction{
|
||||||
Method: option.ACTION_ClosePopups,
|
Method: option.ACTION_ClosePopups,
|
||||||
@@ -472,6 +467,12 @@ func (s *StepMobile) ClosePopups(opts ...option.ActionOption) *StepMobile {
|
|||||||
return s
|
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 {
|
func (s *StepMobile) Call(name string, fn func(), opts ...option.ActionOption) *StepMobile {
|
||||||
s.obj().Actions = append(s.obj().Actions, option.MobileAction{
|
s.obj().Actions = append(s.obj().Actions, option.MobileAction{
|
||||||
Method: option.ACTION_CallFunction,
|
Method: option.ACTION_CallFunction,
|
||||||
@@ -713,19 +714,19 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err
|
|||||||
|
|
||||||
var stepVariables map[string]interface{}
|
var stepVariables map[string]interface{}
|
||||||
var stepValidators []interface{}
|
var stepValidators []interface{}
|
||||||
var ignorePopup bool
|
var stepAutoPopupHandler bool
|
||||||
|
|
||||||
var mobileStep *MobileUI
|
var mobileStep *MobileUI
|
||||||
switch stepMobile := step.(type) {
|
switch stepMobile := step.(type) {
|
||||||
case *StepMobile:
|
case *StepMobile:
|
||||||
mobileStep = stepMobile.obj()
|
mobileStep = stepMobile.obj()
|
||||||
stepVariables = stepMobile.Variables
|
stepVariables = stepMobile.Variables
|
||||||
ignorePopup = stepMobile.IgnorePopup
|
stepAutoPopupHandler = stepMobile.AutoPopupHandler
|
||||||
case *StepMobileUIValidation:
|
case *StepMobileUIValidation:
|
||||||
mobileStep = stepMobile.obj()
|
mobileStep = stepMobile.obj()
|
||||||
stepVariables = stepMobile.Variables
|
stepVariables = stepMobile.Variables
|
||||||
stepValidators = stepMobile.Validators
|
stepValidators = stepMobile.Validators
|
||||||
ignorePopup = stepMobile.StepMobile.IgnorePopup
|
stepAutoPopupHandler = stepMobile.StepMobile.AutoPopupHandler
|
||||||
default:
|
default:
|
||||||
return stepResult, errors.New("invalid mobile UI step type")
|
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)
|
stepResult.Actions = append(stepResult.Actions, actionResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
// automatic handling of pop-up windows on each step finished
|
|
||||||
var config *TConfig
|
var config *TConfig
|
||||||
if s.caseRunner != nil && s.caseRunner.Config != nil {
|
if s.caseRunner != nil && s.caseRunner.Config != nil {
|
||||||
config = s.caseRunner.Config.Get()
|
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()
|
startTime := time.Now()
|
||||||
actionResult := &ActionResult{
|
actionResult := &ActionResult{
|
||||||
MobileAction: option.MobileAction{
|
MobileAction: option.MobileAction{
|
||||||
|
|||||||
Reference in New Issue
Block a user