Files
httprunner/docs/uixt/operations.md
2025-06-11 14:57:08 +08:00

885 lines
17 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 UIXT 提供了丰富的 UI 操作接口,支持触摸、滑动、输入、应用管理等各种操作。本文档详细介绍每种操作的使用方法和最佳实践。
## 基础操作
### 点击操作
#### 相对坐标点击
使用 0-1 范围的相对坐标进行点击,适用于不同屏幕尺寸的设备。
```go
// 点击屏幕中心
err := driver.TapXY(0.5, 0.5)
// 点击右上角
err := driver.TapXY(0.9, 0.1)
// 点击左下角
err := driver.TapXY(0.1, 0.9)
```
#### 绝对坐标点击
使用像素坐标进行精确点击。
```go
// 点击绝对坐标 (500, 800)
err := driver.TapAbsXY(500, 800)
// 获取屏幕尺寸后计算坐标
size, err := driver.WindowSize()
if err == nil {
centerX := float64(size.Width) / 2
centerY := float64(size.Height) / 2
err = driver.TapAbsXY(centerX, centerY)
}
```
#### 选择器点击
通过文本或其他选择器进行点击。
```go
// 通过文本点击
err := driver.TapBySelector("登录")
err := driver.TapBySelector("text=登录")
// 通过资源ID点击Android
err := driver.TapBySelector("resource-id=com.example:id/login_button")
// 通过XPath点击Web
err := driver.TapBySelector("//button[@id='login']")
// 通过CSS选择器点击Web
err := driver.TapBySelector("#login-button")
```
#### 双击操作
```go
// 双击指定坐标
err := driver.DoubleTap(100, 200)
// 双击相对坐标
err := driver.DoubleTap(0.5, 0.5)
```
#### 长按操作
```go
// 长按指定坐标
err := driver.TouchAndHold(150, 300)
// 带选项的长按
err := driver.TouchAndHold(150, 300,
option.WithDuration(2*time.Second),
)
```
### 滑动操作
#### 基础滑动
```go
// 从下往上滑动(向上滚动)
err := driver.Swipe(0.5, 0.8, 0.5, 0.2)
// 从上往下滑动(向下滚动)
err := driver.Swipe(0.5, 0.2, 0.5, 0.8)
// 从右往左滑动(向左翻页)
err := driver.Swipe(0.8, 0.5, 0.2, 0.5)
// 从左往右滑动(向右翻页)
err := driver.Swipe(0.2, 0.5, 0.8, 0.5)
```
#### 带选项的滑动
```go
// 慢速滑动
err := driver.Swipe(0.5, 0.8, 0.5, 0.2,
option.WithDuration(2*time.Second),
)
// 快速滑动
err := driver.Swipe(0.5, 0.8, 0.5, 0.2,
option.WithDuration(200*time.Millisecond),
)
// 多步滑动
err := driver.Swipe(0.5, 0.8, 0.5, 0.2,
option.WithSteps(20),
)
```
#### 拖拽操作
```go
// 拖拽元素从一个位置到另一个位置
err := driver.Drag(0.2, 0.3, 0.8, 0.7)
// 带持续时间的拖拽
err := driver.Drag(0.2, 0.3, 0.8, 0.7,
option.WithDuration(1*time.Second),
)
```
### 输入操作
#### 文本输入
```go
// 基础文本输入
err := driver.Input("Hello World")
// 输入中文
err := driver.Input("你好世界")
// 输入特殊字符
err := driver.Input("user@example.com")
err := driver.Input("P@ssw0rd123!")
```
#### 退格操作
```go
// 删除一个字符
err := driver.Backspace(1)
// 删除多个字符
err := driver.Backspace(5)
// 清空输入框(删除大量字符)
err := driver.Backspace(100)
```
#### 输入法设置
```go
// 设置输入法Android
err := driver.SetIme("com.google.android.inputmethod.latin/.LatinIME")
// 设置中文输入法
err := driver.SetIme("com.sohu.inputmethod.sogou/.SogouIME")
```
### 按键操作
#### 系统按键
```go
// Home 键
err := driver.Home()
// Back 键Android
err := driver.Back()
// 通用按键操作
err := driver.PressButton(types.DeviceButtonHome)
err := driver.PressButton(types.DeviceButtonBack)
err := driver.PressButton(types.DeviceButtonVolumeUp)
err := driver.PressButton(types.DeviceButtonVolumeDown)
```
#### 特殊按键
```go
// 电源键
err := driver.PressButton(types.DeviceButtonPower)
// 菜单键
err := driver.PressButton(types.DeviceButtonMenu)
// 搜索键
err := driver.PressButton(types.DeviceButtonSearch)
```
## 高级操作
### 智能操作
#### OCR 识别点击
```go
// 通过 OCR 识别文本并点击
err := xtDriver.TapOCR("登录")
// 使用正则表达式匹配
err := xtDriver.TapOCR(`\d{4}`, option.WithRegex(true))
// 选择特定索引的文本
err := xtDriver.TapOCR("按钮", option.WithIndex(1))
```
#### 计算机视觉点击
```go
// 通过 CV 识别 UI 元素并点击
err := xtDriver.TapCV("button", "登录按钮")
// 识别图标并点击
err := xtDriver.TapCV("icon", "设置图标")
```
#### 智能滑动查找
```go
// 滑动查找应用并点击
err := xtDriver.SwipeToTapApp("微信")
// 滑动查找文本并点击
err := xtDriver.SwipeToTapText("设置")
// 滑动查找多个文本中的一个
err := xtDriver.SwipeToTapTexts([]string{"登录", "Sign In", "ログイン"})
```
### 组合操作
#### 登录流程
```go
func performLogin(driver IDriver, username, password string) error {
// 1. 点击用户名输入框
err := driver.TapBySelector("用户名")
if err != nil {
return err
}
// 2. 输入用户名
err = driver.Input(username)
if err != nil {
return err
}
// 3. 点击密码输入框
err = driver.TapBySelector("密码")
if err != nil {
return err
}
// 4. 输入密码
err = driver.Input(password)
if err != nil {
return err
}
// 5. 点击登录按钮
err = driver.TapBySelector("登录")
if err != nil {
return err
}
return nil
}
```
#### 列表滚动查找
```go
func findInList(driver IDriver, targetText string) error {
maxSwipes := 10
for i := 0; i < maxSwipes; i++ {
// 尝试点击目标文本
err := driver.TapBySelector(targetText)
if err == nil {
return nil // 找到并点击成功
}
// 向上滑动继续查找
err = driver.Swipe(0.5, 0.8, 0.5, 0.2)
if err != nil {
return err
}
// 等待滑动完成
time.Sleep(500 * time.Millisecond)
}
return fmt.Errorf("text '%s' not found after %d swipes", targetText, maxSwipes)
}
```
#### 表单填写
```go
func fillForm(driver IDriver, formData map[string]string) error {
for fieldName, value := range formData {
// 点击字段
err := driver.TapBySelector(fieldName)
if err != nil {
return fmt.Errorf("failed to tap field %s: %w", fieldName, err)
}
// 清空现有内容
err = driver.Backspace(50)
if err != nil {
return fmt.Errorf("failed to clear field %s: %w", fieldName, err)
}
// 输入新值
err = driver.Input(value)
if err != nil {
return fmt.Errorf("failed to input value for field %s: %w", fieldName, err)
}
}
return nil
}
```
## 应用管理
### 应用生命周期
#### 启动应用
```go
// 启动应用
err := driver.AppLaunch("com.example.app")
// 启动系统应用
err := driver.AppLaunch("com.android.settings") // Android 设置
err := driver.AppLaunch("com.apple.Preferences") // iOS 设置
```
#### 终止应用
```go
// 终止应用
terminated, err := driver.AppTerminate("com.example.app")
if err != nil {
return err
}
if terminated {
fmt.Println("App terminated successfully")
} else {
fmt.Println("App was not running")
}
```
#### 清理应用数据
```go
// 清理应用数据和缓存Android
err := driver.AppClear("com.example.app")
```
### 应用信息
#### 获取前台应用
```go
// 获取当前前台应用信息
appInfo, err := driver.ForegroundInfo()
if err != nil {
return err
}
fmt.Printf("Current app: %s (%s)\n", appInfo.Name, appInfo.PackageName)
```
#### 列出已安装应用
```go
// 列出所有已安装的应用(需要扩展功能)
packages, err := xtDriver.ListPackages()
if err != nil {
return err
}
for _, pkg := range packages {
fmt.Printf("Package: %s\n", pkg)
}
```
## 屏幕操作
### 截图操作
#### 基础截图
```go
// 获取屏幕截图
screenshot, err := driver.ScreenShot()
if err != nil {
return err
}
// 保存截图到文件
err = ioutil.WriteFile("screenshot.png", screenshot.Bytes(), 0644)
```
#### 带选项的截图
```go
// 高质量截图
screenshot, err := driver.ScreenShot(
option.WithQuality(100),
)
// 指定格式截图
screenshot, err := driver.ScreenShot(
option.WithFormat("jpeg"),
)
```
### 屏幕录制
```go
// 开始录制
videoPath, err := driver.ScreenRecord(
option.WithDuration(30*time.Second),
option.WithBitRate(4000000),
)
if err != nil {
return err
}
fmt.Printf("Video saved to: %s\n", videoPath)
```
### 屏幕信息
#### 获取屏幕尺寸
```go
// 获取屏幕尺寸
size, err := driver.WindowSize()
if err != nil {
return err
}
fmt.Printf("Screen size: %dx%d\n", size.Width, size.Height)
```
#### 获取屏幕方向
```go
// 获取当前方向
orientation, err := driver.Orientation()
if err != nil {
return err
}
fmt.Printf("Orientation: %s\n", orientation)
// 获取旋转角度
rotation, err := driver.Rotation()
if err != nil {
return err
}
fmt.Printf("Rotation: %d degrees\n", rotation)
```
#### 设置屏幕方向
```go
// 设置为横屏
err := driver.SetRotation(types.RotationLandscape)
// 设置为竖屏
err := driver.SetRotation(types.RotationPortrait)
// 设置为倒置横屏
err := driver.SetRotation(types.RotationLandscapeFlipped)
```
## 文件操作
### 文件传输
#### 推送文件到设备
```go
// 推送单个文件
err := driver.PushFile("/local/path/file.txt", "/sdcard/Download/")
// 推送图片
err := driver.PushImage("/local/path/image.jpg")
```
#### 从设备拉取文件
```go
// 拉取文件到本地
err := driver.PullFiles("/local/download/", "/sdcard/Download/")
// 拉取图片
err := driver.PullImages("/local/images/")
```
#### 清理文件
```go
// 清理指定路径的文件
err := driver.ClearFiles("/sdcard/Download/temp.txt")
// 清理图片
err := driver.ClearImages()
```
## Web 操作
### 页面导航
```go
// 导航到URL仅Web驱动
if webDriver, ok := driver.(*BrowserDriver); ok {
err := webDriver.NavigateTo("https://example.com")
// 刷新页面
err = webDriver.Refresh()
// 后退
err = webDriver.GoBack()
// 前进
err = webDriver.GoForward()
}
```
### 元素操作
#### 悬停操作
```go
// 悬停在元素上主要用于Web
err := driver.HoverBySelector("#menu-item")
// 悬停在坐标上
err := driver.HoverXY(0.5, 0.3)
```
#### 右键点击
```go
// 右键点击坐标
err := driver.SecondaryClick(100, 200)
// 右键点击元素
err := driver.SecondaryClickBySelector("#context-menu-target")
```
### JavaScript 执行
```go
// 执行JavaScript仅Web驱动
if webDriver, ok := driver.(*BrowserDriver); ok {
result, err := webDriver.ExecuteScript("return document.title;")
if err == nil {
fmt.Printf("Page title: %s\n", result)
}
// 执行复杂脚本
script := `
var element = document.getElementById('target');
element.style.backgroundColor = 'red';
return element.innerText;
`
result, err = webDriver.ExecuteScript(script)
}
```
## 等待和同步
### 显式等待
```go
// 等待元素出现
err := waitForElement(driver, "登录", 10*time.Second)
func waitForElement(driver IDriver, selector string, timeout time.Duration) error {
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
err := driver.TapBySelector(selector)
if err == nil {
return nil // 元素找到
}
time.Sleep(500 * time.Millisecond)
}
return fmt.Errorf("element '%s' not found within %v", selector, timeout)
}
```
### 条件等待
```go
// 等待条件满足
err := waitForCondition(func() bool {
// 检查某个条件
appInfo, err := driver.ForegroundInfo()
return err == nil && appInfo.PackageName == "com.target.app"
}, 30*time.Second)
func waitForCondition(condition func() bool, timeout time.Duration) error {
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
if condition() {
return nil
}
time.Sleep(1 * time.Second)
}
return fmt.Errorf("condition not met within %v", timeout)
}
```
### 智能等待
```go
// 等待页面加载完成
func waitForPageLoad(driver IDriver) error {
// 等待一段时间让页面开始加载
time.Sleep(1 * time.Second)
// 连续检查页面是否稳定
var lastScreenshot []byte
stableCount := 0
for i := 0; i < 10; i++ {
screenshot, err := driver.ScreenShot()
if err != nil {
return err
}
currentScreenshot := screenshot.Bytes()
if lastScreenshot != nil && bytes.Equal(lastScreenshot, currentScreenshot) {
stableCount++
if stableCount >= 3 {
return nil // 页面稳定
}
} else {
stableCount = 0
}
lastScreenshot = currentScreenshot
time.Sleep(1 * time.Second)
}
return fmt.Errorf("page did not stabilize")
}
```
## 错误处理
### 重试机制
```go
// 带重试的操作
func performWithRetry(operation func() error, maxRetries int) error {
var lastErr error
for i := 0; i < maxRetries; i++ {
err := operation()
if err == nil {
return nil
}
lastErr = err
// 指数退避
waitTime := time.Duration(math.Pow(2, float64(i))) * time.Second
time.Sleep(waitTime)
}
return fmt.Errorf("operation failed after %d retries: %w", maxRetries, lastErr)
}
// 使用示例
err := performWithRetry(func() error {
return driver.TapBySelector("登录")
}, 3)
```
### 异常恢复
```go
// 操作失败时的恢复策略
func performWithRecovery(driver IDriver, operation func() error) error {
err := operation()
if err == nil {
return nil
}
// 尝试恢复策略
log.Warn().Err(err).Msg("operation failed, attempting recovery")
// 策略1: 返回主屏幕
if err := driver.Home(); err != nil {
log.Error().Err(err).Msg("failed to go home")
}
// 策略2: 等待一段时间
time.Sleep(2 * time.Second)
// 策略3: 重新尝试操作
return operation()
}
```
## 性能优化
### 批量操作
```go
// 批量执行操作以提高性能
func performBatchOperations(driver IDriver, operations []func() error) error {
// 如果驱动支持批量模式
if batchDriver, ok := driver.(interface{ BeginBatch(); EndBatch() }); ok {
batchDriver.BeginBatch()
defer batchDriver.EndBatch()
}
for i, operation := range operations {
err := operation()
if err != nil {
return fmt.Errorf("batch operation %d failed: %w", i, err)
}
}
return nil
}
```
### 缓存优化
```go
// 缓存屏幕截图以避免重复获取
type ScreenshotCache struct {
screenshot *bytes.Buffer
timestamp time.Time
ttl time.Duration
}
func (c *ScreenshotCache) GetScreenshot(driver IDriver) (*bytes.Buffer, error) {
if c.screenshot != nil && time.Since(c.timestamp) < c.ttl {
return c.screenshot, nil
}
screenshot, err := driver.ScreenShot()
if err != nil {
return nil, err
}
c.screenshot = screenshot
c.timestamp = time.Now()
return screenshot, nil
}
```
## 最佳实践
### 1. 操作前检查
```go
// 操作前检查设备状态
func checkDeviceReady(driver IDriver) error {
status, err := driver.Status()
if err != nil {
return fmt.Errorf("failed to get device status: %w", err)
}
if status.State != "online" {
return fmt.Errorf("device not ready: %s", status.State)
}
return nil
}
```
### 2. 操作后验证
```go
// 操作后验证结果
func tapAndVerify(driver IDriver, selector string, expectedResult func() bool) error {
err := driver.TapBySelector(selector)
if err != nil {
return err
}
// 等待操作生效
time.Sleep(1 * time.Second)
// 验证结果
if !expectedResult() {
return fmt.Errorf("tap operation did not produce expected result")
}
return nil
}
```
### 3. 资源清理
```go
// 确保资源清理
func performOperationWithCleanup(driver IDriver, operation func() error) error {
// 记录初始状态
initialApp, _ := driver.ForegroundInfo()
defer func() {
// 恢复到初始状态
if initialApp != nil {
driver.AppLaunch(initialApp.PackageName)
}
}()
return operation()
}
```
### 4. 日志记录
```go
// 详细的操作日志
func loggedTap(driver IDriver, x, y float64) error {
log.Info().
Float64("x", x).
Float64("y", y).
Msg("performing tap operation")
start := time.Now()
err := driver.TapXY(x, y)
elapsed := time.Since(start)
if err != nil {
log.Error().
Err(err).
Float64("x", x).
Float64("y", y).
Dur("elapsed", elapsed).
Msg("tap operation failed")
} else {
log.Info().
Float64("x", x).
Float64("y", y).
Dur("elapsed", elapsed).
Msg("tap operation completed")
}
return err
}
```
## 参考资料
- [Android UiAutomator2 文档](https://developer.android.com/training/testing/ui-automator)
- [iOS WebDriverAgent 文档](https://github.com/appium/WebDriverAgent)
- [WebDriver 规范](https://w3c.github.io/webdriver/)
- [Appium 文档](https://appium.io/docs/)