From 51ee639cacf91fff13f7f33faa71171c2f439238 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Wed, 11 Jun 2025 14:30:49 +0800 Subject: [PATCH] docs: update docs --- uixt/ai/README.md => docs/uixt/AI.md | 114 +- docs/uixt/README.md | 328 ++++++ docs/uixt/devices.md | 1047 ++++++++++++++++ docs/uixt/drivers.md | 934 +++++++++++++++ uixt/mcp_server.md => docs/uixt/mcp-server.md | 0 docs/uixt/mcp-tools.md | 1049 +++++++++++++++++ docs/uixt/operations.md | 885 ++++++++++++++ docs/uixt/options.md | 699 +++++++++++ docs/uixt/{ui_mark.md => ui-mark.md} | 0 internal/version/VERSION | 2 +- uixt/README.md | 34 - uixt/ai/querier.md | 299 ----- 12 files changed, 5047 insertions(+), 344 deletions(-) rename uixt/ai/README.md => docs/uixt/AI.md (80%) create mode 100644 docs/uixt/README.md create mode 100644 docs/uixt/devices.md create mode 100644 docs/uixt/drivers.md rename uixt/mcp_server.md => docs/uixt/mcp-server.md (100%) create mode 100644 docs/uixt/mcp-tools.md create mode 100644 docs/uixt/operations.md create mode 100644 docs/uixt/options.md rename docs/uixt/{ui_mark.md => ui-mark.md} (100%) delete mode 100644 uixt/README.md delete mode 100644 uixt/ai/querier.md diff --git a/uixt/ai/README.md b/docs/uixt/AI.md similarity index 80% rename from uixt/ai/README.md rename to docs/uixt/AI.md index 577ecb46..cdfd72c3 100644 --- a/uixt/ai/README.md +++ b/docs/uixt/AI.md @@ -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"` // 布尔值清晰性 } ``` diff --git a/docs/uixt/README.md b/docs/uixt/README.md new file mode 100644 index 00000000..d9a8ded2 --- /dev/null +++ b/docs/uixt/README.md @@ -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 自动化测试更智能、更简单! diff --git a/docs/uixt/devices.md b/docs/uixt/devices.md new file mode 100644 index 00000000..e33fffad --- /dev/null +++ b/docs/uixt/devices.md @@ -0,0 +1,1047 @@ +# 设备管理文档 + +## 概述 + +HttpRunner UIXT 提供统一的设备管理接口,支持 Android、iOS、HarmonyOS 和 Web 浏览器等多种平台的设备发现、连接和管理。 + +## 设备接口 + +### IDevice 核心接口 + +所有设备都实现统一的 `IDevice` 接口: + +```go +type IDevice interface { + UUID() string // 设备唯一标识 + NewDriver(driverType DriverType) (IDriver, error) // 创建驱动 +} +``` + +## Android 设备 + +### 环境准备 + +#### Android SDK 安装 + +```bash +# 下载并安装 Android SDK +export ANDROID_HOME=/path/to/android-sdk +export PATH=$PATH:$ANDROID_HOME/platform-tools +export PATH=$PATH:$ANDROID_HOME/tools + +# 验证安装 +adb version +``` + +#### 真机配置 + +1. **开启开发者选项** + - 进入设置 → 关于手机 + - 连续点击版本号 7 次 + +2. **启用 USB 调试** + - 进入设置 → 开发者选项 + - 开启 USB 调试 + +3. **连接设备** + ```bash + # 连接设备并授权 + adb devices + + # 如果显示 unauthorized,在设备上点击允许 + ``` + +#### 模拟器配置 + +```bash +# 创建 AVD +avdmanager create avd -n test_device -k "system-images;android-30;google_apis;x86_64" + +# 启动模拟器 +emulator -avd test_device + +# 验证连接 +adb devices +``` + +### 设备创建 + +```go +import "github.com/httprunner/httprunner/v5/uixt/option" + +// 基础创建 +device, err := uixt.NewAndroidDevice( + option.WithSerialNumber("device_serial"), +) + +// 高级配置 +device, err := uixt.NewAndroidDevice( + option.WithSerialNumber("emulator-5554"), + option.WithAdbLogOn(true), // 启用 ADB 日志 + option.WithReset(true), // 重置设备状态 + option.WithSystemPort(8200), // 系统端口 + option.WithDevicePort(6790), // 设备端口 + option.WithForwardPort(8080), // 端口转发 + option.WithInstallApp("/path/to/app.apk"), // 安装应用 + option.WithGrantPermissions(true), // 授予权限 + option.WithSkipServerInstallation(false), // 跳过服务器安装 + option.WithUiAutomator2Timeout(60), // UiAutomator2 超时 +) +``` + +### 设备发现 + +```go +// 发现所有连接的 Android 设备 +devices, err := uixt.DiscoverAndroidDevices() +for _, device := range devices { + fmt.Printf("Found device: %s\n", device.UUID()) +} + +// 发现模拟器 +emulators, err := uixt.DiscoverAndroidEmulators() +``` + +### 配置选项 + +| 选项 | 类型 | 说明 | 默认值 | +|------|------|------|--------| +| `WithSerialNumber` | string | 设备序列号 | 必需 | +| `WithAdbLogOn` | bool | 启用 ADB 日志 | false | +| `WithReset` | bool | 重置设备状态 | false | +| `WithSystemPort` | int | UiAutomator2 系统端口 | 8200 | +| `WithDevicePort` | int | 设备端口 | 6790 | +| `WithForwardPort` | int | 端口转发 | 0 | +| `WithInstallApp` | string | 安装应用路径 | "" | +| `WithGrantPermissions` | bool | 自动授予权限 | false | +| `WithSkipServerInstallation` | bool | 跳过服务器安装 | false | +| `WithUiAutomator2Timeout` | int | UiAutomator2 超时(秒) | 60 | + +### Android 特有功能 + +#### 应用管理 + +```go +// 应用安装 +err = driver.InstallApp("/path/to/app.apk") +err = driver.InstallApp("/path/to/app.apk", option.WithForceInstall(true)) + +// 应用卸载 +err = driver.UninstallApp("com.example.app") +err = driver.UninstallApp("com.example.app", option.WithKeepData(true)) + +// 应用信息 +appInfo, err := driver.GetAppInfo("com.example.app") +installed, err := driver.IsAppInstalled("com.example.app") +permissions, err := driver.GetAppPermissions("com.example.app") +``` + +#### 权限管理 + +```go +// 授予权限 +err = driver.GrantPermission("com.example.app", "android.permission.CAMERA") + +// 撤销权限 +err = driver.RevokePermission("com.example.app", "android.permission.CAMERA") + +// 批量授予权限 +permissions := []string{ + "android.permission.CAMERA", + "android.permission.RECORD_AUDIO", + "android.permission.ACCESS_FINE_LOCATION", +} +err = driver.GrantPermissions("com.example.app", permissions) +``` + +#### 系统设置 + +```go +// WiFi 操作 +err = driver.EnableWiFi() +err = driver.DisableWiFi() +err = driver.ConnectWiFi("SSID", "password") + +// 移动数据操作 +err = driver.EnableMobileData() +err = driver.DisableMobileData() + +// 飞行模式 +err = driver.EnableAirplaneMode() +err = driver.DisableAirplaneMode() +``` + +### 设备信息 + +```go +// 获取设备信息 +info, err := device.DeviceInfo() +fmt.Printf("Device: %s %s\n", info.Brand, info.Model) +fmt.Printf("Android: %s\n", info.Version) + +// 获取电池信息 +battery, err := device.BatteryInfo() +fmt.Printf("Battery: %d%%\n", battery.Level) + +// 获取屏幕尺寸 +size, err := device.WindowSize() +fmt.Printf("Screen: %dx%d\n", size.Width, size.Height) +``` + +## iOS 设备 + +### 环境准备 + +#### Xcode 和开发者工具 + +```bash +# 安装 Xcode(从 App Store) +# 安装命令行工具 +xcode-select --install + +# 安装 ios-deploy +npm install -g ios-deploy + +# 验证安装 +ios-deploy --version +``` + +#### WebDriverAgent 配置 + +```bash +# 克隆 WebDriverAgent +git clone https://github.com/appium/WebDriverAgent.git +cd WebDriverAgent + +# 配置开发者证书 +# 在 Xcode 中打开 WebDriverAgent.xcodeproj +# 设置开发团队和签名证书 + +# 构建并安装到设备 +xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination 'id=device_udid' test +``` + +#### 真机配置 + +1. **启用开发者模式** + - 连接设备到 Mac + - 在设备上信任开发者证书 + +2. **设备信任** + - 设置 → 通用 → VPN与设备管理 + - 信任开发者应用 + +3. **获取设备 UDID** + ```bash + # 使用 Xcode + xcrun simctl list devices + + # 使用 idevice_id + idevice_id -l + ``` + +#### 模拟器配置 + +```bash +# 列出可用的模拟器 +xcrun simctl list devices + +# 创建新模拟器 +xcrun simctl create "iPhone 14" "iPhone 14" "iOS 16.0" + +# 启动模拟器 +xcrun simctl boot "iPhone 14" + +# 安装应用到模拟器 +xcrun simctl install booted /path/to/app.app +``` + +### 设备创建 + +```go +// 基础创建 +device, err := uixt.NewIOSDevice( + option.WithUDID("device_udid"), +) + +// 高级配置 +device, err := uixt.NewIOSDevice( + option.WithUDID("00008030-001234567890123A"), + option.WithWDAPort(8700), // WDA 端口 + option.WithWDAMjpegPort(8800), // MJPEG 端口 + option.WithResetHomeOnStartup(false), // 启动时不回到主屏 + option.WithPreventWDAAttachments(true), // 防止 WDA 附件 + option.WithWDAStartupTimeout(120), // WDA 启动超时 + option.WithWDAConnectionTimeout(60), // WDA 连接超时 + option.WithWDACommandTimeout(30), // WDA 命令超时 + option.WithAcceptAlerts(true), // 自动接受弹窗 + option.WithDismissAlerts(false), // 自动关闭弹窗 +) +``` + +### 设备发现 + +```go +// 发现所有连接的 iOS 设备 +devices, err := uixt.DiscoverIOSDevices() +for _, device := range devices { + fmt.Printf("Found device: %s\n", device.UUID()) +} + +// 发现模拟器 +simulators, err := uixt.DiscoverIOSSimulators() + +// 按条件筛选设备 +realDevices, err := uixt.DiscoverIOSDevices(uixt.DeviceFilter{ + DeviceType: "real", + IOSVersion: "16.0+", +}) +``` + +### 配置选项 + +| 选项 | 类型 | 说明 | 默认值 | +|------|------|------|--------| +| `WithUDID` | string | 设备 UDID | 必需 | +| `WithWDAPort` | int | WebDriverAgent 端口 | 8700 | +| `WithWDAMjpegPort` | int | MJPEG 流端口 | 8800 | +| `WithResetHomeOnStartup` | bool | 启动时回到主屏 | true | +| `WithPreventWDAAttachments` | bool | 防止 WDA 附件 | false | +| `WithWDAStartupTimeout` | int | WDA 启动超时(秒) | 120 | +| `WithWDAConnectionTimeout` | int | WDA 连接超时(秒) | 60 | +| `WithWDACommandTimeout` | int | WDA 命令超时(秒) | 30 | +| `WithAcceptAlerts` | bool | 自动接受弹窗 | false | +| `WithDismissAlerts` | bool | 自动关闭弹窗 | false | + +### iOS 特有功能 + +#### WebDriverAgent 管理 + +```go +// 启动 WDA +err = device.StartWDA() + +// 停止 WDA +err = device.StopWDA() + +// 检查 WDA 状态 +isRunning := device.IsWDARunning() + +// 重启 WDA +err = device.RestartWDA() + +// 获取 WDA 状态 +status, err := device.GetWDAStatus() +``` + +#### 应用管理 + +```go +// 应用安装(需要开发者证书) +err = driver.InstallApp("/path/to/app.ipa") + +// 应用卸载 +err = driver.UninstallApp("com.example.app") + +// 应用信息 +appInfo, err := driver.GetAppInfo("com.example.app") +installed, err := driver.IsAppInstalled("com.example.app") + +// 应用状态 +state, err := driver.GetAppState("com.example.app") +// 0: not installed, 1: not running, 2: running in background, 4: running in foreground +``` + +#### 系统操作 + +```go +// Siri 操作 +err = driver.ActivateSiri("打开设置") + +// 锁定/解锁 +err = driver.Lock() +err = driver.Unlock() + +// 摇晃设备 +err = driver.Shake() + +// 音量控制 +err = driver.VolumeUp() +err = driver.VolumeDown() + +// 截图和录制 +screenshot, err := driver.ScreenShot() +err = driver.StartScreenRecord() +videoPath, err := driver.StopScreenRecord() +``` + +#### 设备信息 + +```go +// 获取设备信息 +info, err := device.DeviceInfo() +fmt.Printf("Device: %s %s\n", info.Model, info.Name) +fmt.Printf("iOS: %s\n", info.Version) + +// 获取电池信息 +battery, err := device.BatteryInfo() +fmt.Printf("Battery: %d%%, State: %s\n", battery.Level, battery.State) + +// 获取屏幕信息 +size, err := device.WindowSize() +scale, err := device.GetScreenScale() +``` + +## HarmonyOS 设备 + +### 环境准备 + +#### HarmonyOS SDK 安装 + +```bash +# 下载并安装 HarmonyOS SDK +export HARMONY_HOME=/path/to/harmony-sdk +export PATH=$PATH:$HARMONY_HOME/toolchains + +# 验证安装 +hdc version +``` + +#### 设备配置 + +1. **开启开发者模式** + - 进入设置 → 关于手机 + - 连续点击版本号 7 次 + +2. **启用 USB 调试** + - 进入设置 → 系统和更新 → 开发人员选项 + - 开启 USB 调试 + +3. **连接设备** + ```bash + # 连接设备并授权 + hdc list targets + + # 如果显示 unauthorized,在设备上点击允许 + ``` + +### 设备创建 + +```go +// 基础创建 +device, err := uixt.NewHarmonyDevice( + option.WithConnectKey("device_connect_key"), +) + +// 高级配置 +device, err := uixt.NewHarmonyDevice( + option.WithConnectKey("192.168.1.100:5555"), + option.WithHDCLogOn(true), // 启用 HDC 日志 + option.WithSystemPort(9200), // 系统端口 + option.WithDevicePort(6790), // 设备端口 + option.WithHDCTimeout(60), // HDC 超时 +) +``` + +### 设备发现 + +```go +// 发现所有连接的 HarmonyOS 设备 +devices, err := uixt.DiscoverHarmonyDevices() +for _, device := range devices { + fmt.Printf("Found device: %s\n", device.UUID()) +} + +// 网络设备发现 +networkDevices, err := uixt.DiscoverHarmonyNetworkDevices() +``` + +### 配置选项 + +| 选项 | 类型 | 说明 | 默认值 | +|------|------|------|--------| +| `WithConnectKey` | string | 设备连接密钥 | 必需 | +| `WithHDCLogOn` | bool | 启用 HDC 日志 | false | +| `WithSystemPort` | int | 系统端口 | 9200 | +| `WithDevicePort` | int | 设备端口 | 6790 | +| `WithHDCTimeout` | int | HDC 超时(秒) | 60 | + +### HarmonyOS 特有功能 + +#### 应用管理 + +```go +// 应用安装 +err = driver.InstallApp("/path/to/app.hap") + +// 应用卸载 +err = driver.UninstallApp("com.example.harmony.app") + +// 应用信息 +appInfo, err := driver.GetAppInfo("com.example.harmony.app") +installed, err := driver.IsAppInstalled("com.example.harmony.app") +``` + +#### 分布式操作 + +```go +// 设备协同 +err = driver.ConnectDistributedDevice("target_device_id") +err = driver.DisconnectDistributedDevice("target_device_id") + +// 跨设备应用迁移 +err = driver.MigrateApp("com.example.app", "target_device_id") +``` + +#### 原子化服务 + +```go +// 启动原子化服务 +err = driver.LaunchAtomicService("service_id", map[string]interface{}{ + "param1": "value1", + "param2": "value2", +}) + +// 停止原子化服务 +err = driver.StopAtomicService("service_id") +``` + +## Web 浏览器设备 + +### 环境准备 + +#### 浏览器驱动安装 + +```bash +# Chrome +# 下载 ChromeDriver 并添加到 PATH +wget https://chromedriver.storage.googleapis.com/latest/chromedriver_mac64.zip +unzip chromedriver_mac64.zip +mv chromedriver /usr/local/bin/ + +# Firefox +# 下载 GeckoDriver +wget https://github.com/mozilla/geckodriver/releases/download/v0.33.0/geckodriver-v0.33.0-macos.tar.gz +tar -xzf geckodriver-v0.33.0-macos.tar.gz +mv geckodriver /usr/local/bin/ + +# Safari (macOS only) +# 启用开发者菜单 +# Safari → 偏好设置 → 高级 → 在菜单栏中显示"开发"菜单 +# 开发 → 允许远程自动化 + +# Edge +# 下载 EdgeDriver +wget https://msedgedriver.azureedge.net/latest/edgedriver_mac64.zip +``` + +### 设备创建 + +```go +// Chrome 浏览器 +device, err := uixt.NewBrowserDevice( + option.WithBrowserID("chrome"), +) + +// 高级配置 +device, err := uixt.NewBrowserDevice( + option.WithBrowserID("chrome"), + option.WithHeadless(false), // 非无头模式 + option.WithWindowSize(1920, 1080), // 窗口大小 + option.WithUserAgent("custom-agent"), // 自定义 User-Agent + option.WithProxy("http://proxy:8080"), // 代理设置 + option.WithExtensions([]string{"ext1", "ext2"}), // 扩展 + option.WithDownloadDir("/path/to/downloads"), // 下载目录 + option.WithIncognito(true), // 隐私模式 +) +``` + +### 支持的浏览器 + +| 浏览器 | ID | 驱动 | 说明 | +|--------|----|----- |------| +| Chrome | `chrome` | ChromeDriver | Google Chrome | +| Firefox | `firefox` | GeckoDriver | Mozilla Firefox | +| Safari | `safari` | SafariDriver | Apple Safari (macOS) | +| Edge | `edge` | EdgeDriver | Microsoft Edge | + +### 配置选项 + +| 选项 | 类型 | 说明 | 默认值 | +|------|------|------|--------| +| `WithBrowserID` | string | 浏览器标识 | 必需 | +| `WithHeadless` | bool | 无头模式 | true | +| `WithWindowSize` | int, int | 窗口大小 | 1280x720 | +| `WithUserAgent` | string | User-Agent | 默认 | +| `WithProxy` | string | 代理地址 | 无 | +| `WithExtensions` | []string | 扩展列表 | 无 | +| `WithDownloadDir` | string | 下载目录 | 默认 | +| `WithIncognito` | bool | 隐私模式 | false | + +### Web 特有功能 + +#### 页面管理 + +```go +// 页面导航 +err = driver.NavigateTo("https://example.com") +err = driver.Refresh() +err = driver.GoBack() +err = driver.GoForward() + +// 页面信息 +title, err := driver.GetTitle() +url, err := driver.GetCurrentURL() +source, err := driver.GetPageSource() +``` + +#### 标签页管理 + +```go +// 标签页操作 +err = driver.NewTab() +err = driver.CloseTab(1) +err = driver.SwitchToTab(0) + +// 获取标签页信息 +tabs, err := driver.GetTabs() +currentTab, err := driver.GetCurrentTab() +``` + +#### Cookie 管理 + +```go +// Cookie 操作 +cookies, err := driver.GetCookies() +err = driver.SetCookie("name", "value", "domain.com") +err = driver.DeleteCookie("name") +err = driver.DeleteAllCookies() +``` + +#### JavaScript 执行 + +```go +// 执行 JavaScript +result, err := driver.ExecuteScript("return document.title;") +err = driver.ExecuteAsyncScript("callback(arguments[0]);", "test") + +// 注入脚本 +err = driver.InjectScript("console.log('injected');") +``` + +## 设备管理工具 + +### 设备发现工具 + +```go +// 发现所有平台的设备 +allDevices, err := uixt.DiscoverAllDevices() +for platform, devices := range allDevices { + fmt.Printf("Platform: %s\n", platform) + for _, device := range devices { + fmt.Printf(" Device: %s\n", device.UUID()) + } +} + +// 按平台发现 +androidDevices, err := uixt.DiscoverDevicesByPlatform("android") +iosDevices, err := uixt.DiscoverDevicesByPlatform("ios") +``` + +### 设备选择工具 + +```go +// 交互式设备选择 +device, err := uixt.SelectDeviceInteractively() + +// 按条件选择设备 +device, err := uixt.SelectDevice(uixt.DeviceFilter{ + Platform: "android", + Model: "Pixel", + Online: true, + Version: "11+", +}) + +// 智能选择最佳设备 +device, err := uixt.SelectBestDevice(uixt.DevicePreference{ + PreferReal: true, // 优先真机 + PreferHighRes: true, // 优先高分辨率 + PreferNewVersion: true, // 优先新版本 +}) +``` + +### 设备健康检查 + +```go +// 检查设备健康状态 +health, err := device.HealthCheck() +if health.IsHealthy { + fmt.Println("Device is healthy") +} else { + fmt.Printf("Device issues: %v\n", health.Issues) +} + +// 修复设备问题 +err = device.Repair() + +// 设备诊断 +diagnosis, err := device.Diagnose() +fmt.Printf("Diagnosis: %s\n", diagnosis.Report) +``` + +## 设备状态管理 + +### 设备状态 + +```go +// 获取设备状态 +status, err := device.Status() +fmt.Printf("Status: %s\n", status.State) // online, offline, unauthorized + +// 等待设备就绪 +err = device.WaitForReady(30 * time.Second) + +// 检查设备连接 +isConnected := device.IsConnected() + +// 设备可用性检查 +isAvailable := device.IsAvailable() +``` + +### 设备重置 + +```go +// 软重置(重启应用) +err = device.SoftReset() + +// 硬重置(重启设备) +err = device.HardReset() + +// 恢复出厂设置(仅 Android) +err = device.FactoryReset() + +// 清理设备缓存 +err = device.ClearCache() +``` + +## 多设备管理 + +### 设备池 + +```go +// 创建设备池 +pool := uixt.NewDevicePool() + +// 添加设备到池 +pool.AddDevice(androidDevice) +pool.AddDevice(iosDevice) +pool.AddDevice(harmonyDevice) + +// 从池中获取可用设备 +device, err := pool.AcquireDevice(uixt.DeviceFilter{ + Platform: "android", +}) +defer pool.ReleaseDevice(device) + +// 并行执行任务 +results := pool.ExecuteParallel(func(device IDevice) interface{} { + // 在设备上执行任务 + return performTask(device) +}) + +// 设备池统计 +stats := pool.GetStats() +fmt.Printf("Total: %d, Available: %d, InUse: %d\n", + stats.Total, stats.Available, stats.InUse) +``` + +### 设备同步 + +```go +// 同步多个设备的操作 +sync := uixt.NewDeviceSync() +sync.AddDevice(device1) +sync.AddDevice(device2) +sync.AddDevice(device3) + +// 同步执行操作 +err = sync.Execute(func(device IDevice) error { + return device.TapXY(0.5, 0.5) +}) + +// 等待所有设备完成 +err = sync.WaitForAll(30 * time.Second) +``` + +### 设备集群 + +```go +// 创建设备集群 +cluster := uixt.NewDeviceCluster() + +// 添加设备组 +cluster.AddGroup("android", androidDevices) +cluster.AddGroup("ios", iosDevices) + +// 按组执行任务 +results, err := cluster.ExecuteByGroup("android", func(device IDevice) interface{} { + return performAndroidTask(device) +}) + +// 负载均衡 +device, err := cluster.GetLeastBusyDevice() +``` + +## 最佳实践 + +### 1. 设备选择策略 + +```go +// 优先选择真机,其次模拟器 +func selectBestDevice() (IDevice, error) { + // 先尝试真机 + devices, err := uixt.DiscoverAndroidDevices() + if err == nil && len(devices) > 0 { + return devices[0], nil + } + + // 再尝试模拟器 + emulators, err := uixt.DiscoverAndroidEmulators() + if err == nil && len(emulators) > 0 { + return emulators[0], nil + } + + return nil, fmt.Errorf("no available devices") +} +``` + +### 2. 设备资源管理 + +```go +// 使用 defer 确保资源释放 +func useDevice() error { + device, err := uixt.NewAndroidDevice(option.WithSerialNumber("device_serial")) + if err != nil { + return err + } + defer device.Cleanup() // 确保清理资源 + + // 使用设备... + return nil +} +``` + +### 3. 错误处理和重试 + +```go +// 带重试的设备操作 +func performWithRetry(device IDevice, operation func() error) error { + maxRetries := 3 + for i := 0; i < maxRetries; i++ { + err := operation() + if err == nil { + return nil + } + + // 检查是否是设备连接问题 + if isDeviceConnectionError(err) { + // 尝试重新连接 + device.Reconnect() + } + + time.Sleep(time.Duration(i+1) * time.Second) + } + return fmt.Errorf("operation failed after %d retries", maxRetries) +} +``` + +### 4. 设备监控 + +```go +// 设备监控 +func monitorDevice(device IDevice) { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for range ticker.C { + status, err := device.Status() + if err != nil { + log.Error("Failed to get device status: %v", err) + continue + } + + if status.State != "online" { + log.Warn("Device %s is %s", device.UUID(), status.State) + // 尝试修复 + device.Repair() + } + } +} +``` + +## 故障排除 + +### 常见问题 + +#### Android 设备 + +1. **设备未识别** + ```bash + # 检查 ADB 连接 + adb devices + + # 重启 ADB 服务 + adb kill-server + adb start-server + + # 检查驱动程序 + # Windows: 更新设备驱动 + # macOS: 检查系统偏好设置中的安全性设置 + ``` + +2. **UiAutomator2 启动失败** + ```bash + # 检查端口占用 + netstat -an | grep 8200 + + # 清理应用数据 + adb shell pm clear io.appium.uiautomator2.server + adb shell pm clear io.appium.uiautomator2.server.test + + # 重新安装服务 + adb uninstall io.appium.uiautomator2.server + adb uninstall io.appium.uiautomator2.server.test + ``` + +3. **权限问题** + ```bash + # 检查 USB 调试权限 + adb shell settings get global development_settings_enabled + + # 授予应用权限 + adb shell pm grant com.example.app android.permission.CAMERA + ``` + +#### iOS 设备 + +1. **WDA 启动失败** + ```bash + # 检查开发者证书 + security find-identity -v -p codesigning + + # 重新安装 WDA + xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination 'id=device_udid' test + + # 检查设备信任 + # 设置 → 通用 → VPN与设备管理 → 信任开发者应用 + ``` + +2. **设备信任问题** + - 在设备上信任开发者证书 + - 检查设备是否已解锁 + - 确保设备已配对 + +3. **网络连接问题** + ```bash + # 检查端口转发 + iproxy 8700 8700 device_udid + + # 测试 WDA 连接 + curl http://localhost:8700/status + ``` + +#### HarmonyOS 设备 + +1. **HDC 连接失败** + ```bash + # 检查 HDC 连接 + hdc list targets + + # 重启 HDC 服务 + hdc kill + hdc start + + # 检查网络连接(网络调试) + ping device_ip + ``` + +2. **应用安装失败** + ```bash + # 检查应用签名 + hdc shell bm dump -a + + # 清理应用数据 + hdc shell bm uninstall -n com.example.app + ``` + +#### Web 浏览器 + +1. **驱动版本不匹配** + ```bash + # 检查浏览器版本 + google-chrome --version + firefox --version + + # 更新驱动程序 + # 确保驱动版本与浏览器版本匹配 + ``` + +2. **端口冲突** + ```bash + # 查找占用端口的进程 + lsof -i :4444 + + # 终止进程 + kill -9 + ``` + +#### 通用问题 + +1. **端口冲突** + ```bash + # 查找占用端口的进程 + lsof -i :8700 + + # 终止进程 + kill -9 + + # 使用不同端口 + device, err := uixt.NewIOSDevice( + option.WithUDID("device_udid"), + option.WithWDAPort(8701), + ) + ``` + +2. **权限问题** + ```bash + # 检查文件权限 + ls -la /path/to/device/files + + # 修改权限 + chmod +x /path/to/executable + + # macOS 安全设置 + # 系统偏好设置 → 安全性与隐私 → 隐私 → 辅助功能 + ``` + +3. **内存不足** + ```bash + # 检查系统资源 + top + free -h + + # 清理设备缓存 + device.ClearCache() + + # 重启设备 + device.HardReset() + ``` + +## 参考资料 + +- [Android Debug Bridge (ADB)](https://developer.android.com/studio/command-line/adb) +- [WebDriverAgent](https://github.com/appium/WebDriverAgent) +- [HarmonyOS HDC](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-debugging-and-testing-0000001263040487) +- [WebDriver 协议](https://w3c.github.io/webdriver/) +- [ChromeDriver](https://chromedriver.chromium.org/) +- [GeckoDriver](https://github.com/mozilla/geckodriver) \ No newline at end of file diff --git a/docs/uixt/drivers.md b/docs/uixt/drivers.md new file mode 100644 index 00000000..692fca40 --- /dev/null +++ b/docs/uixt/drivers.md @@ -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) \ No newline at end of file diff --git a/uixt/mcp_server.md b/docs/uixt/mcp-server.md similarity index 100% rename from uixt/mcp_server.md rename to docs/uixt/mcp-server.md diff --git a/docs/uixt/mcp-tools.md b/docs/uixt/mcp-tools.md new file mode 100644 index 00000000..af3c4611 --- /dev/null +++ b/docs/uixt/mcp-tools.md @@ -0,0 +1,1049 @@ +# MCP 工具文档 + +## 概述 + +HttpRunner UIXT 基于 Model Context Protocol (MCP) 协议实现了标准化的工具接口,将所有 UI 操作封装为 MCP 工具,支持 AI 模型直接调用,实现真正的智能化 UI 自动化。 + +## MCP 架构 + +### 整体架构 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ MCP 生态系统 │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ MCP Client │ │ MCP Server │ │ Tool Registry │ │ +│ │ (AI Model) │◄──►│ (UIXT Server) │◄──►│ (工具注册) │ │ +│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ 工具层 │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ Device Tools │ │ Action Tools │ │ AI Tools │ │ +│ │ (设备工具) │ │ (操作工具) │ │ (AI工具) │ │ +│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ 底层驱动 │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ Android Driver │ │ iOS Driver │ │ Browser Driver │ │ +│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 核心组件 + +#### MCPServer4XTDriver + +MCP 协议服务器主体: + +```go +type MCPServer4XTDriver struct { + mcpServer *server.MCPServer // MCP 协议服务器 + mcpTools []mcp.Tool // 注册的工具列表 + actionToolMap map[option.ActionName]ActionTool // 动作到工具的映射 +} +``` + +#### ActionTool 接口 + +所有 MCP 工具的统一契约: + +```go +type ActionTool interface { + Name() option.ActionName // 工具名称 + Description() string // 工具描述 + Options() []mcp.ToolOption // MCP 选项定义 + Implement() server.ToolHandlerFunc // 工具实现逻辑 + ConvertActionToCallToolRequest(action MobileAction) (mcp.CallToolRequest, error) // 动作转换 +} +``` + +## 工具分类 + +### 设备管理工具 (mcp_tools_device.go) + +#### list_available_devices +发现可用的设备和模拟器。 + +```json +{ + "name": "uixt__list_available_devices", + "description": "List all available devices including Android devices, iOS devices, and simulators", + "inputSchema": { + "type": "object", + "properties": {}, + "required": [] + } +} +``` + +**响应示例**: +```json +{ + "action": "list_available_devices", + "success": true, + "message": "Found 3 available devices", + "devices": [ + { + "platform": "android", + "serial": "emulator-5554", + "name": "Android Emulator", + "status": "online" + } + ], + "count": 3 +} +``` + +#### select_device +选择特定的设备进行操作。 + +```json +{ + "name": "uixt__select_device", + "description": "Select a specific device by platform and serial number", + "inputSchema": { + "type": "object", + "properties": { + "platform": { + "type": "string", + "description": "Device platform (android, ios, browser, harmony)" + }, + "serial": { + "type": "string", + "description": "Device serial number or identifier" + } + }, + "required": ["platform", "serial"] + } +} +``` + +### 触摸操作工具 (mcp_tools_touch.go) + +#### tap_xy +在相对坐标位置点击(0-1 范围)。 + +```json +{ + "name": "uixt__tap_xy", + "description": "Tap at relative coordinates (0-1 range)", + "inputSchema": { + "type": "object", + "properties": { + "x": { + "type": "number", + "description": "X coordinate (0-1 range)" + }, + "y": { + "type": "number", + "description": "Y coordinate (0-1 range)" + } + }, + "required": ["x", "y"] + } +} +``` + +#### tap_abs_xy +在绝对像素坐标位置点击。 + +```json +{ + "name": "uixt__tap_abs_xy", + "description": "Tap at absolute pixel coordinates", + "inputSchema": { + "type": "object", + "properties": { + "x": { + "type": "number", + "description": "Absolute X coordinate in pixels" + }, + "y": { + "type": "number", + "description": "Absolute Y coordinate in pixels" + } + }, + "required": ["x", "y"] + } +} +``` + +#### tap_ocr +通过 OCR 识别文本并点击。 + +```json +{ + "name": "uixt__tap_ocr", + "description": "Find text using OCR and tap on it", + "inputSchema": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Text to find and tap" + }, + "regex": { + "type": "boolean", + "description": "Whether to use regex matching" + }, + "index": { + "type": "integer", + "description": "Index of text occurrence to tap (0-based)" + } + }, + "required": ["text"] + } +} +``` + +#### tap_cv +通过计算机视觉识别 UI 元素并点击。 + +```json +{ + "name": "uixt__tap_cv", + "description": "Find UI element using computer vision and tap on it", + "inputSchema": { + "type": "object", + "properties": { + "element_type": { + "type": "string", + "description": "Type of UI element to find" + }, + "description": { + "type": "string", + "description": "Description of the element" + } + }, + "required": ["element_type"] + } +} +``` + +### 滑动操作工具 (mcp_tools_swipe.go) + +#### swipe +通用滑动操作,自动检测方向或坐标。 + +```json +{ + "name": "uixt__swipe", + "description": "Perform swipe gesture with automatic direction or coordinate detection", + "inputSchema": { + "type": "object", + "properties": { + "direction": { + "type": "string", + "description": "Swipe direction (up, down, left, right)" + }, + "from_x": { + "type": "number", + "description": "Start X coordinate (0-1 range)" + }, + "from_y": { + "type": "number", + "description": "Start Y coordinate (0-1 range)" + }, + "to_x": { + "type": "number", + "description": "End X coordinate (0-1 range)" + }, + "to_y": { + "type": "number", + "description": "End Y coordinate (0-1 range)" + } + } + } +} +``` + +#### swipe_to_tap_app +滑动查找并点击应用。 + +```json +{ + "name": "uixt__swipe_to_tap_app", + "description": "Swipe to find and tap on an app", + "inputSchema": { + "type": "object", + "properties": { + "app_name": { + "type": "string", + "description": "Name of the app to find and tap" + }, + "max_swipes": { + "type": "integer", + "description": "Maximum number of swipes to perform" + } + }, + "required": ["app_name"] + } +} +``` + +### 输入操作工具 (mcp_tools_input.go) + +#### input +在焦点元素上输入文本。 + +```json +{ + "name": "uixt__input", + "description": "Input text into the focused element", + "inputSchema": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Text to input" + } + }, + "required": ["text"] + } +} +``` + +#### set_ime +设置输入法编辑器。 + +```json +{ + "name": "uixt__set_ime", + "description": "Set the Input Method Editor (IME)", + "inputSchema": { + "type": "object", + "properties": { + "ime": { + "type": "string", + "description": "IME package name or identifier" + } + }, + "required": ["ime"] + } +} +``` + +### 按键操作工具 (mcp_tools_button.go) + +#### press_button +按设备按键。 + +```json +{ + "name": "uixt__press_button", + "description": "Press a device button", + "inputSchema": { + "type": "object", + "properties": { + "button": { + "type": "string", + "description": "Button name (home, back, volume_up, volume_down, etc.)" + } + }, + "required": ["button"] + } +} +``` + +### 应用管理工具 (mcp_tools_app.go) + +#### list_packages +列出所有已安装的应用包。 + +```json +{ + "name": "uixt__list_packages", + "description": "List all installed app packages on the device", + "inputSchema": { + "type": "object", + "properties": {}, + "required": [] + } +} +``` + +#### app_launch +启动应用。 + +```json +{ + "name": "uixt__app_launch", + "description": "Launch an app by package name", + "inputSchema": { + "type": "object", + "properties": { + "package_name": { + "type": "string", + "description": "Package name of the app to launch" + } + }, + "required": ["package_name"] + } +} +``` + +#### app_terminate +终止应用。 + +```json +{ + "name": "uixt__app_terminate", + "description": "Terminate a running app", + "inputSchema": { + "type": "object", + "properties": { + "package_name": { + "type": "string", + "description": "Package name of the app to terminate" + } + }, + "required": ["package_name"] + } +} +``` + +### 屏幕操作工具 (mcp_tools_screen.go) + +#### screenshot +捕获屏幕截图。 + +```json +{ + "name": "uixt__screenshot", + "description": "Take a screenshot of the device screen", + "inputSchema": { + "type": "object", + "properties": {}, + "required": [] + } +} +``` + +**响应示例**: +```json +{ + "action": "screenshot", + "success": true, + "message": "Screenshot captured successfully", + "screenshot": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", + "width": 1080, + "height": 1920 +} +``` + +#### get_screen_size +获取屏幕尺寸。 + +```json +{ + "name": "uixt__get_screen_size", + "description": "Get the screen size of the device", + "inputSchema": { + "type": "object", + "properties": {}, + "required": [] + } +} +``` + +### 实用工具 (mcp_tools_utility.go) + +#### sleep +等待指定秒数。 + +```json +{ + "name": "uixt__sleep", + "description": "Sleep for specified number of seconds", + "inputSchema": { + "type": "object", + "properties": { + "seconds": { + "type": "number", + "description": "Number of seconds to sleep" + } + }, + "required": ["seconds"] + } +} +``` + +#### close_popups +关闭弹窗或对话框。 + +```json +{ + "name": "uixt__close_popups", + "description": "Close popups or dialogs on the screen", + "inputSchema": { + "type": "object", + "properties": {}, + "required": [] + } +} +``` + +### Web 操作工具 (mcp_tools_web.go) + +#### secondary_click +在指定坐标右键点击。 + +```json +{ + "name": "uixt__secondary_click", + "description": "Perform secondary click (right-click) at coordinates", + "inputSchema": { + "type": "object", + "properties": { + "x": { + "type": "number", + "description": "X coordinate for secondary click" + }, + "y": { + "type": "number", + "description": "Y coordinate for secondary click" + } + }, + "required": ["x", "y"] + } +} +``` + +#### hover_by_selector +通过选择器悬停元素。 + +```json +{ + "name": "uixt__hover_by_selector", + "description": "Hover over element by CSS selector or XPath", + "inputSchema": { + "type": "object", + "properties": { + "selector": { + "type": "string", + "description": "CSS selector or XPath of the element" + } + }, + "required": ["selector"] + } +} +``` + +### AI 操作工具 (mcp_tools_ai.go) + +#### start_to_goal +使用自然语言描述执行从开始到目标的任务。 + +```json +{ + "name": "uixt__start_to_goal", + "description": "Execute a task from start to goal using natural language description", + "inputSchema": { + "type": "object", + "properties": { + "goal": { + "type": "string", + "description": "Natural language description of the goal" + } + }, + "required": ["goal"] + } +} +``` + +#### ai_action +使用自然语言提示执行 AI 驱动的动作。 + +```json +{ + "name": "uixt__ai_action", + "description": "Execute AI-driven action using natural language prompt", + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "Natural language prompt for the action" + } + }, + "required": ["prompt"] + } +} +``` + +## 工具实现 + +### ActionTool 实现示例 + +```go +// 点击工具实现 +type ToolTapXY struct { + X float64 `json:"x" desc:"X coordinate (0-1 range)"` + Y float64 `json:"y" desc:"Y coordinate (0-1 range)"` +} + +func (t *ToolTapXY) Name() option.ActionName { + return option.ActionTapXY +} + +func (t *ToolTapXY) Description() string { + return "Tap at relative coordinates (0-1 range)" +} + +func (t *ToolTapXY) Options() []mcp.ToolOption { + return []mcp.ToolOption{ + { + Name: "x", + Type: "number", + Description: "X coordinate (0-1 range)", + Required: true, + }, + { + Name: "y", + Type: "number", + Description: "Y coordinate (0-1 range)", + Required: true, + }, + } +} + +func (t *ToolTapXY) Implement() server.ToolHandlerFunc { + return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // 解析参数 + x, ok := req.Params.Arguments["x"].(float64) + if !ok { + return mcp.NewToolResultError("invalid x coordinate"), nil + } + + y, ok := req.Params.Arguments["y"].(float64) + if !ok { + return mcp.NewToolResultError("invalid y coordinate"), nil + } + + // 执行操作 + err := GetXTDriverFromContext(ctx).TapXY(x, y) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("tap failed: %v", err)), nil + } + + // 设置响应数据 + t.X = x + t.Y = y + + return NewMCPSuccessResponse( + fmt.Sprintf("Tapped at coordinates (%.2f, %.2f)", x, y), + t, + ), nil + } +} +``` + +### 响应格式 + +所有工具使用统一的扁平化响应格式: + +```go +func NewMCPSuccessResponse(message string, actionTool ActionTool) *mcp.CallToolResult { + response := map[string]interface{}{ + "action": string(actionTool.Name()), + "success": true, + "message": message, + } + + // 使用反射提取工具字段 + toolValue := reflect.ValueOf(actionTool) + if toolValue.Kind() == reflect.Ptr { + toolValue = toolValue.Elem() + } + + toolType := toolValue.Type() + for i := 0; i < toolValue.NumField(); i++ { + field := toolType.Field(i) + jsonTag := field.Tag.Get("json") + if jsonTag != "" && jsonTag != "-" { + fieldName := strings.Split(jsonTag, ",")[0] + response[fieldName] = toolValue.Field(i).Interface() + } + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + { + Type: mcp.ContentTypeText, + Text: toJSONString(response), + }, + }, + } +} +``` + +## 工具注册 + +### 服务器初始化 + +```go +func NewMCPServer() *MCPServer4XTDriver { + server := &MCPServer4XTDriver{ + mcpTools: make([]mcp.Tool, 0), + actionToolMap: make(map[option.ActionName]ActionTool), + } + + // 注册所有工具 + server.registerDeviceTools() + server.registerTouchTools() + server.registerSwipeTools() + server.registerInputTools() + server.registerButtonTools() + server.registerAppTools() + server.registerScreenTools() + server.registerUtilityTools() + server.registerWebTools() + server.registerAITools() + + return server +} +``` + +### 工具注册方法 + +```go +func (s *MCPServer4XTDriver) registerTool(tool ActionTool) { + // 创建 MCP 工具定义 + mcpTool := mcp.Tool{ + Name: fmt.Sprintf("uixt__%s", tool.Name()), + Description: tool.Description(), + InputSchema: map[string]interface{}{ + "type": "object", + "properties": generateProperties(tool.Options()), + "required": getRequiredFields(tool.Options()), + }, + } + + // 注册到服务器 + s.mcpTools = append(s.mcpTools, mcpTool) + s.actionToolMap[tool.Name()] = tool +} +``` + +## 工具调用 + +### 客户端调用 + +```go +// 通过 MCP 客户端调用工具 +func callTool(client client.MCPClient, toolName string, args map[string]interface{}) (*mcp.CallToolResult, error) { + req := mcp.CallToolRequest{ + Params: mcp.CallToolParams{ + Name: fmt.Sprintf("uixt__%s", toolName), + Arguments: args, + }, + } + + return client.CallTool(context.Background(), req) +} + +// 使用示例 +result, err := callTool(client, "tap_xy", map[string]interface{}{ + "x": 0.5, + "y": 0.5, +}) +``` + +### 服务器处理 + +```go +func (s *MCPServer4XTDriver) CallTool(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // 提取工具名称 + toolName := strings.TrimPrefix(req.Params.Name, "uixt__") + actionName := option.ActionName(toolName) + + // 查找工具 + tool, exists := s.actionToolMap[actionName] + if !exists { + return mcp.NewToolResultError(fmt.Sprintf("tool %s not found", toolName)), nil + } + + // 执行工具 + handler := tool.Implement() + return handler(ctx, req) +} +``` + +## 扩展开发 + +### 创建自定义工具 + +```go +// 1. 定义工具结构 +type ToolCustomAction struct { + Parameter1 string `json:"parameter1" desc:"Description of parameter1"` + Parameter2 int `json:"parameter2" desc:"Description of parameter2"` +} + +// 2. 实现 ActionTool 接口 +func (t *ToolCustomAction) Name() option.ActionName { + return option.ActionName("custom_action") +} + +func (t *ToolCustomAction) Description() string { + return "Perform a custom action" +} + +func (t *ToolCustomAction) Options() []mcp.ToolOption { + return []mcp.ToolOption{ + { + Name: "parameter1", + Type: "string", + Description: "Description of parameter1", + Required: true, + }, + { + Name: "parameter2", + Type: "integer", + Description: "Description of parameter2", + Required: false, + }, + } +} + +func (t *ToolCustomAction) Implement() server.ToolHandlerFunc { + return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // 解析参数 + param1, ok := req.Params.Arguments["parameter1"].(string) + if !ok { + return mcp.NewToolResultError("invalid parameter1"), nil + } + + param2, _ := req.Params.Arguments["parameter2"].(float64) + + // 执行自定义逻辑 + err := performCustomAction(param1, int(param2)) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("custom action failed: %v", err)), nil + } + + // 设置响应数据 + t.Parameter1 = param1 + t.Parameter2 = int(param2) + + return NewMCPSuccessResponse("Custom action completed", t), nil + } +} + +// 3. 注册工具 +func (s *MCPServer4XTDriver) registerCustomTools() { + s.registerTool(&ToolCustomAction{}) +} +``` + +### 工具分组 + +```go +// 按功能分组注册工具 +func (s *MCPServer4XTDriver) registerToolGroup(groupName string, tools []ActionTool) { + for _, tool := range tools { + // 添加分组前缀 + mcpTool := mcp.Tool{ + Name: fmt.Sprintf("uixt__%s__%s", groupName, tool.Name()), + Description: fmt.Sprintf("[%s] %s", groupName, tool.Description()), + InputSchema: generateInputSchema(tool), + } + + s.mcpTools = append(s.mcpTools, mcpTool) + s.actionToolMap[tool.Name()] = tool + } +} +``` + +## 最佳实践 + +### 1. 工具设计原则 + +```go +// 单一职责:每个工具只做一件事 +type ToolSinglePurpose struct { + // 明确的参数定义 + TargetText string `json:"target_text" desc:"Text to search for"` +} + +// 参数验证:在工具实现中验证参数 +func (t *ToolSinglePurpose) Implement() server.ToolHandlerFunc { + return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // 参数验证 + if err := t.validateParameters(req.Params.Arguments); err != nil { + return mcp.NewToolResultError(err.Error()), nil + } + + // 执行逻辑 + return t.execute(ctx, req) + } +} +``` + +### 2. 错误处理 + +```go +// 统一的错误处理 +func handleToolError(err error, toolName string) *mcp.CallToolResult { + if err == nil { + return nil + } + + // 记录错误日志 + log.Error().Err(err).Str("tool", toolName).Msg("tool execution failed") + + // 返回用户友好的错误信息 + return mcp.NewToolResultError(fmt.Sprintf("Tool %s failed: %v", toolName, err)) +} +``` + +### 3. 性能优化 + +```go +// 工具执行缓存 +type ToolCache struct { + cache map[string]*mcp.CallToolResult + mutex sync.RWMutex +} + +func (c *ToolCache) GetOrExecute(key string, executor func() (*mcp.CallToolResult, error)) (*mcp.CallToolResult, error) { + c.mutex.RLock() + if result, exists := c.cache[key]; exists { + c.mutex.RUnlock() + return result, nil + } + c.mutex.RUnlock() + + // 执行工具 + result, err := executor() + if err != nil { + return nil, err + } + + // 缓存结果 + c.mutex.Lock() + c.cache[key] = result + c.mutex.Unlock() + + return result, nil +} +``` + +### 4. 工具组合 + +```go +// 复合工具:组合多个基础工具 +type ToolComposite struct { + Steps []ToolStep `json:"steps" desc:"Sequence of tool steps"` +} + +type ToolStep struct { + Tool string `json:"tool"` + Arguments map[string]interface{} `json:"arguments"` +} + +func (t *ToolComposite) Implement() server.ToolHandlerFunc { + return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + results := make([]interface{}, 0, len(t.Steps)) + + for i, step := range t.Steps { + // 执行每个步骤 + result, err := executeToolStep(ctx, step) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("step %d failed: %v", i+1, err)), nil + } + results = append(results, result) + } + + return NewMCPSuccessResponse("Composite tool completed", t), nil + } +} +``` + +## 故障排除 + +### 常见问题 + +#### 工具注册失败 + +```go +// 检查工具注册 +func validateToolRegistration(server *MCPServer4XTDriver) error { + tools := server.ListTools() + if len(tools) == 0 { + return fmt.Errorf("no tools registered") + } + + // 检查必需工具 + requiredTools := []string{"tap_xy", "screenshot", "app_launch"} + for _, required := range requiredTools { + found := false + for _, tool := range tools { + if strings.HasSuffix(tool.Name, required) { + found = true + break + } + } + if !found { + return fmt.Errorf("required tool %s not found", required) + } + } + + return nil +} +``` + +#### 工具调用失败 + +```go +// 调试工具调用 +func debugToolCall(req mcp.CallToolRequest) { + log.Debug(). + Str("tool", req.Params.Name). + Interface("arguments", req.Params.Arguments). + Msg("tool call debug") + + // 验证参数类型 + for key, value := range req.Params.Arguments { + log.Debug(). + Str("param", key). + Str("type", fmt.Sprintf("%T", value)). + Interface("value", value). + Msg("parameter debug") + } +} +``` + +#### 性能问题 + +```go +// 监控工具性能 +func monitorToolPerformance(toolName string, executor func() (*mcp.CallToolResult, error)) (*mcp.CallToolResult, error) { + start := time.Now() + + result, err := executor() + + elapsed := time.Since(start) + log.Info(). + Str("tool", toolName). + Dur("elapsed", elapsed). + Bool("success", err == nil). + Msg("tool performance") + + if elapsed > 5*time.Second { + log.Warn(). + Str("tool", toolName). + Dur("elapsed", elapsed). + Msg("slow tool execution") + } + + return result, err +} +``` + +## 参考资料 + +- [Model Context Protocol 规范](https://modelcontextprotocol.io/docs/) +- [MCP Go 实现](https://github.com/mark3labs/mcp-go) +- [HttpRunner UIXT MCP 服务器文档](mcp_server.md) \ No newline at end of file diff --git a/docs/uixt/operations.md b/docs/uixt/operations.md new file mode 100644 index 00000000..157b6637 --- /dev/null +++ b/docs/uixt/operations.md @@ -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/) \ No newline at end of file diff --git a/docs/uixt/options.md b/docs/uixt/options.md new file mode 100644 index 00000000..b5712a45 --- /dev/null +++ b/docs/uixt/options.md @@ -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) \ No newline at end of file diff --git a/docs/uixt/ui_mark.md b/docs/uixt/ui-mark.md similarity index 100% rename from docs/uixt/ui_mark.md rename to docs/uixt/ui-mark.md diff --git a/internal/version/VERSION b/internal/version/VERSION index 287ad371..0588426c 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-beta-2506111218 +v5.0.0-beta-2506111457 diff --git a/uixt/README.md b/uixt/README.md deleted file mode 100644 index 4007eee6..00000000 --- a/uixt/README.md +++ /dev/null @@ -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 diff --git a/uixt/ai/querier.md b/uixt/ai/querier.md deleted file mode 100644 index 42e9cae1..00000000 --- a/uixt/ai/querier.md +++ /dev/null @@ -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 模型使用不同的响应格式处理 \ No newline at end of file