mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-11 01:50:31 +08:00
17 KiB
17 KiB
操作指南文档
概述
HttpRunner UIXT 提供了丰富的 UI 操作接口,支持触摸、滑动、输入、应用管理等各种操作。本文档详细介绍每种操作的使用方法和最佳实践。
基础操作
点击操作
相对坐标点击
使用 0-1 范围的相对坐标进行点击,适用于不同屏幕尺寸的设备。
// 点击屏幕中心
err := driver.TapXY(0.5, 0.5)
// 点击右上角
err := driver.TapXY(0.9, 0.1)
// 点击左下角
err := driver.TapXY(0.1, 0.9)
绝对坐标点击
使用像素坐标进行精确点击。
// 点击绝对坐标 (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)
}
选择器点击
通过文本或其他选择器进行点击。
// 通过文本点击
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")
双击操作
// 双击指定坐标
err := driver.DoubleTap(100, 200)
// 双击相对坐标
err := driver.DoubleTap(0.5, 0.5)
长按操作
// 长按指定坐标
err := driver.TouchAndHold(150, 300)
// 带选项的长按
err := driver.TouchAndHold(150, 300,
option.WithDuration(2*time.Second),
)
滑动操作
基础滑动
// 从下往上滑动(向上滚动)
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)
带选项的滑动
// 慢速滑动
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),
)
拖拽操作
// 拖拽元素从一个位置到另一个位置
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),
)
输入操作
文本输入
// 基础文本输入
err := driver.Input("Hello World")
// 输入中文
err := driver.Input("你好世界")
// 输入特殊字符
err := driver.Input("user@example.com")
err := driver.Input("P@ssw0rd123!")
退格操作
// 删除一个字符
err := driver.Backspace(1)
// 删除多个字符
err := driver.Backspace(5)
// 清空输入框(删除大量字符)
err := driver.Backspace(100)
输入法设置
// 设置输入法(Android)
err := driver.SetIme("com.google.android.inputmethod.latin/.LatinIME")
// 设置中文输入法
err := driver.SetIme("com.sohu.inputmethod.sogou/.SogouIME")
按键操作
系统按键
// 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)
特殊按键
// 电源键
err := driver.PressButton(types.DeviceButtonPower)
// 菜单键
err := driver.PressButton(types.DeviceButtonMenu)
// 搜索键
err := driver.PressButton(types.DeviceButtonSearch)
高级操作
智能操作
OCR 识别点击
// 通过 OCR 识别文本并点击
err := xtDriver.TapOCR("登录")
// 使用正则表达式匹配
err := xtDriver.TapOCR(`\d{4}`, option.WithRegex(true))
// 选择特定索引的文本
err := xtDriver.TapOCR("按钮", option.WithIndex(1))
计算机视觉点击
// 通过 CV 识别 UI 元素并点击
err := xtDriver.TapCV("button", "登录按钮")
// 识别图标并点击
err := xtDriver.TapCV("icon", "设置图标")
智能滑动查找
// 滑动查找应用并点击
err := xtDriver.SwipeToTapApp("微信")
// 滑动查找文本并点击
err := xtDriver.SwipeToTapText("设置")
// 滑动查找多个文本中的一个
err := xtDriver.SwipeToTapTexts([]string{"登录", "Sign In", "ログイン"})
组合操作
登录流程
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
}
列表滚动查找
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)
}
表单填写
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
}
应用管理
应用生命周期
启动应用
// 启动应用
err := driver.AppLaunch("com.example.app")
// 启动系统应用
err := driver.AppLaunch("com.android.settings") // Android 设置
err := driver.AppLaunch("com.apple.Preferences") // iOS 设置
终止应用
// 终止应用
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")
}
清理应用数据
// 清理应用数据和缓存(Android)
err := driver.AppClear("com.example.app")
应用信息
获取前台应用
// 获取当前前台应用信息
appInfo, err := driver.ForegroundInfo()
if err != nil {
return err
}
fmt.Printf("Current app: %s (%s)\n", appInfo.Name, appInfo.PackageName)
列出已安装应用
// 列出所有已安装的应用(需要扩展功能)
packages, err := xtDriver.ListPackages()
if err != nil {
return err
}
for _, pkg := range packages {
fmt.Printf("Package: %s\n", pkg)
}
屏幕操作
截图操作
基础截图
// 获取屏幕截图
screenshot, err := driver.ScreenShot()
if err != nil {
return err
}
// 保存截图到文件
err = ioutil.WriteFile("screenshot.png", screenshot.Bytes(), 0644)
带选项的截图
// 高质量截图
screenshot, err := driver.ScreenShot(
option.WithQuality(100),
)
// 指定格式截图
screenshot, err := driver.ScreenShot(
option.WithFormat("jpeg"),
)
屏幕录制
// 开始录制
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)
屏幕信息
获取屏幕尺寸
// 获取屏幕尺寸
size, err := driver.WindowSize()
if err != nil {
return err
}
fmt.Printf("Screen size: %dx%d\n", size.Width, size.Height)
获取屏幕方向
// 获取当前方向
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)
设置屏幕方向
// 设置为横屏
err := driver.SetRotation(types.RotationLandscape)
// 设置为竖屏
err := driver.SetRotation(types.RotationPortrait)
// 设置为倒置横屏
err := driver.SetRotation(types.RotationLandscapeFlipped)
文件操作
文件传输
推送文件到设备
// 推送单个文件
err := driver.PushFile("/local/path/file.txt", "/sdcard/Download/")
// 推送图片
err := driver.PushImage("/local/path/image.jpg")
从设备拉取文件
// 拉取文件到本地
err := driver.PullFiles("/local/download/", "/sdcard/Download/")
// 拉取图片
err := driver.PullImages("/local/images/")
清理文件
// 清理指定路径的文件
err := driver.ClearFiles("/sdcard/Download/temp.txt")
// 清理图片
err := driver.ClearImages()
Web 操作
页面导航
// 导航到URL(仅Web驱动)
if webDriver, ok := driver.(*BrowserDriver); ok {
err := webDriver.NavigateTo("https://example.com")
// 刷新页面
err = webDriver.Refresh()
// 后退
err = webDriver.GoBack()
// 前进
err = webDriver.GoForward()
}
元素操作
悬停操作
// 悬停在元素上(主要用于Web)
err := driver.HoverBySelector("#menu-item")
// 悬停在坐标上
err := driver.HoverXY(0.5, 0.3)
右键点击
// 右键点击坐标
err := driver.SecondaryClick(100, 200)
// 右键点击元素
err := driver.SecondaryClickBySelector("#context-menu-target")
JavaScript 执行
// 执行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)
}
等待和同步
显式等待
// 等待元素出现
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)
}
条件等待
// 等待条件满足
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)
}
智能等待
// 等待页面加载完成
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")
}
错误处理
重试机制
// 带重试的操作
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)
异常恢复
// 操作失败时的恢复策略
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()
}
性能优化
批量操作
// 批量执行操作以提高性能
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
}
缓存优化
// 缓存屏幕截图以避免重复获取
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. 操作前检查
// 操作前检查设备状态
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. 操作后验证
// 操作后验证结果
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. 资源清理
// 确保资源清理
func performOperationWithCleanup(driver IDriver, operation func() error) error {
// 记录初始状态
initialApp, _ := driver.ForegroundInfo()
defer func() {
// 恢复到初始状态
if initialApp != nil {
driver.AppLaunch(initialApp.PackageName)
}
}()
return operation()
}
4. 日志记录
// 详细的操作日志
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
}