mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-14 06:57:35 +08:00
885 lines
17 KiB
Markdown
885 lines
17 KiB
Markdown
# 操作指南文档
|
||
|
||
## 概述
|
||
|
||
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/) |