Files
httprunner/docs/dev-instruct.md
lilong.129 1145f424b1 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
2025-06-14 12:11:04 +08:00

337 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 代码阅读指南
## 核心数据结构
HttpRunner 以 `TestCase` 为核心,将任意测试场景抽象为有序步骤的集合。
```go
type TestCase struct {
Config IConfig `json:"config" yaml:"config"`
TestSteps []IStep `json:"teststeps" yaml:"teststeps"`
}
type IConfig interface {
Get() *TConfig
}
```
其中,测试步骤 `IStep` 采用了 `go interface` 的设计理念,支持进行任意拓展;步骤内容统一在 `Run` 方法中进行实现。
```go
type IStep interface {
Name() string
Type() StepType
Config() *StepConfig
Run(*SessionRunner) (*StepResult, error)
}
```
我们只需遵循 `IStep` 的接口定义,即可实现各种类型的测试步骤类型。当前 hrp 已支持的步骤类型包括:
- [request](step_request.go):发起单次 HTTP 请求
- [api](step_api.go):引用执行其它 API 文件
- [testcase](step_testcase.go):引用执行其它测试用例文件
- [thinktime](step_thinktime.go):思考时间,按照配置的逻辑进行等待
- [transaction](step_transaction.go):事务机制,用于压测
- [rendezvous](step_rendezvous.go):集合点机制,用于压测
- [websocket](step_websocket.go)WebSocket 通信
- [android](step_ui.go)Android UI 自动化
- [ios](step_ui.go)iOS UI 自动化
- [harmony](step_ui.go)Harmony UI 自动化
- [browser](step_ui.go):浏览器 UI 自动化
- [shell](step_shell.go):执行 shell 命令
- [function](step_function.go):自定义函数调用
基于该机制,我们可以扩展支持新的协议类型,例如 HTTP2/WebSocket/RPC 等;同时也可以支持新的测试类型,例如 UI 自动化。甚至我们还可以在一个测试用例中混合调用多种不同的 Step 类型,例如实现 HTTP/RPC/UI 混合场景。
## 运行主流程
### 整体控制器 HRPRunner
执行测试时,会初始化一个 `HRPRunner`,用于控制测试的执行策略。
```go
type HRPRunner struct {
t *testing.T
failfast bool
httpStatOn bool
requestsLogOn bool
pluginLogOn bool
venv string
saveTests bool
genHTMLReport bool
httpClient *http.Client
http2Client *http.Client
wsDialer *websocket.Dialer
caseTimeoutTimer *time.Timer // case timeout timer
interruptSignal chan os.Signal // interrupt signal channel
}
func (r *HRPRunner) Run(testcases ...ITestCase) error
func NewCaseRunner(testcase TestCase, hrpRunner *HRPRunner) (*CaseRunner, error)
```
重点关注的方法:
- Run测试执行的主入口支持运行一个或多个测试用例
- NewCaseRunner针对给定的测试用例初始化一个 CaseRunner
HRPRunner 支持多种配置选项:
- SetFailfast配置是否在步骤失败时立即停止
- SetRequestsLogOn开启请求响应详细日志
- SetHTTPStatOn开启 HTTP 延迟统计
- SetPluginLogOn开启插件日志
- SetProxyUrl配置代理 URL用于抓包调试
- SetRequestTimeout配置全局请求超时
- SetCaseTimeout配置测试用例超时
- GenHTMLReport生成 HTML 测试报告
### 用例执行器 CaseRunner
针对每个测试用例,采用 CaseRunner 存储其公共信息,包括 plugin/parser
```go
type CaseRunner struct {
TestCase // each testcase init its own CaseRunner
hrpRunner *HRPRunner // all case runners share one HRPRunner
parser *Parser // each CaseRunner init its own Parser
parametersIterator *ParametersIterator
}
func (r *CaseRunner) NewSession() *SessionRunner
```
重点关注一个方法:
- NewSession测试用例的每一次执行对应一个 SessionRunner
### SessionRunner
测试用例的具体执行都由 `SessionRunner` 完成,每个 session 实例中除了包含测试用例自身内容外,还会包含测试过程的 session 数据和最终测试结果 summary。
```go
type SessionRunner struct {
caseRunner *CaseRunner // all session runners share one CaseRunner
sessionVariables map[string]interface{} // testcase execution session variables
summary *TestCaseSummary // record test case summary
// transactions stores transaction timing info.
// key is transaction name, value is map of transaction type and time, e.g. start time and end time.
transactions map[string]map[TransactionType]time.Time
// websocket session
ws *wsSession
}
func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCaseSummary, err error)
func (r *SessionRunner) RunStep(step IStep) (stepResult *StepResult, err error)
func (r *SessionRunner) ParseStep(step IStep) error
```
重点关注的方法:
- Start启动执行用例依次执行所有测试步骤
- RunStep执行单个测试步骤支持循环执行
- ParseStep解析步骤配置包括变量替换和验证器解析
```go
func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCaseSummary, err error) {
// report GA event
sdk.SendGA4Event("hrp_session_runner_start", nil)
config := r.caseRunner.TestCase.Config.Get()
log.Info().Str("testcase", config.Name).Msg("run testcase start")
// update config variables with given variables
r.InitWithParameters(givenVars)
defer func() {
// release session resources
r.ReleaseResources()
summary = r.summary
summary.Name = config.Name
summary.Time.Duration = time.Since(summary.Time.StartAt).Seconds()
// ... handle export variables and logs
}()
// run step in sequential order
for _, step := range r.caseRunner.TestSteps {
select {
case <-r.caseRunner.hrpRunner.caseTimeoutTimer.C:
log.Warn().Msg("timeout in session runner")
return summary, errors.Wrap(code.TimeoutError, "session runner timeout")
case <-r.caseRunner.hrpRunner.interruptSignal:
log.Warn().Msg("interrupted in session runner")
return summary, errors.Wrap(code.InterruptError, "session runner interrupted")
default:
_, err := r.RunStep(step)
if err == nil {
continue
}
// interrupted or timeout, abort running
if errors.Is(err, code.InterruptError) || errors.Is(err, code.TimeoutError) {
return summary, err
}
// check if failfast
if r.caseRunner.hrpRunner.failfast {
return summary, errors.Wrap(err, "abort running due to failfast setting")
}
}
}
log.Info().Str("testcase", config.Name).Msg("run testcase end")
return summary, nil
}
```
在主流程中SessionRunner 并不需要关注 step 的具体类型,统一都是调用 `r.RunStep(step)`,具体实现逻辑都在对应 step 的 `Run(*SessionRunner)` 方法中。
## 新增特性
### 1. 超时和中断处理
v5 版本增加了完善的超时和中断处理机制:
- 支持测试用例级别的超时控制
- 支持优雅的中断处理SIGTERM, SIGINT
- 在执行过程中实时检查超时和中断信号
### 2. 多平台 UI 自动化
统一的 UI 自动化接口,支持多个平台:
- **Android**:基于 ADB 和 UIAutomator2
- **iOS**:基于 WebDriverAgent (WDA)
- **Harmony**:基于 HDC (Harmony Device Connector)
- **Browser**:基于 WebDriver 协议
### 3. AI 集成
集成了大模型能力:
- 支持 AI 驱动的 UI 操作
- 通过 MCP (Model Context Protocol) 与大模型通信
- 支持自然语言描述的测试步骤
### 4. 增强的步骤配置
步骤配置支持更多选项:
```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"`
AutoPopupHandler bool `json:"auto_popup_handler,omitempty" yaml:"auto_popup_handler,omitempty"` // enable auto popup handler for this step
}
```
### 5. 协议支持扩展
除了 HTTP/HTTPS还支持
- HTTP/2 协议
- WebSocket 通信
- 自定义函数调用
### 6. 资源管理
增强的资源管理机制:
- 自动释放会话资源
- UI 驱动器缓存管理
- 日志收集和聚合
## UI 自动化步骤示例
### StepMobile 结构
UI 自动化步骤统一使用 `StepMobile` 结构:
```go
type StepMobile struct {
StepConfig
Mobile *MobileUI `json:"mobile,omitempty" yaml:"mobile,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"`
Browser *MobileUI `json:"browser,omitempty" yaml:"browser,omitempty"`
}
```
### 常用 UI 操作方法
```go
// 基础操作
func (s *StepMobile) TapXY(x, y float64, opts ...option.ActionOption) *StepMobile
func (s *StepMobile) TapByOCR(ocrText string, opts ...option.ActionOption) *StepMobile
func (s *StepMobile) TapByCV(imagePath string, opts ...option.ActionOption) *StepMobile
func (s *StepMobile) AIAction(prompt string, opts ...option.ActionOption) *StepMobile
// 应用管理
func (s *StepMobile) AppLaunch(bundleId string) *StepMobile
func (s *StepMobile) AppTerminate(bundleId string) *StepMobile
func (s *StepMobile) InstallApp(path string) *StepMobile
// 滑动操作
func (s *StepMobile) Swipe(sx, sy, ex, ey float64, opts ...option.ActionOption) *StepMobile
func (s *StepMobile) SwipeUp(opts ...option.ActionOption) *StepMobile
func (s *StepMobile) SwipeDown(opts ...option.ActionOption) *StepMobile
// 输入操作
func (s *StepMobile) Input(text string, opts ...option.ActionOption) *StepMobile
// 等待操作
func (s *StepMobile) Sleep(nSeconds float64, startTime ...time.Time) *StepMobile
func (s *StepMobile) SleepRandom(params ...float64) *StepMobile
// 验证操作
func (s *StepMobile) Validate() *StepMobileUIValidation
```
### UI 验证方法
```go
// OCR 文本验证
func (s *StepMobileUIValidation) AssertOCRExists(expectedText string, msg ...string) *StepMobileUIValidation
func (s *StepMobileUIValidation) AssertOCRNotExists(expectedText string, msg ...string) *StepMobileUIValidation
// 图像验证
func (s *StepMobileUIValidation) AssertImageExists(expectedImagePath string, msg ...string) *StepMobileUIValidation
func (s *StepMobileUIValidation) AssertImageNotExists(expectedImagePath string, msg ...string) *StepMobileUIValidation
// AI 验证
func (s *StepMobileUIValidation) AssertAI(prompt string, msg ...string) *StepMobileUIValidation
// 应用状态验证
func (s *StepMobileUIValidation) AssertAppInForeground(packageName string, msg ...string) *StepMobileUIValidation
func (s *StepMobileUIValidation) AssertAppNotInForeground(packageName string, msg ...string) *StepMobileUIValidation
```
## 开发建议
### 1. 添加新的步骤类型
要添加新的步骤类型,需要:
1.`step.go` 中定义新的 `StepType` 常量
2. 创建实现 `IStep` 接口的结构体
3.`testcase.go``loadISteps` 方法中添加对应的处理逻辑
### 2. 扩展 UI 平台支持
要支持新的 UI 平台:
1.`uixt/` 目录下实现对应的驱动器
2.`StepMobile` 中添加新的平台字段
3.`obj()` 方法中添加对应的处理逻辑
### 3. 调试技巧
- 使用 `SetRequestsLogOn()` 开启详细的请求日志
- 使用 `SetPluginLogOn()` 开启插件日志
- 使用 `SetProxyUrl()` 配置代理进行抓包分析
- 查看生成的 HTML 报告了解执行详情