mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-06 20:32:44 +08:00
docs: update docs
This commit is contained in:
@@ -256,6 +256,83 @@ if gameInfo, ok := result.Data.(*GameInfo); ok {
|
||||
}
|
||||
```
|
||||
|
||||
#### 高级查询场景
|
||||
|
||||
**UI 元素分析**:
|
||||
```go
|
||||
type UIAnalysis struct {
|
||||
Content string `json:"content"`
|
||||
Thought string `json:"thought"`
|
||||
Elements []UIElement `json:"elements"`
|
||||
}
|
||||
|
||||
type UIElement struct {
|
||||
Type string `json:"type"` // button, text, input等
|
||||
Text string `json:"text"` // 文本内容
|
||||
BoundBox BoundingBox `json:"boundBox"` // 位置坐标
|
||||
Clickable bool `json:"clickable"` // 是否可点击
|
||||
}
|
||||
|
||||
result, err := service.Query(ctx, &ai.QueryOptions{
|
||||
Query: `分析这张截图并提供结构化信息:
|
||||
1. 识别界面类型和主要元素
|
||||
2. 提取所有可交互元素的位置和属性
|
||||
3. 统计各类元素的数量`,
|
||||
Screenshot: screenshot,
|
||||
Size: screenSize,
|
||||
OutputSchema: UIAnalysis{},
|
||||
})
|
||||
```
|
||||
|
||||
**网格游戏分析**:
|
||||
```go
|
||||
type GridGame struct {
|
||||
Content string `json:"content"`
|
||||
Thought string `json:"thought"`
|
||||
Grid [][]Cell `json:"grid"` // 网格数据
|
||||
Stats Statistics `json:"statistics"` // 统计信息
|
||||
}
|
||||
|
||||
type Cell struct {
|
||||
Type string `json:"type"` // 单元格类型
|
||||
Value string `json:"value"` // 单元格值
|
||||
Row int `json:"row"` // 行索引
|
||||
Col int `json:"col"` // 列索引
|
||||
}
|
||||
|
||||
result, err := service.Query(ctx, &ai.QueryOptions{
|
||||
Query: "分析这个网格游戏的布局和状态",
|
||||
Screenshot: screenshot,
|
||||
Size: screenSize,
|
||||
OutputSchema: GridGame{},
|
||||
})
|
||||
```
|
||||
|
||||
**表单数据提取**:
|
||||
```go
|
||||
type FormAnalysis struct {
|
||||
Content string `json:"content"`
|
||||
Thought string `json:"thought"`
|
||||
Fields []FormField `json:"fields"`
|
||||
Actions []Action `json:"actions"`
|
||||
}
|
||||
|
||||
type FormField struct {
|
||||
Label string `json:"label"` // 字段标签
|
||||
Type string `json:"type"` // 字段类型
|
||||
Value string `json:"value"` // 当前值
|
||||
Required bool `json:"required"` // 是否必填
|
||||
BoundBox BoundingBox `json:"boundBox"` // 位置
|
||||
}
|
||||
|
||||
result, err := service.Query(ctx, &ai.QueryOptions{
|
||||
Query: "提取表单中的所有字段信息和操作按钮",
|
||||
Screenshot: screenshot,
|
||||
Size: screenSize,
|
||||
OutputSchema: FormAnalysis{},
|
||||
})
|
||||
```
|
||||
|
||||
### 4. 计算机视觉 (CV)
|
||||
|
||||
提供 OCR 文本识别、UI 元素检测、弹窗识别等计算机视觉功能。
|
||||
@@ -310,20 +387,37 @@ center := targetText.Center()
|
||||
|
||||
### 4. 自定义输出格式
|
||||
|
||||
查询功能支持用户定义的复杂结构化输出格式:
|
||||
查询功能支持用户定义的复杂结构化输出格式,具有以下核心特性:
|
||||
|
||||
#### 自动类型转换
|
||||
- 指定 `OutputSchema` 时,`QueryResult.Data` 自动转换为指定类型
|
||||
- 支持直接类型断言:`result.Data.(*YourType)`
|
||||
- 无需手动调用转换函数
|
||||
|
||||
#### 多级回退机制
|
||||
1. 优先解析为指定的结构化类型
|
||||
2. 失败时尝试通用JSON解析
|
||||
3. 最终回退到纯文本响应
|
||||
|
||||
#### 向后兼容
|
||||
- 不指定 `OutputSchema` 时行为不变
|
||||
- 现有代码无需修改
|
||||
|
||||
**结构体设计最佳实践**:
|
||||
```go
|
||||
type UIAnalysisResult struct {
|
||||
Content string `json:"content"`
|
||||
Elements []UIElement `json:"elements"`
|
||||
Statistics Statistics `json:"statistics"`
|
||||
// 推荐:包含标准字段
|
||||
type YourSchema struct {
|
||||
Content string `json:"content"` // 必须:人类可读描述
|
||||
Thought string `json:"thought"` // 必须:AI推理过程
|
||||
// 自定义字段...
|
||||
Data CustomData `json:"data"`
|
||||
}
|
||||
|
||||
type UIElement struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
BoundingBox BoundingBox `json:"boundingBox"`
|
||||
Clickable bool `json:"clickable"`
|
||||
// 使用描述性的JSON标签
|
||||
type Element struct {
|
||||
Type string `json:"elementType"` // 清晰的字段名
|
||||
Position Point `json:"gridPosition"` // 描述性标签
|
||||
Visible bool `json:"isVisible"` // 布尔值清晰性
|
||||
}
|
||||
```
|
||||
|
||||
328
docs/uixt/README.md
Normal file
328
docs/uixt/README.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# HttpRunner UIXT 模块
|
||||
|
||||
## 🚀 概述
|
||||
|
||||
HttpRunner UIXT(UI eXtended Testing)是 HttpRunner v4.3.0+ 引入的跨平台 UI 自动化测试模块,提供统一的 API 接口支持多种平台的 UI 自动化测试,并集成了先进的 AI 能力,实现真正的智能化 UI 自动化测试。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **🎯 跨平台支持**: Android、iOS、HarmonyOS、Web 浏览器统一接口
|
||||
- **🤖 AI 智能化**: 集成大语言模型和计算机视觉,支持自然语言驱动的 UI 操作
|
||||
- **🔧 MCP 协议**: 基于 Model Context Protocol 的标准化工具接口
|
||||
- **📱 多设备管理**: 支持真机、模拟器、浏览器的统一管理
|
||||
- **🎨 丰富操作**: 触摸、滑动、输入、应用管理等完整操作集
|
||||
- **📊 智能识别**: OCR 文本识别、UI 元素检测、弹窗识别
|
||||
|
||||
## 🏗️ 核心架构
|
||||
|
||||
### 整体架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ HttpRunner UIXT │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ XTDriver (扩展驱动) │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ IDriver │ │ AI Services │ │ MCP Server │ │
|
||||
│ │ (核心驱动) │ │ (AI 能力) │ │ (工具协议) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 设备驱动层 │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ Android Driver │ │ iOS Driver │ │ Browser Driver │ │
|
||||
│ │ (ADB/UIA2) │ │ (WDA) │ │ (WebDriver) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 设备层 │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ Android Device │ │ iOS Device │ │ Browser Device │ │
|
||||
│ │ (真机/模拟器) │ │ (真机/模拟器) │ │ (浏览器) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 核心设计思路
|
||||
|
||||
#### 1. 分层架构设计
|
||||
- **设备层**: 抽象不同平台的设备管理
|
||||
- **驱动层**: 统一不同平台的操作接口
|
||||
- **扩展层**: 提供 AI 和高级功能
|
||||
- **协议层**: 标准化的工具调用接口
|
||||
|
||||
#### 2. 接口统一化
|
||||
所有平台都实现相同的 `IDriver` 接口,确保操作的一致性:
|
||||
|
||||
```go
|
||||
type IDriver interface {
|
||||
// 设备信息和状态
|
||||
Status() (types.DeviceStatus, error)
|
||||
DeviceInfo() (types.DeviceInfo, error)
|
||||
WindowSize() (types.Size, error)
|
||||
ScreenShot(opts ...option.ActionOption) (*bytes.Buffer, error)
|
||||
|
||||
// 基础操作
|
||||
TapXY(x, y float64, opts ...option.ActionOption) error
|
||||
Swipe(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error
|
||||
Input(text string, opts ...option.ActionOption) error
|
||||
|
||||
// 应用管理
|
||||
AppLaunch(packageName string) error
|
||||
AppTerminate(packageName string) (bool, error)
|
||||
|
||||
// ... 更多操作
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. AI 能力集成
|
||||
通过 `XTDriver` 扩展驱动集成 AI 服务:
|
||||
|
||||
```go
|
||||
type XTDriver struct {
|
||||
IDriver // 基础驱动能力
|
||||
CVService ai.ICVService // 计算机视觉服务
|
||||
LLMService ai.ILLMService // 大语言模型服务
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. MCP 工具化
|
||||
将所有操作封装为 MCP 工具,支持 AI 模型直接调用:
|
||||
|
||||
```go
|
||||
type ActionTool interface {
|
||||
Name() option.ActionName
|
||||
Description() string
|
||||
Options() []mcp.ToolOption
|
||||
Implement() server.ToolHandlerFunc
|
||||
}
|
||||
```
|
||||
|
||||
## 📖 支持平台
|
||||
|
||||
### Android 平台
|
||||
- **驱动方式**: ADB + UiAutomator2
|
||||
- **支持设备**: 真机、模拟器
|
||||
- **最低版本**: Android 5.0+
|
||||
- **特色功能**: 应用管理、文件传输、日志捕获
|
||||
|
||||
### iOS 平台
|
||||
- **驱动方式**: WebDriverAgent (WDA)
|
||||
- **支持设备**: 真机、模拟器
|
||||
- **最低版本**: iOS 10.0+
|
||||
- **特色功能**: 应用管理、图片传输、性能监控
|
||||
|
||||
### HarmonyOS 平台
|
||||
- **驱动方式**: HDC (HarmonyOS Device Connector)
|
||||
- **支持设备**: 真机、模拟器
|
||||
- **最低版本**: HarmonyOS 2.0+
|
||||
- **特色功能**: 原生鸿蒙应用支持
|
||||
|
||||
### Web 浏览器
|
||||
- **驱动方式**: WebDriver 协议
|
||||
- **支持浏览器**: Chrome、Firefox、Safari、Edge
|
||||
- **特色功能**: 多标签页管理、JavaScript 执行
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 环境准备
|
||||
|
||||
#### Android 环境
|
||||
```bash
|
||||
# 安装 Android SDK
|
||||
export ANDROID_HOME=/path/to/android-sdk
|
||||
export PATH=$PATH:$ANDROID_HOME/platform-tools
|
||||
|
||||
# 启用 USB 调试
|
||||
adb devices
|
||||
```
|
||||
|
||||
#### iOS 环境
|
||||
```bash
|
||||
# 安装 Xcode 和 WebDriverAgent
|
||||
# 配置开发者证书
|
||||
# 启动 WDA 服务
|
||||
```
|
||||
|
||||
#### AI 服务配置
|
||||
```bash
|
||||
# 配置大语言模型服务
|
||||
export OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
export OPENAI_API_KEY=your_api_key
|
||||
|
||||
# 配置计算机视觉服务
|
||||
export VEDEM_IMAGE_URL=https://visual.volcengineapi.com
|
||||
export VEDEM_IMAGE_AK=your_access_key
|
||||
export VEDEM_IMAGE_SK=your_secret_key
|
||||
```
|
||||
|
||||
### 2. 基础使用
|
||||
|
||||
#### 创建设备和驱动
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/httprunner/httprunner/v5/uixt"
|
||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建 Android 设备
|
||||
device, err := uixt.NewAndroidDevice(
|
||||
option.WithSerialNumber("your_device_serial"),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 创建基础驱动
|
||||
driver, err := uixt.NewUIA2Driver(device)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 创建扩展驱动(集成 AI 能力)
|
||||
xtDriver, err := uixt.NewXTDriver(driver,
|
||||
option.WithCVService(option.CVServiceTypeVEDEM),
|
||||
option.WithLLMService(option.OPENAI_GPT_4O),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 初始化会话
|
||||
err = xtDriver.Setup()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer xtDriver.TearDown()
|
||||
}
|
||||
```
|
||||
|
||||
#### 基础操作示例
|
||||
```go
|
||||
// 获取屏幕截图
|
||||
screenshot, err := xtDriver.ScreenShot()
|
||||
|
||||
// 点击操作
|
||||
err = xtDriver.TapXY(0.5, 0.5) // 相对坐标 (50%, 50%)
|
||||
|
||||
// 滑动操作
|
||||
err = xtDriver.Swipe(0.5, 0.8, 0.5, 0.2) // 从下往上滑动
|
||||
|
||||
// 输入文本
|
||||
err = xtDriver.Input("Hello World")
|
||||
|
||||
// 启动应用
|
||||
err = xtDriver.AppLaunch("com.example.app")
|
||||
```
|
||||
|
||||
#### AI 智能操作
|
||||
```go
|
||||
import "context"
|
||||
|
||||
// 使用自然语言执行操作
|
||||
result, err := xtDriver.LLMService.Plan(context.Background(), &ai.PlanningOptions{
|
||||
UserInstruction: "点击登录按钮",
|
||||
Message: message,
|
||||
Size: screenSize,
|
||||
})
|
||||
|
||||
// 智能断言
|
||||
assertResult, err := xtDriver.LLMService.Assert(context.Background(), &ai.AssertOptions{
|
||||
Assertion: "登录按钮应该可见",
|
||||
Screenshot: screenshot,
|
||||
Size: screenSize,
|
||||
})
|
||||
|
||||
// 智能查询
|
||||
queryResult, err := xtDriver.LLMService.Query(context.Background(), &ai.QueryOptions{
|
||||
Query: "提取页面中的所有文本内容",
|
||||
Screenshot: screenshot,
|
||||
Size: screenSize,
|
||||
})
|
||||
```
|
||||
|
||||
### 3. 高级配置
|
||||
|
||||
#### 混合模型配置
|
||||
```go
|
||||
// 为不同组件配置不同的最优模型
|
||||
config := option.NewLLMServiceConfig(option.DOUBAO_1_5_THINKING_VISION_PRO_250428).
|
||||
WithPlannerModel(option.DOUBAO_1_5_UI_TARS_250328). // UI理解用UI-TARS
|
||||
WithAsserterModel(option.OPENAI_GPT_4O). // 推理用GPT-4O
|
||||
WithQuerierModel(option.DEEPSEEK_R1_250528) // 查询用DeepSeek
|
||||
|
||||
xtDriver, err := uixt.NewXTDriver(driver,
|
||||
option.WithLLMConfig(config),
|
||||
)
|
||||
```
|
||||
|
||||
#### 使用推荐配置
|
||||
```go
|
||||
configs := option.RecommendedConfigurations()
|
||||
xtDriver, err := uixt.NewXTDriver(driver,
|
||||
option.WithLLMConfig(configs["mixed_optimal"]),
|
||||
)
|
||||
```
|
||||
|
||||
## 📚 详细文档
|
||||
|
||||
### 核心文档
|
||||
|
||||
- **[设备管理](devices.md)** - 设备发现、连接、配置和管理
|
||||
- **[驱动接口](drivers.md)** - 各平台驱动的功能和使用方法
|
||||
- **[操作指南](operations.md)** - 详细的 UI 操作使用指南
|
||||
- **[配置选项](options.md)** - 完整的配置参数说明
|
||||
|
||||
### AI 和工具
|
||||
|
||||
- **[AI 模块](ai.md)** - LLM 和 CV 服务的集成使用、智能规划、断言、查询
|
||||
- **[MCP 工具](mcp-tools.md)** - MCP 协议和工具系统详解
|
||||
|
||||
### 快速导航
|
||||
|
||||
| 文档 | 内容概述 |
|
||||
|------|----------|
|
||||
| [设备管理](devices.md) | 设备发现、连接、多设备管理、故障排除、平台特有功能 |
|
||||
| [驱动接口](drivers.md) | IDriver 接口、平台驱动、XTDriver 扩展、选择器类型 |
|
||||
| [操作指南](operations.md) | 点击、滑动、输入、应用管理、屏幕操作 |
|
||||
| [AI 模块](ai.md) | 智能规划、智能断言、智能查询、CV 识别、多模型配置 |
|
||||
| [MCP 工具](mcp-tools.md) | 工具分类、实现方式、扩展开发 |
|
||||
| [配置选项](options.md) | 设备配置、AI 配置、环境变量、最佳实践 |
|
||||
|
||||
## 🔧 依赖项目
|
||||
|
||||
### 核心依赖
|
||||
- [electricbubble/gwda](https://github.com/electricbubble/gwda) - iOS WebDriverAgent 客户端
|
||||
- [electricbubble/guia2](https://github.com/electricbubble/guia2) - Android UiAutomator2 客户端
|
||||
- [mark3labs/mcp-go](https://github.com/mark3labs/mcp-go) - MCP 协议 Go 实现
|
||||
|
||||
### AI 服务依赖
|
||||
- [cloudwego/eino](https://github.com/cloudwego/eino) - 统一的 LLM 接口
|
||||
- 火山引擎 VEDEM - 计算机视觉服务
|
||||
- OpenAI GPT-4O - 大语言模型服务
|
||||
- 豆包系列模型 - 专业 UI 自动化模型
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
我们欢迎社区贡献!请查看以下资源:
|
||||
|
||||
- [贡献指南](CONTRIBUTING.md) - 如何参与项目贡献
|
||||
- [开发环境搭建](development.md) - 开发环境配置
|
||||
- [代码规范](coding-standards.md) - 代码风格和规范
|
||||
- [测试指南](testing.md) - 测试编写和执行
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目采用 Apache 2.0 许可证,详情请查看 [LICENSE](LICENSE) 文件。
|
||||
|
||||
## 🙏 致谢
|
||||
|
||||
感谢以下开源项目的贡献:
|
||||
- [appium-uiautomator2-server](https://github.com/appium/appium-uiautomator2-server) - Android 自动化基础
|
||||
- [appium/WebDriverAgent](https://github.com/appium/WebDriverAgent) - iOS 自动化基础
|
||||
- [danielpaulus/go-ios](https://github.com/danielpaulus/go-ios) - iOS 客户端库
|
||||
|
||||
---
|
||||
|
||||
**HttpRunner UIXT** - 让 UI 自动化测试更智能、更简单!
|
||||
1047
docs/uixt/devices.md
Normal file
1047
docs/uixt/devices.md
Normal file
File diff suppressed because it is too large
Load Diff
934
docs/uixt/drivers.md
Normal file
934
docs/uixt/drivers.md
Normal file
@@ -0,0 +1,934 @@
|
||||
# 驱动接口文档
|
||||
|
||||
## 概述
|
||||
|
||||
HttpRunner UIXT 提供统一的驱动接口 `IDriver`,支持多种平台的 UI 自动化操作。每个平台都有专门的驱动实现,但对外提供相同的接口,确保跨平台的一致性。
|
||||
|
||||
## IDriver 核心接口
|
||||
|
||||
### 接口定义
|
||||
|
||||
```go
|
||||
type IDriver interface {
|
||||
// 设备管理
|
||||
GetDevice() IDevice
|
||||
Setup() error
|
||||
TearDown() error
|
||||
|
||||
// 会话管理
|
||||
InitSession(capabilities option.Capabilities) error
|
||||
GetSession() *DriverSession
|
||||
DeleteSession() error
|
||||
|
||||
// 设备信息和状态
|
||||
Status() (types.DeviceStatus, error)
|
||||
DeviceInfo() (types.DeviceInfo, error)
|
||||
BatteryInfo() (types.BatteryInfo, error)
|
||||
ForegroundInfo() (app types.AppInfo, err error)
|
||||
WindowSize() (types.Size, error)
|
||||
ScreenShot(opts ...option.ActionOption) (*bytes.Buffer, error)
|
||||
ScreenRecord(opts ...option.ActionOption) (videoPath string, err error)
|
||||
Source(srcOpt ...option.SourceOption) (string, error)
|
||||
Orientation() (orientation types.Orientation, err error)
|
||||
Rotation() (rotation types.Rotation, err error)
|
||||
|
||||
// 配置
|
||||
SetRotation(rotation types.Rotation) error
|
||||
SetIme(ime string) error
|
||||
|
||||
// 基础操作
|
||||
Home() error
|
||||
Unlock() error
|
||||
Back() error
|
||||
PressButton(button types.DeviceButton) error
|
||||
|
||||
// 悬停操作
|
||||
HoverBySelector(selector string, opts ...option.ActionOption) error
|
||||
|
||||
// 点击操作
|
||||
TapXY(x, y float64, opts ...option.ActionOption) error
|
||||
TapAbsXY(x, y float64, opts ...option.ActionOption) error
|
||||
TapBySelector(text string, opts ...option.ActionOption) error
|
||||
DoubleTap(x, y float64, opts ...option.ActionOption) error
|
||||
TouchAndHold(x, y float64, opts ...option.ActionOption) error
|
||||
|
||||
// 右键操作
|
||||
SecondaryClick(x, y float64) error
|
||||
SecondaryClickBySelector(selector string, options ...option.ActionOption) error
|
||||
|
||||
// 滑动操作
|
||||
Drag(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error
|
||||
Swipe(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error
|
||||
|
||||
// 输入操作
|
||||
Input(text string, opts ...option.ActionOption) error
|
||||
Backspace(count int, opts ...option.ActionOption) error
|
||||
|
||||
// 应用管理
|
||||
AppLaunch(packageName string) error
|
||||
AppTerminate(packageName string) (bool, error)
|
||||
AppClear(packageName string) error
|
||||
|
||||
// 文件管理
|
||||
PushImage(localPath string) error
|
||||
PullImages(localDir string) error
|
||||
ClearImages() error
|
||||
PushFile(localPath string, remoteDir string) error
|
||||
PullFiles(localDir string, remoteDirs ...string) error
|
||||
ClearFiles(paths ...string) error
|
||||
|
||||
// 日志管理
|
||||
StartCaptureLog(identifier ...string) error
|
||||
StopCaptureLog() (result interface{}, err error)
|
||||
}
|
||||
```
|
||||
|
||||
## Android 驱动
|
||||
|
||||
### ADBDriver
|
||||
|
||||
基于 ADB (Android Debug Bridge) 的基础驱动,提供设备管理和基础操作。
|
||||
|
||||
```go
|
||||
// 创建 ADB 驱动
|
||||
device, err := uixt.NewAndroidDevice(option.WithSerialNumber("device_serial"))
|
||||
driver, err := uixt.NewADBDriver(device)
|
||||
```
|
||||
|
||||
#### 特色功能
|
||||
|
||||
- **应用管理**: 安装、卸载、启动、终止应用
|
||||
- **文件传输**: 推送和拉取文件
|
||||
- **Shell 命令**: 执行 Android shell 命令
|
||||
- **日志捕获**: 实时捕获系统日志
|
||||
- **屏幕录制**: 录制屏幕视频
|
||||
- **系统设置**: 网络、权限、系统配置
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```go
|
||||
// 应用管理
|
||||
err = driver.InstallApp("/path/to/app.apk")
|
||||
err = driver.UninstallApp("com.example.app")
|
||||
err = driver.AppLaunch("com.example.app")
|
||||
terminated, err := driver.AppTerminate("com.example.app")
|
||||
err = driver.AppClear("com.example.app")
|
||||
|
||||
// 文件操作
|
||||
err = driver.PushFile("/local/path/file.txt", "/sdcard/")
|
||||
err = driver.PullFiles("/local/dir", "/sdcard/Download")
|
||||
|
||||
// Shell 命令
|
||||
output, err := driver.Shell("pm list packages")
|
||||
output, err := driver.Shell("dumpsys battery")
|
||||
|
||||
// 日志捕获
|
||||
err = driver.StartCaptureLog("main", "system")
|
||||
logs, err := driver.StopCaptureLog()
|
||||
|
||||
// 权限管理
|
||||
err = driver.GrantPermission("com.example.app", "android.permission.CAMERA")
|
||||
err = driver.RevokePermission("com.example.app", "android.permission.CAMERA")
|
||||
|
||||
// 系统设置
|
||||
err = driver.EnableWiFi()
|
||||
err = driver.ConnectWiFi("SSID", "password")
|
||||
err = driver.EnableMobileData()
|
||||
```
|
||||
|
||||
### UIA2Driver
|
||||
|
||||
基于 UiAutomator2 的高级驱动,提供完整的 UI 自动化功能。
|
||||
|
||||
```go
|
||||
// 创建 UIA2 驱动
|
||||
device, err := uixt.NewAndroidDevice(option.WithSerialNumber("device_serial"))
|
||||
driver, err := uixt.NewUIA2Driver(device)
|
||||
```
|
||||
|
||||
#### 特色功能
|
||||
|
||||
- **UI 元素定位**: 支持多种选择器
|
||||
- **手势操作**: 点击、滑动、拖拽等
|
||||
- **输入操作**: 文本输入、按键操作
|
||||
- **屏幕操作**: 截图、录制、旋转
|
||||
- **页面源码**: 获取 UI 层次结构
|
||||
- **等待机制**: 元素等待和条件等待
|
||||
|
||||
#### 选择器类型
|
||||
|
||||
```go
|
||||
// 文本选择器
|
||||
err = driver.TapBySelector("text=登录")
|
||||
err = driver.TapBySelector("textContains=登")
|
||||
err = driver.TapBySelector("textMatches=登.*")
|
||||
|
||||
// 资源ID选择器
|
||||
err = driver.TapBySelector("resource-id=com.example:id/login_button")
|
||||
err = driver.TapBySelector("resourceId=login_button")
|
||||
|
||||
// 类名选择器
|
||||
err = driver.TapBySelector("className=android.widget.Button")
|
||||
|
||||
// 描述选择器
|
||||
err = driver.TapBySelector("description=登录按钮")
|
||||
err = driver.TapBySelector("contentDescription=登录按钮")
|
||||
|
||||
// 组合选择器
|
||||
err = driver.TapBySelector("className=android.widget.Button,text=登录")
|
||||
|
||||
// XPath 选择器
|
||||
err = driver.TapBySelector("xpath=//android.widget.Button[@text='登录']")
|
||||
```
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```go
|
||||
// UI 操作
|
||||
err = driver.TapXY(0.5, 0.5) // 相对坐标点击
|
||||
err = driver.TapAbsXY(500, 800) // 绝对坐标点击
|
||||
err = driver.TapBySelector("text=登录") // 通过文本点击
|
||||
err = driver.DoubleTap(0.5, 0.5) // 双击
|
||||
err = driver.TouchAndHold(0.5, 0.5) // 长按
|
||||
|
||||
// 滑动操作
|
||||
err = driver.Swipe(0.5, 0.8, 0.5, 0.2) // 滑动
|
||||
err = driver.Drag(0.2, 0.5, 0.8, 0.5) // 拖拽
|
||||
|
||||
// 输入操作
|
||||
err = driver.Input("Hello World")
|
||||
err = driver.Backspace(5)
|
||||
err = driver.PressButton(types.DeviceButtonBack)
|
||||
|
||||
// 屏幕操作
|
||||
screenshot, err := driver.ScreenShot()
|
||||
videoPath, err := driver.ScreenRecord()
|
||||
source, err := driver.Source()
|
||||
|
||||
// 等待操作
|
||||
err = driver.WaitForElement("text=登录", 10*time.Second)
|
||||
err = driver.WaitForElementGone("text=加载中", 30*time.Second)
|
||||
```
|
||||
|
||||
## iOS 驱动
|
||||
|
||||
### WDADriver
|
||||
|
||||
基于 WebDriverAgent 的 iOS 驱动,提供完整的 iOS UI 自动化功能。
|
||||
|
||||
```go
|
||||
// 创建 WDA 驱动
|
||||
device, err := uixt.NewIOSDevice(option.WithUDID("device_udid"))
|
||||
driver, err := uixt.NewWDADriver(device)
|
||||
```
|
||||
|
||||
#### 特色功能
|
||||
|
||||
- **原生 iOS 支持**: 支持 iOS 原生应用和系统应用
|
||||
- **多点触控**: 支持复杂手势和多指操作
|
||||
- **应用管理**: 启动、终止、安装、卸载应用
|
||||
- **性能监控**: 获取应用性能数据和系统信息
|
||||
- **弹窗处理**: 自动处理系统弹窗和权限请求
|
||||
- **屏幕录制**: 支持高质量屏幕录制
|
||||
|
||||
#### 选择器类型
|
||||
|
||||
```go
|
||||
// 文本选择器
|
||||
err = driver.TapBySelector("label=登录")
|
||||
err = driver.TapBySelector("name=登录按钮")
|
||||
|
||||
// 类型选择器
|
||||
err = driver.TapBySelector("type=XCUIElementTypeButton")
|
||||
err = driver.TapBySelector("className=XCUIElementTypeButton")
|
||||
|
||||
// 可访问性标识符
|
||||
err = driver.TapBySelector("id=login_button")
|
||||
err = driver.TapBySelector("accessibilityId=login_button")
|
||||
|
||||
// 值选择器
|
||||
err = driver.TapBySelector("value=用户名")
|
||||
|
||||
// 组合选择器
|
||||
err = driver.TapBySelector("type=XCUIElementTypeButton,label=登录")
|
||||
|
||||
// XPath 选择器
|
||||
err = driver.TapBySelector("xpath=//XCUIElementTypeButton[@label='登录']")
|
||||
|
||||
// 谓词选择器
|
||||
err = driver.TapBySelector("predicate=label CONTAINS '登录'")
|
||||
err = driver.TapBySelector("predicate=type == 'XCUIElementTypeButton' AND visible == 1")
|
||||
```
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```go
|
||||
// 应用管理
|
||||
err = driver.AppLaunch("com.apple.mobilesafari")
|
||||
err = driver.AppLaunch("com.example.app")
|
||||
terminated, err := driver.AppTerminate("com.example.app")
|
||||
err = driver.AppActivate("com.example.app") // 激活后台应用
|
||||
|
||||
// 手势操作
|
||||
err = driver.TapXY(0.5, 0.5) // 点击
|
||||
err = driver.DoubleTap(100, 200) // 双击
|
||||
err = driver.TouchAndHold(150, 300) // 长按
|
||||
err = driver.Swipe(0.5, 0.8, 0.5, 0.2) // 滑动
|
||||
err = driver.Drag(0.2, 0.5, 0.8, 0.5) // 拖拽
|
||||
|
||||
// 输入操作
|
||||
err = driver.Input("Hello World")
|
||||
err = driver.Backspace(5)
|
||||
err = driver.ClearText()
|
||||
|
||||
// 设备操作
|
||||
err = driver.Home() // 回到主屏
|
||||
err = driver.Back() // 返回(如果支持)
|
||||
err = driver.SetRotation(types.RotationLandscape)
|
||||
|
||||
// 屏幕操作
|
||||
screenshot, err := driver.ScreenShot()
|
||||
err = driver.StartScreenRecord()
|
||||
videoPath, err := driver.StopScreenRecord()
|
||||
source, err := driver.Source()
|
||||
|
||||
// 等待操作
|
||||
err = driver.WaitForElement("label=登录", 10*time.Second)
|
||||
err = driver.WaitForElementGone("label=加载中", 30*time.Second)
|
||||
```
|
||||
|
||||
#### iOS 特有功能
|
||||
|
||||
```go
|
||||
// Siri 操作
|
||||
err = driver.ActivateSiri("打开设置")
|
||||
err = driver.ActivateSiri("发送消息给张三")
|
||||
|
||||
// 3D Touch / Force Touch
|
||||
err = driver.ForceTouch(100, 200, 0.8) // 压力值 0.0-1.0
|
||||
err = driver.ForceTouchBySelector("label=应用图标", 0.8)
|
||||
|
||||
// 设备控制
|
||||
err = driver.Lock() // 锁定设备
|
||||
err = driver.Unlock() // 解锁设备
|
||||
err = driver.Shake() // 摇晃设备
|
||||
|
||||
// 音量控制
|
||||
err = driver.VolumeUp() // 音量增加
|
||||
err = driver.VolumeDown() // 音量减少
|
||||
err = driver.SetVolume(0.5) // 设置音量 (0.0-1.0)
|
||||
|
||||
// 弹窗处理
|
||||
err = driver.AcceptAlert() // 接受弹窗
|
||||
err = driver.DismissAlert() // 关闭弹窗
|
||||
alertText, err := driver.GetAlertText() // 获取弹窗文本
|
||||
|
||||
// 键盘操作
|
||||
err = driver.HideKeyboard() // 隐藏键盘
|
||||
isVisible, err := driver.IsKeyboardShown() // 检查键盘是否显示
|
||||
|
||||
// 应用状态
|
||||
state, err := driver.GetAppState("com.example.app")
|
||||
// 0: not installed, 1: not running, 2: running in background, 4: running in foreground
|
||||
|
||||
// 设备信息
|
||||
battery, err := driver.BatteryInfo()
|
||||
orientation, err := driver.Orientation()
|
||||
size, err := driver.WindowSize()
|
||||
```
|
||||
|
||||
## HarmonyOS 驱动
|
||||
|
||||
### HDCDriver
|
||||
|
||||
基于 HDC (HarmonyOS Device Connector) 的鸿蒙驱动,提供完整的 HarmonyOS UI 自动化功能。
|
||||
|
||||
```go
|
||||
// 创建 HDC 驱动
|
||||
device, err := uixt.NewHarmonyDevice(option.WithConnectKey("device_key"))
|
||||
driver, err := uixt.NewHDCDriver(device)
|
||||
```
|
||||
|
||||
#### 特色功能
|
||||
|
||||
- **原生鸿蒙支持**: 支持 HarmonyOS 应用和系统应用
|
||||
- **分布式操作**: 支持多设备协同和跨设备操作
|
||||
- **原子化服务**: 支持轻量级应用和服务
|
||||
- **ArkUI 支持**: 支持 ArkUI 框架的组件识别
|
||||
- **多模态交互**: 支持语音、手势等多种交互方式
|
||||
|
||||
#### 选择器类型
|
||||
|
||||
```go
|
||||
// 文本选择器
|
||||
err = driver.TapBySelector("text=登录")
|
||||
err = driver.TapBySelector("textContains=登")
|
||||
|
||||
// 组件类型选择器
|
||||
err = driver.TapBySelector("type=Button")
|
||||
err = driver.TapBySelector("className=ohos.agp.components.Button")
|
||||
|
||||
// ID 选择器
|
||||
err = driver.TapBySelector("id=login_button")
|
||||
err = driver.TapBySelector("resourceId=login_button")
|
||||
|
||||
// 描述选择器
|
||||
err = driver.TapBySelector("description=登录按钮")
|
||||
err = driver.TapBySelector("contentDescription=登录按钮")
|
||||
|
||||
// 组合选择器
|
||||
err = driver.TapBySelector("type=Button,text=登录")
|
||||
|
||||
// XPath 选择器
|
||||
err = driver.TapBySelector("xpath=//Button[@text='登录']")
|
||||
```
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```go
|
||||
// 基础操作
|
||||
err = driver.TapXY(0.5, 0.5) // 点击
|
||||
err = driver.DoubleTap(0.5, 0.5) // 双击
|
||||
err = driver.TouchAndHold(0.5, 0.5) // 长按
|
||||
err = driver.Swipe(0.2, 0.8, 0.8, 0.2) // 滑动
|
||||
err = driver.Drag(0.2, 0.5, 0.8, 0.5) // 拖拽
|
||||
|
||||
// 输入操作
|
||||
err = driver.Input("测试文本")
|
||||
err = driver.Backspace(5)
|
||||
err = driver.PressButton(types.DeviceButtonBack)
|
||||
|
||||
// 应用管理
|
||||
err = driver.AppLaunch("com.huawei.hmos.example")
|
||||
err = driver.AppLaunch("com.example.harmony.app")
|
||||
terminated, err := driver.AppTerminate("com.example.app")
|
||||
err = driver.AppClear("com.example.app")
|
||||
|
||||
// 屏幕操作
|
||||
screenshot, err := driver.ScreenShot()
|
||||
videoPath, err := driver.ScreenRecord()
|
||||
source, err := driver.Source()
|
||||
|
||||
// 等待操作
|
||||
err = driver.WaitForElement("text=登录", 10*time.Second)
|
||||
err = driver.WaitForElementGone("text=加载中", 30*time.Second)
|
||||
```
|
||||
|
||||
#### HarmonyOS 特有功能
|
||||
|
||||
```go
|
||||
// 分布式操作
|
||||
err = driver.ConnectDistributedDevice("target_device_id")
|
||||
err = driver.DisconnectDistributedDevice("target_device_id")
|
||||
|
||||
// 跨设备应用迁移
|
||||
err = driver.MigrateApp("com.example.app", "target_device_id")
|
||||
|
||||
// 原子化服务
|
||||
err = driver.LaunchAtomicService("service_id", map[string]interface{}{
|
||||
"param1": "value1",
|
||||
"param2": "value2",
|
||||
})
|
||||
err = driver.StopAtomicService("service_id")
|
||||
|
||||
// 多模态交互
|
||||
err = driver.VoiceCommand("打开设置")
|
||||
err = driver.GestureCommand("swipe_up")
|
||||
|
||||
// 系统设置
|
||||
err = driver.EnableDistributedCapability()
|
||||
err = driver.DisableDistributedCapability()
|
||||
|
||||
// 性能监控
|
||||
performance, err := driver.GetPerformanceData()
|
||||
memory, err := driver.GetMemoryInfo()
|
||||
cpu, err := driver.GetCPUInfo()
|
||||
|
||||
// 设备信息
|
||||
info, err := driver.DeviceInfo()
|
||||
battery, err := driver.BatteryInfo()
|
||||
```
|
||||
|
||||
## Web 驱动
|
||||
|
||||
### BrowserDriver
|
||||
|
||||
基于 WebDriver 协议的浏览器驱动,支持多种浏览器的 Web 自动化测试。
|
||||
|
||||
```go
|
||||
// 创建浏览器驱动
|
||||
device, err := uixt.NewBrowserDevice(option.WithBrowserID("chrome"))
|
||||
driver, err := uixt.NewBrowserDriver(device)
|
||||
```
|
||||
|
||||
#### 特色功能
|
||||
|
||||
- **多浏览器支持**: Chrome、Firefox、Safari、Edge
|
||||
- **JavaScript 执行**: 执行自定义脚本和异步脚本
|
||||
- **多标签页管理**: 创建、切换、关闭标签页
|
||||
- **Cookie 管理**: 获取、设置、删除 Cookie
|
||||
- **文件上传下载**: 支持文件操作
|
||||
- **网络监控**: 监控网络请求和响应
|
||||
- **移动端模拟**: 模拟移动设备和触摸操作
|
||||
|
||||
#### 选择器类型
|
||||
|
||||
```go
|
||||
// CSS 选择器
|
||||
err = driver.TapBySelector("#login-button")
|
||||
err = driver.TapBySelector(".btn-primary")
|
||||
err = driver.TapBySelector("button[type='submit']")
|
||||
|
||||
// XPath 选择器
|
||||
err = driver.TapBySelector("xpath=//button[@id='login']")
|
||||
err = driver.TapBySelector("xpath=//div[contains(@class, 'login')]//button")
|
||||
|
||||
// 文本选择器
|
||||
err = driver.TapBySelector("text=登录")
|
||||
err = driver.TapBySelector("linkText=点击这里")
|
||||
err = driver.TapBySelector("partialLinkText=点击")
|
||||
|
||||
// 标签名选择器
|
||||
err = driver.TapBySelector("tagName=button")
|
||||
err = driver.TapBySelector("tagName=input")
|
||||
|
||||
// 属性选择器
|
||||
err = driver.TapBySelector("name=username")
|
||||
err = driver.TapBySelector("className=btn")
|
||||
```
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```go
|
||||
// 页面导航
|
||||
err = driver.NavigateTo("https://example.com")
|
||||
err = driver.Refresh()
|
||||
err = driver.GoBack()
|
||||
err = driver.GoForward()
|
||||
|
||||
// 元素操作
|
||||
err = driver.TapBySelector("#login-button")
|
||||
err = driver.DoubleTap(100, 200)
|
||||
err = driver.TouchAndHold(150, 300)
|
||||
err = driver.Input("username")
|
||||
err = driver.Backspace(5)
|
||||
|
||||
// 滑动和拖拽
|
||||
err = driver.Swipe(0.5, 0.8, 0.5, 0.2)
|
||||
err = driver.Drag(0.2, 0.5, 0.8, 0.5)
|
||||
|
||||
// 屏幕操作
|
||||
screenshot, err := driver.ScreenShot()
|
||||
err = driver.StartScreenRecord()
|
||||
videoPath, err := driver.StopScreenRecord()
|
||||
|
||||
// JavaScript 执行
|
||||
result, err := driver.ExecuteScript("return document.title;")
|
||||
err = driver.ExecuteAsyncScript("callback(arguments[0]);", "test")
|
||||
|
||||
// 标签页管理
|
||||
err = driver.NewTab()
|
||||
err = driver.CloseTab(1)
|
||||
err = driver.SwitchToTab(0)
|
||||
|
||||
// 等待操作
|
||||
err = driver.WaitForElement("#element", 10*time.Second)
|
||||
err = driver.WaitForElementGone("#loading", 30*time.Second)
|
||||
err = driver.WaitForPageLoad(30*time.Second)
|
||||
```
|
||||
|
||||
#### Web 特有功能
|
||||
|
||||
```go
|
||||
// Cookie 操作
|
||||
cookies, err := driver.GetCookies()
|
||||
err = driver.SetCookie("name", "value", "domain.com")
|
||||
err = driver.DeleteCookie("name")
|
||||
err = driver.DeleteAllCookies()
|
||||
|
||||
// 窗口管理
|
||||
err = driver.SetWindowSize(1920, 1080)
|
||||
size, err := driver.GetWindowSize()
|
||||
err = driver.Maximize()
|
||||
err = driver.Minimize()
|
||||
err = driver.Fullscreen()
|
||||
|
||||
// 页面信息
|
||||
title, err := driver.GetTitle()
|
||||
url, err := driver.GetCurrentURL()
|
||||
source, err := driver.GetPageSource()
|
||||
|
||||
// 框架操作
|
||||
err = driver.SwitchToFrame("frame_name")
|
||||
err = driver.SwitchToFrameByIndex(0)
|
||||
err = driver.SwitchToDefaultContent()
|
||||
|
||||
// 弹窗处理
|
||||
err = driver.AcceptAlert()
|
||||
err = driver.DismissAlert()
|
||||
alertText, err := driver.GetAlertText()
|
||||
err = driver.SendAlertText("input text")
|
||||
|
||||
// 文件操作
|
||||
err = driver.UploadFile("#file-input", "/path/to/file.txt")
|
||||
downloadPath, err := driver.DownloadFile("https://example.com/file.pdf")
|
||||
|
||||
// 网络监控
|
||||
err = driver.StartNetworkMonitoring()
|
||||
requests, err := driver.GetNetworkRequests()
|
||||
err = driver.StopNetworkMonitoring()
|
||||
|
||||
// 移动端模拟
|
||||
err = driver.SetMobileEmulation("iPhone 12")
|
||||
err = driver.SetUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)")
|
||||
|
||||
// 性能监控
|
||||
metrics, err := driver.GetPerformanceMetrics()
|
||||
logs, err := driver.GetBrowserLogs()
|
||||
|
||||
// 截图和录制
|
||||
fullPageScreenshot, err := driver.FullPageScreenShot()
|
||||
elementScreenshot, err := driver.ElementScreenShot("#element")
|
||||
|
||||
// 元素信息
|
||||
isVisible, err := driver.IsElementVisible("#element")
|
||||
isEnabled, err := driver.IsElementEnabled("#button")
|
||||
text, err := driver.GetElementText("#element")
|
||||
value, err := driver.GetElementValue("#input")
|
||||
attribute, err := driver.GetElementAttribute("#element", "class")
|
||||
|
||||
// 表单操作
|
||||
err = driver.SelectOption("#select", "option_value")
|
||||
err = driver.CheckCheckbox("#checkbox")
|
||||
err = driver.UncheckCheckbox("#checkbox")
|
||||
err = driver.SelectRadioButton("#radio")
|
||||
|
||||
// 滚动操作
|
||||
err = driver.ScrollToElement("#element")
|
||||
err = driver.ScrollToTop()
|
||||
err = driver.ScrollToBottom()
|
||||
err = driver.ScrollBy(0, 500)
|
||||
```
|
||||
|
||||
## 扩展驱动 (XTDriver)
|
||||
|
||||
### 概述
|
||||
|
||||
`XTDriver` 是对基础驱动的扩展,集成了 AI 能力和 MCP 工具系统。
|
||||
|
||||
```go
|
||||
// 创建扩展驱动
|
||||
baseDriver, err := uixt.NewUIA2Driver(device)
|
||||
xtDriver, err := uixt.NewXTDriver(baseDriver,
|
||||
option.WithCVService(option.CVServiceTypeVEDEM),
|
||||
option.WithLLMService(option.OPENAI_GPT_4O),
|
||||
)
|
||||
```
|
||||
|
||||
### 核心组件
|
||||
|
||||
```go
|
||||
type XTDriver struct {
|
||||
IDriver // 基础驱动能力
|
||||
CVService ai.ICVService // 计算机视觉服务
|
||||
LLMService ai.ILLMService // 大语言模型服务
|
||||
client *MCPClient4XTDriver // MCP 客户端
|
||||
}
|
||||
```
|
||||
|
||||
### AI 增强功能
|
||||
|
||||
#### 智能操作
|
||||
|
||||
```go
|
||||
// 使用自然语言执行操作
|
||||
result, err := xtDriver.LLMService.Plan(ctx, &ai.PlanningOptions{
|
||||
UserInstruction: "点击登录按钮并输入用户名",
|
||||
Message: message,
|
||||
Size: screenSize,
|
||||
})
|
||||
|
||||
// 执行规划的操作
|
||||
for _, toolCall := range result.ToolCalls {
|
||||
// 自动执行工具调用
|
||||
}
|
||||
```
|
||||
|
||||
#### 智能识别
|
||||
|
||||
```go
|
||||
// OCR 文本识别
|
||||
cvResult, err := xtDriver.CVService.ReadFromBuffer(screenshot)
|
||||
ocrTexts := cvResult.OCRResult.ToOCRTexts()
|
||||
|
||||
// 查找特定文本
|
||||
targetText, err := ocrTexts.FindText("登录")
|
||||
center := targetText.Center()
|
||||
|
||||
// 点击识别的文本
|
||||
err = xtDriver.TapAbsXY(center.X, center.Y)
|
||||
```
|
||||
|
||||
#### 智能断言
|
||||
|
||||
```go
|
||||
// 使用自然语言进行断言
|
||||
assertResult, err := xtDriver.LLMService.Assert(ctx, &ai.AssertOptions{
|
||||
Assertion: "页面应该显示用户已登录",
|
||||
Screenshot: screenshot,
|
||||
Size: screenSize,
|
||||
})
|
||||
|
||||
if assertResult.Pass {
|
||||
fmt.Println("断言通过")
|
||||
} else {
|
||||
fmt.Printf("断言失败: %s\n", assertResult.Thought)
|
||||
}
|
||||
```
|
||||
|
||||
### MCP 工具集成
|
||||
|
||||
```go
|
||||
// 执行 MCP 工具
|
||||
result, err := xtDriver.ExecuteAction(ctx, option.MobileAction{
|
||||
Method: option.ActionTapXY,
|
||||
Params: map[string]interface{}{
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## 驱动选择指南
|
||||
|
||||
### 平台对应关系
|
||||
|
||||
| 平台 | 推荐驱动 | 备选驱动 | 说明 |
|
||||
|------|----------|----------|------|
|
||||
| Android | UIA2Driver | ADBDriver | UIA2 提供完整 UI 功能,ADB 提供基础操作 |
|
||||
| iOS | WDADriver | - | 唯一选择,基于 WebDriverAgent |
|
||||
| HarmonyOS | HDCDriver | - | 原生鸿蒙支持 |
|
||||
| Web | BrowserDriver | - | 支持所有主流浏览器 |
|
||||
|
||||
### 选择建议
|
||||
|
||||
#### 功能需求
|
||||
|
||||
- **基础操作**: ADBDriver (Android)
|
||||
- **完整 UI 自动化**: UIA2Driver (Android), WDADriver (iOS)
|
||||
- **AI 增强**: XTDriver (所有平台)
|
||||
- **Web 自动化**: BrowserDriver
|
||||
|
||||
#### 性能考虑
|
||||
|
||||
- **速度优先**: ADBDriver < UIA2Driver < WDADriver
|
||||
- **稳定性**: WDADriver > UIA2Driver > ADBDriver
|
||||
- **功能完整性**: XTDriver > 平台驱动 > 基础驱动
|
||||
|
||||
## 驱动配置
|
||||
|
||||
### 通用配置
|
||||
|
||||
```go
|
||||
// 超时配置
|
||||
driver.SetTimeout(30 * time.Second)
|
||||
|
||||
// 重试配置
|
||||
driver.SetRetryCount(3)
|
||||
driver.SetRetryInterval(1 * time.Second)
|
||||
|
||||
// 日志配置
|
||||
driver.SetLogLevel(log.DebugLevel)
|
||||
driver.EnableActionLog(true)
|
||||
```
|
||||
|
||||
### 平台特定配置
|
||||
|
||||
#### Android 配置
|
||||
|
||||
```go
|
||||
// UiAutomator2 配置
|
||||
driver.SetUiAutomator2Config(uia2.Config{
|
||||
WaitForIdleTimeout: 10 * time.Second,
|
||||
WaitForSelectorTimeout: 20 * time.Second,
|
||||
ActionAcknowledgmentTimeout: 3 * time.Second,
|
||||
})
|
||||
|
||||
// ADB 配置
|
||||
driver.SetADBConfig(adb.Config{
|
||||
CommandTimeout: 30 * time.Second,
|
||||
ShellTimeout: 60 * time.Second,
|
||||
})
|
||||
```
|
||||
|
||||
#### iOS 配置
|
||||
|
||||
```go
|
||||
// WebDriverAgent 配置
|
||||
driver.SetWDAConfig(wda.Config{
|
||||
ConnectionTimeout: 60 * time.Second,
|
||||
CommandTimeout: 30 * time.Second,
|
||||
SnapshotTimeout: 15 * time.Second,
|
||||
})
|
||||
```
|
||||
|
||||
#### Web 配置
|
||||
|
||||
```go
|
||||
// WebDriver 配置
|
||||
driver.SetWebDriverConfig(webdriver.Config{
|
||||
PageLoadTimeout: 30 * time.Second,
|
||||
ScriptTimeout: 10 * time.Second,
|
||||
ImplicitWaitTimeout: 5 * time.Second,
|
||||
})
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 驱动生命周期管理
|
||||
|
||||
```go
|
||||
func useDriver() error {
|
||||
// 创建驱动
|
||||
driver, err := createDriver()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化
|
||||
err = driver.Setup()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer driver.TearDown() // 确保清理
|
||||
|
||||
// 使用驱动
|
||||
return performOperations(driver)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 错误处理
|
||||
|
||||
```go
|
||||
// 带重试的操作
|
||||
func tapWithRetry(driver IDriver, x, y float64) error {
|
||||
maxRetries := 3
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
err := driver.TapXY(x, y)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否是临时错误
|
||||
if isTemporaryError(err) {
|
||||
time.Sleep(time.Duration(i+1) * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("operation failed after %d retries", maxRetries)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 性能优化
|
||||
|
||||
```go
|
||||
// 批量操作
|
||||
func performBatchOperations(driver IDriver, operations []Operation) error {
|
||||
// 开始批量模式
|
||||
driver.BeginBatch()
|
||||
defer driver.EndBatch()
|
||||
|
||||
for _, op := range operations {
|
||||
err := op.Execute(driver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 跨平台兼容
|
||||
|
||||
```go
|
||||
// 平台适配
|
||||
func performPlatformSpecificOperation(driver IDriver) error {
|
||||
switch d := driver.(type) {
|
||||
case *UIA2Driver:
|
||||
// Android 特定操作
|
||||
return d.AndroidSpecificMethod()
|
||||
case *WDADriver:
|
||||
// iOS 特定操作
|
||||
return d.IOSSpecificMethod()
|
||||
case *BrowserDriver:
|
||||
// Web 特定操作
|
||||
return d.WebSpecificMethod()
|
||||
default:
|
||||
// 通用操作
|
||||
return driver.TapXY(0.5, 0.5)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 驱动初始化失败
|
||||
|
||||
```go
|
||||
// 检查设备连接
|
||||
status, err := driver.Status()
|
||||
if err != nil {
|
||||
log.Error("Device not connected: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查驱动服务
|
||||
if !driver.IsServiceRunning() {
|
||||
err = driver.StartService()
|
||||
if err != nil {
|
||||
log.Error("Failed to start driver service: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 操作超时
|
||||
|
||||
```go
|
||||
// 增加超时时间
|
||||
driver.SetTimeout(60 * time.Second)
|
||||
|
||||
// 等待元素出现
|
||||
err = driver.WaitForElement("selector", 30*time.Second)
|
||||
if err != nil {
|
||||
log.Error("Element not found: %v", err)
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
#### 内存泄漏
|
||||
|
||||
```go
|
||||
// 定期清理资源
|
||||
func periodicCleanup(driver IDriver) {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
driver.ClearCache()
|
||||
runtime.GC()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [UiAutomator2 文档](https://github.com/appium/appium-uiautomator2-driver)
|
||||
- [WebDriverAgent 文档](https://github.com/appium/WebDriverAgent)
|
||||
- [WebDriver 规范](https://w3c.github.io/webdriver/)
|
||||
- [Android ADB 文档](https://developer.android.com/studio/command-line/adb)
|
||||
1049
docs/uixt/mcp-tools.md
Normal file
1049
docs/uixt/mcp-tools.md
Normal file
File diff suppressed because it is too large
Load Diff
885
docs/uixt/operations.md
Normal file
885
docs/uixt/operations.md
Normal file
@@ -0,0 +1,885 @@
|
||||
# 操作指南文档
|
||||
|
||||
## 概述
|
||||
|
||||
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/)
|
||||
699
docs/uixt/options.md
Normal file
699
docs/uixt/options.md
Normal file
@@ -0,0 +1,699 @@
|
||||
# 配置选项文档
|
||||
|
||||
## 概述
|
||||
|
||||
HttpRunner UIXT 提供了丰富的配置选项,支持设备配置、驱动配置、AI 服务配置等多个层面的定制化设置。本文档详细介绍所有可用的配置选项。
|
||||
|
||||
## 设备配置选项
|
||||
|
||||
### Android 设备配置
|
||||
|
||||
#### 基础选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithSerialNumber` | string | 设备序列号 | 必需 | `"emulator-5554"` |
|
||||
| `WithAdbLogOn` | bool | 启用 ADB 日志 | false | `true` |
|
||||
| `WithReset` | bool | 重置设备状态 | false | `true` |
|
||||
|
||||
```go
|
||||
device, err := uixt.NewAndroidDevice(
|
||||
option.WithSerialNumber("emulator-5554"),
|
||||
option.WithAdbLogOn(true),
|
||||
option.WithReset(true),
|
||||
)
|
||||
```
|
||||
|
||||
#### 网络选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithSystemPort` | int | UiAutomator2 系统端口 | 8200 | `8200` |
|
||||
| `WithDevicePort` | int | 设备端口 | 6790 | `6790` |
|
||||
| `WithForwardPort` | int | 端口转发 | 0 | `8080` |
|
||||
| `WithProxy` | string | 代理设置 | "" | `"http://proxy:8080"` |
|
||||
|
||||
```go
|
||||
device, err := uixt.NewAndroidDevice(
|
||||
option.WithSerialNumber("device_serial"),
|
||||
option.WithSystemPort(8200),
|
||||
option.WithDevicePort(6790),
|
||||
option.WithForwardPort(8080),
|
||||
option.WithProxy("http://proxy.example.com:8080"),
|
||||
)
|
||||
```
|
||||
|
||||
#### 应用管理选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithInstallApp` | string | 自动安装应用路径 | "" | `"/path/to/app.apk"` |
|
||||
| `WithGrantPermissions` | bool | 自动授予权限 | false | `true` |
|
||||
| `WithSkipServerInstallation` | bool | 跳过服务器安装 | false | `true` |
|
||||
| `WithUiAutomator2Timeout` | int | UiAutomator2 超时(秒) | 60 | `120` |
|
||||
|
||||
```go
|
||||
device, err := uixt.NewAndroidDevice(
|
||||
option.WithSerialNumber("device_serial"),
|
||||
option.WithInstallApp("/path/to/app.apk"),
|
||||
option.WithGrantPermissions(true),
|
||||
option.WithUiAutomator2Timeout(120),
|
||||
)
|
||||
```
|
||||
|
||||
### iOS 设备配置
|
||||
|
||||
#### 基础选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithUDID` | string | 设备 UDID | 必需 | `"00008030-001234567890123A"` |
|
||||
| `WithWDAPort` | int | WebDriverAgent 端口 | 8700 | `8700` |
|
||||
| `WithWDAMjpegPort` | int | MJPEG 流端口 | 8800 | `8800` |
|
||||
|
||||
```go
|
||||
device, err := uixt.NewIOSDevice(
|
||||
option.WithUDID("00008030-001234567890123A"),
|
||||
option.WithWDAPort(8700),
|
||||
option.WithWDAMjpegPort(8800),
|
||||
)
|
||||
```
|
||||
|
||||
#### WDA 配置选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithResetHomeOnStartup` | bool | 启动时回到主屏 | true | `false` |
|
||||
| `WithPreventWDAAttachments` | bool | 防止 WDA 附件 | false | `true` |
|
||||
| `WithWDAStartupTimeout` | int | WDA 启动超时(秒) | 120 | `180` |
|
||||
| `WithWDAConnectionTimeout` | int | WDA 连接超时(秒) | 60 | `90` |
|
||||
|
||||
```go
|
||||
device, err := uixt.NewIOSDevice(
|
||||
option.WithUDID("device_udid"),
|
||||
option.WithResetHomeOnStartup(false),
|
||||
option.WithPreventWDAAttachments(true),
|
||||
option.WithWDAStartupTimeout(180),
|
||||
option.WithWDAConnectionTimeout(90),
|
||||
)
|
||||
```
|
||||
|
||||
### HarmonyOS 设备配置
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithConnectKey` | string | 设备连接密钥 | 必需 | `"192.168.1.100:5555"` |
|
||||
| `WithHDCLogOn` | bool | 启用 HDC 日志 | false | `true` |
|
||||
| `WithSystemPort` | int | 系统端口 | 9200 | `9200` |
|
||||
|
||||
```go
|
||||
device, err := uixt.NewHarmonyDevice(
|
||||
option.WithConnectKey("192.168.1.100:5555"),
|
||||
option.WithHDCLogOn(true),
|
||||
option.WithSystemPort(9200),
|
||||
)
|
||||
```
|
||||
|
||||
### Web 浏览器配置
|
||||
|
||||
#### 基础选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithBrowserID` | string | 浏览器标识 | 必需 | `"chrome"` |
|
||||
| `WithHeadless` | bool | 无头模式 | true | `false` |
|
||||
| `WithWindowSize` | int, int | 窗口大小 | 1280x720 | `1920, 1080` |
|
||||
|
||||
```go
|
||||
device, err := uixt.NewBrowserDevice(
|
||||
option.WithBrowserID("chrome"),
|
||||
option.WithHeadless(false),
|
||||
option.WithWindowSize(1920, 1080),
|
||||
)
|
||||
```
|
||||
|
||||
#### 高级选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithUserAgent` | string | 自定义 User-Agent | 默认 | `"custom-agent"` |
|
||||
| `WithProxy` | string | 代理地址 | 无 | `"http://proxy:8080"` |
|
||||
| `WithExtensions` | []string | 扩展列表 | 无 | `[]string{"ext1", "ext2"}` |
|
||||
| `WithDownloadDir` | string | 下载目录 | 默认 | `"/path/to/downloads"` |
|
||||
|
||||
```go
|
||||
device, err := uixt.NewBrowserDevice(
|
||||
option.WithBrowserID("chrome"),
|
||||
option.WithUserAgent("custom-agent"),
|
||||
option.WithProxy("http://proxy:8080"),
|
||||
option.WithExtensions([]string{"extension1", "extension2"}),
|
||||
option.WithDownloadDir("/custom/download/path"),
|
||||
)
|
||||
```
|
||||
|
||||
## AI 服务配置
|
||||
|
||||
### LLM 服务配置
|
||||
|
||||
#### 基础配置
|
||||
|
||||
```go
|
||||
// 使用单一模型
|
||||
xtDriver, err := uixt.NewXTDriver(driver,
|
||||
option.WithLLMService(option.OPENAI_GPT_4O),
|
||||
)
|
||||
```
|
||||
|
||||
#### 高级配置
|
||||
|
||||
```go
|
||||
// 混合模型配置
|
||||
config := option.NewLLMServiceConfig(option.DOUBAO_1_5_THINKING_VISION_PRO_250428).
|
||||
WithPlannerModel(option.DOUBAO_1_5_UI_TARS_250328).
|
||||
WithAsserterModel(option.OPENAI_GPT_4O).
|
||||
WithQuerierModel(option.DEEPSEEK_R1_250528)
|
||||
|
||||
xtDriver, err := uixt.NewXTDriver(driver,
|
||||
option.WithLLMConfig(config),
|
||||
)
|
||||
```
|
||||
|
||||
#### 支持的模型
|
||||
|
||||
| 模型名称 | 特点 | 适用场景 |
|
||||
|---------|------|----------|
|
||||
| `DOUBAO_1_5_UI_TARS_250328` | UI 理解专业模型 | UI 元素识别和操作规划 |
|
||||
| `DOUBAO_1_5_THINKING_VISION_PRO_250428` | 思考推理模型 | 复杂逻辑推理和断言 |
|
||||
| `OPENAI_GPT_4O` | 高性能通用模型 | 全场景通用 |
|
||||
| `DEEPSEEK_R1_250528` | 成本效益模型 | 大量查询场景 |
|
||||
|
||||
#### 推荐配置
|
||||
|
||||
```go
|
||||
configs := option.RecommendedConfigurations()
|
||||
|
||||
// 混合优化配置(推荐)
|
||||
config := configs["mixed_optimal"]
|
||||
|
||||
// 高性能配置
|
||||
config := configs["high_performance"]
|
||||
|
||||
// 成本优化配置
|
||||
config := configs["cost_effective"]
|
||||
|
||||
// UI 专注配置
|
||||
config := configs["ui_focused"]
|
||||
|
||||
// 推理专注配置
|
||||
config := configs["reasoning_focused"]
|
||||
```
|
||||
|
||||
### CV 服务配置
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithCVService` | CVServiceType | CV 服务类型 | 无 | `option.CVServiceTypeVEDEM` |
|
||||
|
||||
```go
|
||||
xtDriver, err := uixt.NewXTDriver(driver,
|
||||
option.WithCVService(option.CVServiceTypeVEDEM),
|
||||
)
|
||||
```
|
||||
|
||||
## 操作配置选项
|
||||
|
||||
### 通用操作选项
|
||||
|
||||
#### 时间相关选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithDuration` | time.Duration | 操作持续时间 | 默认 | `2*time.Second` |
|
||||
| `WithTimeout` | time.Duration | 操作超时时间 | 30s | `60*time.Second` |
|
||||
| `WithDelay` | time.Duration | 操作前延迟 | 0 | `500*time.Millisecond` |
|
||||
|
||||
```go
|
||||
// 慢速滑动
|
||||
err := driver.Swipe(0.5, 0.8, 0.5, 0.2,
|
||||
option.WithDuration(2*time.Second),
|
||||
)
|
||||
|
||||
// 长按操作
|
||||
err := driver.TouchAndHold(150, 300,
|
||||
option.WithDuration(3*time.Second),
|
||||
)
|
||||
|
||||
// 带超时的操作
|
||||
err := driver.TapBySelector("登录",
|
||||
option.WithTimeout(10*time.Second),
|
||||
)
|
||||
```
|
||||
|
||||
#### 精度相关选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithSteps` | int | 滑动步数 | 默认 | `20` |
|
||||
| `WithPressure` | float64 | 压力值(iOS) | 1.0 | `0.8` |
|
||||
| `WithFrequency` | int | 操作频率 | 默认 | `60` |
|
||||
|
||||
```go
|
||||
// 多步滑动
|
||||
err := driver.Swipe(0.5, 0.8, 0.5, 0.2,
|
||||
option.WithSteps(50),
|
||||
)
|
||||
|
||||
// 3D Touch (iOS)
|
||||
err := driver.ForceTouch(100, 200,
|
||||
option.WithPressure(0.8),
|
||||
)
|
||||
```
|
||||
|
||||
### 截图选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithQuality` | int | 图片质量 | 80 | `100` |
|
||||
| `WithFormat` | string | 图片格式 | "png" | `"jpeg"` |
|
||||
| `WithScale` | float64 | 缩放比例 | 1.0 | `0.5` |
|
||||
|
||||
```go
|
||||
// 高质量截图
|
||||
screenshot, err := driver.ScreenShot(
|
||||
option.WithQuality(100),
|
||||
option.WithFormat("png"),
|
||||
)
|
||||
|
||||
// 缩放截图
|
||||
screenshot, err := driver.ScreenShot(
|
||||
option.WithScale(0.5),
|
||||
)
|
||||
```
|
||||
|
||||
### 录制选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithBitRate` | int | 比特率 | 4000000 | `8000000` |
|
||||
| `WithVideoSize` | string | 视频尺寸 | 默认 | `"1280x720"` |
|
||||
| `WithTimeLimit` | time.Duration | 录制时长 | 180s | `300*time.Second` |
|
||||
|
||||
```go
|
||||
// 高质量录制
|
||||
videoPath, err := driver.ScreenRecord(
|
||||
option.WithBitRate(8000000),
|
||||
option.WithVideoSize("1920x1080"),
|
||||
option.WithTimeLimit(300*time.Second),
|
||||
)
|
||||
```
|
||||
|
||||
### OCR 选项
|
||||
|
||||
| 选项 | 类型 | 说明 | 默认值 | 示例 |
|
||||
|------|------|------|--------|------|
|
||||
| `WithRegex` | bool | 使用正则表达式 | false | `true` |
|
||||
| `WithIndex` | int | 文本索引 | 0 | `1` |
|
||||
| `WithIgnoreCase` | bool | 忽略大小写 | false | `true` |
|
||||
|
||||
```go
|
||||
// 正则表达式匹配
|
||||
err := xtDriver.TapOCR(`\d{4}`,
|
||||
option.WithRegex(true),
|
||||
)
|
||||
|
||||
// 选择第二个匹配项
|
||||
err := xtDriver.TapOCR("按钮",
|
||||
option.WithIndex(1),
|
||||
)
|
||||
|
||||
// 忽略大小写
|
||||
err := xtDriver.TapOCR("LOGIN",
|
||||
option.WithIgnoreCase(true),
|
||||
)
|
||||
```
|
||||
|
||||
## 环境变量配置
|
||||
|
||||
### LLM 模型配置
|
||||
|
||||
#### 豆包模型
|
||||
|
||||
```bash
|
||||
# 豆包思维视觉专业版
|
||||
DOUBAO_1_5_THINKING_VISION_PRO_250428_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
|
||||
DOUBAO_1_5_THINKING_VISION_PRO_250428_API_KEY=your_doubao_api_key
|
||||
|
||||
# 豆包UI-TARS
|
||||
DOUBAO_1_5_UI_TARS_250328_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
|
||||
DOUBAO_1_5_UI_TARS_250328_API_KEY=your_doubao_ui_tars_api_key
|
||||
```
|
||||
|
||||
#### OpenAI 模型
|
||||
|
||||
```bash
|
||||
# OpenAI GPT-4O
|
||||
OPENAI_GPT_4O_BASE_URL=https://api.openai.com/v1
|
||||
OPENAI_GPT_4O_API_KEY=your_openai_api_key
|
||||
```
|
||||
|
||||
#### DeepSeek 模型
|
||||
|
||||
```bash
|
||||
# DeepSeek
|
||||
DEEPSEEK_R1_250528_BASE_URL=https://api.deepseek.com/v1
|
||||
DEEPSEEK_R1_250528_API_KEY=your_deepseek_api_key
|
||||
```
|
||||
|
||||
#### 默认配置
|
||||
|
||||
```bash
|
||||
# 默认配置,当没有找到服务特定配置时使用
|
||||
LLM_MODEL_NAME=doubao-1.5-thinking-vision-pro-250428
|
||||
OPENAI_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
|
||||
OPENAI_API_KEY=your_default_api_key
|
||||
```
|
||||
|
||||
### CV 服务配置
|
||||
|
||||
#### 火山引擎 VEDEM
|
||||
|
||||
```bash
|
||||
# 火山引擎 VEDEM 配置
|
||||
VEDEM_IMAGE_URL=https://visual.volcengineapi.com
|
||||
VEDEM_IMAGE_AK=your_access_key
|
||||
VEDEM_IMAGE_SK=your_secret_key
|
||||
```
|
||||
|
||||
### 配置优先级
|
||||
|
||||
环境变量的加载优先级(从高到低):
|
||||
|
||||
1. `.env` 文件(当前工作目录)
|
||||
2. `~/.hrp/.env` 文件(全局用户配置)
|
||||
3. 系统环境变量
|
||||
|
||||
```bash
|
||||
# 项目级配置文件 .env
|
||||
OPENAI_API_KEY=project_specific_key
|
||||
|
||||
# 用户级配置文件 ~/.hrp/.env
|
||||
OPENAI_API_KEY=user_default_key
|
||||
|
||||
# 系统环境变量
|
||||
export OPENAI_API_KEY=system_key
|
||||
```
|
||||
|
||||
## 配置文件
|
||||
|
||||
### 项目配置文件
|
||||
|
||||
创建 `.env` 文件在项目根目录:
|
||||
|
||||
```bash
|
||||
# .env
|
||||
# LLM 服务配置
|
||||
OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
OPENAI_API_KEY=your_openai_api_key
|
||||
|
||||
# CV 服务配置
|
||||
VEDEM_IMAGE_URL=https://visual.volcengineapi.com
|
||||
VEDEM_IMAGE_AK=your_access_key
|
||||
VEDEM_IMAGE_SK=your_secret_key
|
||||
|
||||
# 设备配置
|
||||
DEFAULT_ANDROID_SERIAL=emulator-5554
|
||||
DEFAULT_IOS_UDID=00008030-001234567890123A
|
||||
```
|
||||
|
||||
### 用户配置文件
|
||||
|
||||
创建 `~/.hrp/.env` 文件:
|
||||
|
||||
```bash
|
||||
# ~/.hrp/.env
|
||||
# 全局默认配置
|
||||
OPENAI_API_KEY=your_global_api_key
|
||||
VEDEM_IMAGE_AK=your_global_access_key
|
||||
VEDEM_IMAGE_SK=your_global_secret_key
|
||||
```
|
||||
|
||||
### YAML 配置文件
|
||||
|
||||
```yaml
|
||||
# config.yaml
|
||||
devices:
|
||||
android:
|
||||
serial: "emulator-5554"
|
||||
system_port: 8200
|
||||
device_port: 6790
|
||||
adb_log: true
|
||||
|
||||
ios:
|
||||
udid: "00008030-001234567890123A"
|
||||
wda_port: 8700
|
||||
mjpeg_port: 8800
|
||||
reset_home: false
|
||||
|
||||
ai_services:
|
||||
llm:
|
||||
default_model: "doubao-1.5-thinking-vision-pro-250428"
|
||||
planner_model: "doubao-1.5-ui-tars-250328"
|
||||
asserter_model: "openai-gpt-4o"
|
||||
querier_model: "deepseek-r1-250528"
|
||||
|
||||
cv:
|
||||
service_type: "vedem"
|
||||
|
||||
operations:
|
||||
default_timeout: 30
|
||||
screenshot_quality: 80
|
||||
video_bitrate: 4000000
|
||||
```
|
||||
|
||||
## 动态配置
|
||||
|
||||
### 运行时配置
|
||||
|
||||
```go
|
||||
// 运行时修改配置
|
||||
func configureDriver(driver IDriver) error {
|
||||
// 设置超时
|
||||
driver.SetTimeout(60 * time.Second)
|
||||
|
||||
// 设置重试次数
|
||||
driver.SetRetryCount(3)
|
||||
|
||||
// 设置日志级别
|
||||
driver.SetLogLevel(log.DebugLevel)
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 条件配置
|
||||
|
||||
```go
|
||||
// 根据环境选择配置
|
||||
func createDriverWithEnvironmentConfig(platform string) (*uixt.XTDriver, error) {
|
||||
var device uixt.IDevice
|
||||
var err error
|
||||
|
||||
switch platform {
|
||||
case "android":
|
||||
if os.Getenv("CI") == "true" {
|
||||
// CI 环境使用模拟器
|
||||
device, err = uixt.NewAndroidDevice(
|
||||
option.WithSerialNumber("emulator-5554"),
|
||||
option.WithReset(true),
|
||||
)
|
||||
} else {
|
||||
// 本地环境使用真机
|
||||
device, err = uixt.NewAndroidDevice(
|
||||
option.WithSerialNumber(os.Getenv("ANDROID_SERIAL")),
|
||||
option.WithAdbLogOn(true),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driver, err := uixt.NewUIA2Driver(device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 根据环境选择 AI 配置
|
||||
var aiOptions []option.AIServiceOption
|
||||
if os.Getenv("ENABLE_AI") == "true" {
|
||||
configs := option.RecommendedConfigurations()
|
||||
aiOptions = append(aiOptions, option.WithLLMConfig(configs["mixed_optimal"]))
|
||||
aiOptions = append(aiOptions, option.WithCVService(option.CVServiceTypeVEDEM))
|
||||
}
|
||||
|
||||
return uixt.NewXTDriver(driver, aiOptions...)
|
||||
}
|
||||
```
|
||||
|
||||
## 配置验证
|
||||
|
||||
### 配置检查
|
||||
|
||||
```go
|
||||
// 验证配置完整性
|
||||
func validateConfiguration() error {
|
||||
// 检查必需的环境变量
|
||||
requiredEnvs := []string{
|
||||
"OPENAI_API_KEY",
|
||||
"VEDEM_IMAGE_AK",
|
||||
"VEDEM_IMAGE_SK",
|
||||
}
|
||||
|
||||
for _, env := range requiredEnvs {
|
||||
if os.Getenv(env) == "" {
|
||||
return fmt.Errorf("required environment variable %s not set", env)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查设备连接
|
||||
devices, err := uixt.DiscoverAndroidDevices()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to discover Android devices: %w", err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
return fmt.Errorf("no Android devices found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 配置诊断
|
||||
|
||||
```go
|
||||
// 配置诊断工具
|
||||
func diagnoseConfiguration() {
|
||||
fmt.Println("=== Configuration Diagnosis ===")
|
||||
|
||||
// 检查环境变量
|
||||
fmt.Println("\nEnvironment Variables:")
|
||||
envVars := []string{
|
||||
"OPENAI_BASE_URL", "OPENAI_API_KEY",
|
||||
"VEDEM_IMAGE_URL", "VEDEM_IMAGE_AK", "VEDEM_IMAGE_SK",
|
||||
}
|
||||
|
||||
for _, env := range envVars {
|
||||
value := os.Getenv(env)
|
||||
if value != "" {
|
||||
fmt.Printf(" %s: %s\n", env, maskSensitive(value))
|
||||
} else {
|
||||
fmt.Printf(" %s: NOT SET\n", env)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查设备连接
|
||||
fmt.Println("\nDevice Status:")
|
||||
androidDevices, _ := uixt.DiscoverAndroidDevices()
|
||||
fmt.Printf(" Android devices: %d\n", len(androidDevices))
|
||||
|
||||
iosDevices, _ := uixt.DiscoverIOSDevices()
|
||||
fmt.Printf(" iOS devices: %d\n", len(iosDevices))
|
||||
}
|
||||
|
||||
func maskSensitive(value string) string {
|
||||
if len(value) <= 8 {
|
||||
return "***"
|
||||
}
|
||||
return value[:4] + "***" + value[len(value)-4:]
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 配置分层
|
||||
|
||||
```go
|
||||
// 分层配置管理
|
||||
type Config struct {
|
||||
Device DeviceConfig `yaml:"device"`
|
||||
AI AIConfig `yaml:"ai"`
|
||||
Operation OperationConfig `yaml:"operation"`
|
||||
}
|
||||
|
||||
type DeviceConfig struct {
|
||||
Platform string `yaml:"platform"`
|
||||
Serial string `yaml:"serial"`
|
||||
Timeout int `yaml:"timeout"`
|
||||
}
|
||||
|
||||
type AIConfig struct {
|
||||
LLMModel string `yaml:"llm_model"`
|
||||
CVService string `yaml:"cv_service"`
|
||||
}
|
||||
|
||||
type OperationConfig struct {
|
||||
DefaultTimeout int `yaml:"default_timeout"`
|
||||
RetryCount int `yaml:"retry_count"`
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 配置验证
|
||||
|
||||
```go
|
||||
// 配置验证
|
||||
func (c *Config) Validate() error {
|
||||
if c.Device.Platform == "" {
|
||||
return fmt.Errorf("device platform is required")
|
||||
}
|
||||
|
||||
if c.Device.Serial == "" {
|
||||
return fmt.Errorf("device serial is required")
|
||||
}
|
||||
|
||||
if c.Operation.DefaultTimeout <= 0 {
|
||||
c.Operation.DefaultTimeout = 30 // 设置默认值
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 配置热重载
|
||||
|
||||
```go
|
||||
// 配置热重载
|
||||
func watchConfigFile(configPath string, callback func(*Config)) {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
err = watcher.Add(configPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
config, err := loadConfig(configPath)
|
||||
if err == nil {
|
||||
callback(config)
|
||||
}
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
log.Println("error:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [环境变量最佳实践](https://12factor.net/config)
|
||||
- [YAML 配置文件格式](https://yaml.org/)
|
||||
- [Go 配置管理库 Viper](https://github.com/spf13/viper)
|
||||
@@ -1 +1 @@
|
||||
v5.0.0-beta-2506111218
|
||||
v5.0.0-beta-2506111457
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# uixt
|
||||
|
||||
From v4.3.0,HttpRunner will support mobile UI automation testing:
|
||||
|
||||
- iOS: based on [appium/WebDriverAgent], with forked client library [electricbubble/gwda] in golang
|
||||
- Android: based on [appium-uiautomator2-server], with forked client library [electricbubble/guia2] in golang
|
||||
|
||||
Some UI recognition algorithms are also introduced for both iOS and Android:
|
||||
|
||||
- OCR: based on OCR API service from [volcengine], other API service may be extended
|
||||
|
||||
## Dependencies
|
||||
|
||||
### OCR
|
||||
|
||||
OCR API is a paid service, you need to pre-purchase and configure the environment variables.
|
||||
|
||||
- VEDEM_IMAGE_URL
|
||||
- VEDEM_IMAGE_AK
|
||||
- VEDEM_IMAGE_SK
|
||||
|
||||
## Thanks
|
||||
|
||||
This uixt module is initially forked from the following repos and made a lot of changes.
|
||||
|
||||
- [electricbubble/gwda]
|
||||
- [electricbubble/guia2]
|
||||
|
||||
|
||||
[appium/WebDriverAgent]: https://github.com/appium/WebDriverAgent
|
||||
[electricbubble/gwda]: https://github.com/electricbubble/gwda
|
||||
[electricbubble/guia2]: https://github.com/electricbubble/guia2
|
||||
[volcengine]: https://www.volcengine.com/product/text-recognition
|
||||
[appium-uiautomator2-server]: https://github.com/appium/appium-uiautomator2-server
|
||||
@@ -1,299 +0,0 @@
|
||||
# HttpRunner AI Querier - 自定义输出格式功能
|
||||
|
||||
## 功能概述
|
||||
|
||||
HttpRunner 的 AI Querier 模块支持自定义输出格式功能,允许用户指定特定的数据结构,让 AI 模型返回结构化的数据响应。适用于:
|
||||
|
||||
- **UI 元素分析**:自动化测试中的界面元素提取
|
||||
- **游戏界面分析**:网格类游戏(连连看、消消乐、2048等)数据提取
|
||||
- **表单数据提取**:从表单截图中提取结构化信息
|
||||
- **图像内容分析**:任何需要从截图中提取结构化信息的场景
|
||||
|
||||
## 核心数据结构
|
||||
|
||||
```go
|
||||
// QueryOptions - 查询选项
|
||||
type QueryOptions struct {
|
||||
Query string `json:"query"` // 查询文本
|
||||
Screenshot string `json:"screenshot"` // Base64编码的截图
|
||||
Size types.Size `json:"size"` // 屏幕尺寸
|
||||
OutputSchema interface{} `json:"outputSchema,omitempty"` // 自定义输出格式(可选)
|
||||
}
|
||||
|
||||
// QueryResult - 查询结果
|
||||
type QueryResult struct {
|
||||
Content string `json:"content"` // 人类可读的分析结果
|
||||
Thought string `json:"thought"` // AI 推理过程
|
||||
Data interface{} `json:"data,omitempty"` // 结构化数据(使用OutputSchema时自动转换为指定类型)
|
||||
}
|
||||
```
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 标准查询
|
||||
|
||||
```go
|
||||
// 创建查询器
|
||||
modelConfig, err := ai.GetModelConfig(option.OPENAI_GPT_4O)
|
||||
querier, err := ai.NewQuerier(ctx, modelConfig)
|
||||
|
||||
// 执行查询
|
||||
result, err := querier.Query(ctx, &ai.QueryOptions{
|
||||
Query: "请分析这张截图中的内容",
|
||||
Screenshot: screenshot,
|
||||
Size: size,
|
||||
// 不指定 OutputSchema
|
||||
})
|
||||
|
||||
fmt.Printf("分析结果: %s\n", result.Content)
|
||||
fmt.Printf("推理过程: %s\n", result.Thought)
|
||||
// result.Data 为 nil
|
||||
```
|
||||
|
||||
### 自定义格式查询
|
||||
|
||||
```go
|
||||
// 定义输出结构
|
||||
type GameAnalysis struct {
|
||||
Content string `json:"content"` // 分析描述
|
||||
Thought string `json:"thought"` // 思考过程
|
||||
Rows int `json:"rows"` // 行数
|
||||
Cols int `json:"cols"` // 列数
|
||||
Icons []string `json:"icons"` // 图标类型
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
result, err := querier.Query(ctx, &ai.QueryOptions{
|
||||
Query: "分析这个游戏界面的网格结构和图标类型",
|
||||
Screenshot: screenshot,
|
||||
Size: size,
|
||||
OutputSchema: GameAnalysis{}, // 指定输出格式
|
||||
})
|
||||
|
||||
// 直接类型断言获取结构化数据
|
||||
if gameData, ok := result.Data.(*GameAnalysis); ok {
|
||||
fmt.Printf("行数: %d, 列数: %d\n", gameData.Rows, gameData.Cols)
|
||||
fmt.Printf("图标类型: %v\n", gameData.Icons)
|
||||
}
|
||||
```
|
||||
|
||||
## 应用场景示例
|
||||
|
||||
### UI 元素分析
|
||||
|
||||
```go
|
||||
type UIAnalysis struct {
|
||||
Content string `json:"content"`
|
||||
Thought string `json:"thought"`
|
||||
Elements []UIElement `json:"elements"`
|
||||
}
|
||||
|
||||
type UIElement struct {
|
||||
Type string `json:"type"` // button, text, input等
|
||||
Text string `json:"text"` // 文本内容
|
||||
BoundBox BoundingBox `json:"boundBox"` // 位置坐标
|
||||
Clickable bool `json:"clickable"` // 是否可点击
|
||||
}
|
||||
|
||||
type BoundingBox struct {
|
||||
X, Y, Width, Height int `json:"x,y,width,height"`
|
||||
}
|
||||
```
|
||||
|
||||
### 网格游戏分析
|
||||
|
||||
```go
|
||||
type GridGame struct {
|
||||
Content string `json:"content"`
|
||||
Thought string `json:"thought"`
|
||||
Grid [][]Cell `json:"grid"` // 网格数据
|
||||
Stats Statistics `json:"statistics"` // 统计信息
|
||||
}
|
||||
|
||||
type Cell struct {
|
||||
Type string `json:"type"` // 单元格类型
|
||||
Value string `json:"value"` // 单元格值
|
||||
Row int `json:"row"` // 行索引
|
||||
Col int `json:"col"` // 列索引
|
||||
}
|
||||
|
||||
type Statistics struct {
|
||||
TotalCells int `json:"totalCells"`
|
||||
UniqueTypes int `json:"uniqueTypes"`
|
||||
}
|
||||
```
|
||||
|
||||
### 表单数据提取
|
||||
|
||||
```go
|
||||
type FormAnalysis struct {
|
||||
Content string `json:"content"`
|
||||
Thought string `json:"thought"`
|
||||
Fields []FormField `json:"fields"`
|
||||
Actions []Action `json:"actions"`
|
||||
}
|
||||
|
||||
type FormField struct {
|
||||
Label string `json:"label"` // 字段标签
|
||||
Type string `json:"type"` // 字段类型
|
||||
Value string `json:"value"` // 当前值
|
||||
Required bool `json:"required"` // 是否必填
|
||||
BoundBox BoundingBox `json:"boundBox"` // 位置
|
||||
}
|
||||
```
|
||||
|
||||
## 核心特性
|
||||
|
||||
### 自动类型转换
|
||||
- 指定 `OutputSchema` 时,`QueryResult.Data` 自动转换为指定类型
|
||||
- 支持直接类型断言:`result.Data.(*YourType)`
|
||||
- 无需手动调用转换函数
|
||||
|
||||
### 多级回退机制
|
||||
1. 优先解析为指定的结构化类型
|
||||
2. 失败时尝试通用JSON解析
|
||||
3. 最终回退到纯文本响应
|
||||
|
||||
### 向后兼容
|
||||
- 不指定 `OutputSchema` 时行为不变
|
||||
- 现有代码无需修改
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 结构体设计
|
||||
|
||||
```go
|
||||
// 推荐:包含标准字段
|
||||
type YourSchema struct {
|
||||
Content string `json:"content"` // 必须:人类可读描述
|
||||
Thought string `json:"thought"` // 必须:AI推理过程
|
||||
// 自定义字段...
|
||||
Data CustomData `json:"data"`
|
||||
}
|
||||
|
||||
// 使用描述性的JSON标签
|
||||
type Element struct {
|
||||
Type string `json:"elementType"` // 清晰的字段名
|
||||
Position Point `json:"gridPosition"` // 描述性标签
|
||||
Visible bool `json:"isVisible"` // 布尔值清晰性
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 查询指令
|
||||
|
||||
```go
|
||||
// 推荐:详细的查询指令
|
||||
opts := &ai.QueryOptions{
|
||||
Query: `分析这张截图并提供结构化信息:
|
||||
1. 识别界面类型和主要元素
|
||||
2. 提取所有可交互元素的位置和属性
|
||||
3. 统计各类元素的数量`,
|
||||
Screenshot: screenshot,
|
||||
Size: size,
|
||||
OutputSchema: YourSchema{},
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 错误处理
|
||||
|
||||
```go
|
||||
result, err := querier.Query(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 类型断言
|
||||
if data, ok := result.Data.(*YourSchema); ok {
|
||||
// 使用结构化数据
|
||||
processData(data)
|
||||
} else {
|
||||
// 回退到文本结果
|
||||
log.Printf("结构化解析失败,使用文本结果: %s", result.Content)
|
||||
}
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||
"github.com/httprunner/httprunner/v5/uixt/ai"
|
||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||
)
|
||||
|
||||
type ScreenAnalysis struct {
|
||||
Content string `json:"content"`
|
||||
Thought string `json:"thought"`
|
||||
Elements []string `json:"elements"`
|
||||
Categories []string `json:"categories"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
// 创建查询器
|
||||
modelConfig, err := ai.GetModelConfig(option.OPENAI_GPT_4O)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
querier, err := ai.NewQuerier(ctx, modelConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 加载截图
|
||||
screenshot, size, err := builtin.LoadImage("screenshot.png")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 执行结构化查询
|
||||
result, err := querier.Query(ctx, &ai.QueryOptions{
|
||||
Query: "分析截图中的UI元素,提取元素类型和分类信息",
|
||||
Screenshot: screenshot,
|
||||
Size: size,
|
||||
OutputSchema: ScreenAnalysis{},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 使用结构化数据
|
||||
if analysis, ok := result.Data.(*ScreenAnalysis); ok {
|
||||
fmt.Printf("发现 %d 个元素\n", analysis.Count)
|
||||
fmt.Printf("元素类型: %v\n", analysis.Elements)
|
||||
fmt.Printf("分类: %v\n", analysis.Categories)
|
||||
} else {
|
||||
fmt.Printf("文本结果: %s\n", result.Content)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 辅助函数
|
||||
|
||||
对于特殊情况,提供了类型转换辅助函数:
|
||||
|
||||
```go
|
||||
// 手动类型转换(通常不需要)
|
||||
converted, err := ai.ConvertQueryResultData[YourType](result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:使用 `OutputSchema` 时,`Data` 字段已自动转换为正确类型,通常不需要手动调用此函数。
|
||||
|
||||
## 技术限制
|
||||
|
||||
- 需要支持结构化输出的AI模型(如 OpenAI GPT-4)
|
||||
- 复杂嵌套结构需要清晰的查询指令
|
||||
- AI模型可能不总是严格遵循指定格式
|
||||
- UI-TARS 模型使用不同的响应格式处理
|
||||
Reference in New Issue
Block a user