# UI 自动化插件设计方案 > 插件名称建议:手机 UI 自动化 > 插件 ID 建议:`mobile-ui-auto` > 核心技术栈:JavaFX 插件壳 + Python Runner + Airtest + Poco(pocoui) + ADB > 目标:在 WorkUtils 中提供一个可视化入口,用于连接 Android 手机,对指定 APP 执行自动点击、输入、滑动、断言、截图和流程测试。 ## 1. 设计结论 本插件不建议在 Java 里直接实现手机 UI 自动化细节,而是采用“双层架构”: ```text WorkUtils Java 插件层 负责:可视化配置、脚本管理、流程编排、进度展示、报告入口、进程控制 Python 自动化执行层 负责:连接设备、启动 APP、查找元素/图片、点击输入、断言、截图、生成运行结果 ``` 选择理由: - `pocoui/Poco` 和 `Airtest` 原生就是 Python 生态,直接用 Python 写动作最顺。 - Java 项目已有插件框架,适合做工具入口、配置管理、运行控制和报告汇总。 - 手机自动化经常依赖 ADB、截图、图片模板、设备状态,这些动作放在独立 Python 进程中更容易隔离异常。 - 后续如果要加 `uiautomator2`、Appium 或云真机,可以扩展 Python Runner,不破坏 Java 插件壳。 ## 2. 能力边界 ### 2.1 首期能力 | 能力 | 说明 | |------|------| | 设备检测 | 通过 ADB 列出已连接设备,展示 serial、状态、系统版本 | | APP 启动 | 输入 package/activity 或仅 package,启动被测 APP | | 自动点击 | 支持元素定位点击、图片定位点击、坐标点击 | | 自动输入 | 支持输入文本、清空输入框、粘贴式输入 | | 手势模拟 | 支持滑动、返回、Home、等待、按键事件 | | 元素定位 | 优先 Poco 元素定位,必要时使用图片识别兜底 | | 流程脚本 | 支持 YAML/JSON 步骤流,自动转 Python 执行 | | Python 脚本 | 支持直接运行 `.py` 或 `.air` 脚本 | | 运行日志 | Java UI 实时显示 Python stdout/stderr | | 截图报告 | 每个关键步骤可保存截图,失败时自动截图 | | 断言 | 支持元素存在、图片存在、文本存在、页面等待 | ### 2.2 非首期能力 | 能力 | 放到后续原因 | |------|--------------| | iOS 自动化 | 依赖环境复杂,首期聚焦 Android | | 分布式设备池 | 需要设备调度、队列和并发报告模型 | | 录制回放 | 可以实现,建议第二阶段加入;首期先保证脚本执行闭环 | | 智能探索测试 | 需要页面状态建模和策略引擎 | | Appium 兼容 | 跨平台价值高,但启动成本比 Poco 高 | ## 3. 推荐技术组合 ### 3.1 主链路 ```text Python + Airtest + Poco(pocoui) + ADB ``` - Airtest:设备连接、截图、图像识别、触控、断言、报告。 - Poco(pocoui):UI 树定位、元素点击、输入、等待。 - ADB:设备列表、安装 APK、启动 APP、logcat、底层命令。 - AirtestIDE:作为辅助工具,用于新手录制/定位图片/生成初版脚本。 ### 3.2 兜底链路 ```text uiautomator2 ``` 首期只做预留,不默认引入。适合 Poco 在部分原生控件上拿不到稳定 UI 层级时,作为元素定位增强。 ## 4. 总体架构 ```mermaid flowchart LR A["MainWindow / PluginManager"] --> B["MobileUiAutomationPlugin"] B --> C["MobileUiAutomationWindow"] B --> D["MobileAutomationService"] D --> E["PythonEnvManager"] D --> F["DeviceService"] D --> G["CaseService"] D --> H["PythonRunner"] H --> I["runner.py"] I --> J["Airtest API"] I --> K["Poco AndroidUiautomationPoco"] I --> L["ADB"] J --> M["Android Device"] K --> M L --> M I --> N["reports / screenshots / result.json"] H --> O["实时日志回调"] O --> C ``` ## 5. Java 插件层设计 ### 5.1 包结构 ```text src/main/java/com/example/workutils/biz/mobileuiauto/ MobileUiAutomationPlugin.java MobileUiAutomationWindow.java service/ MobileAutomationService.java PythonEnvManager.java DeviceService.java CaseService.java PythonRunner.java model/ DeviceInfo.java AutomationCase.java AutomationRunConfig.java AutomationRunResult.java AutomationStep.java ``` ### 5.2 资源和脚本结构 ```text src/main/resources/mobile-ui-auto/ python/ runner.py requirements.txt flow_executor.py action_library.py templates/ sample-login-flow.yaml sample-case.py ``` 运行时目录: ```text data/mobile-ui-auto/ config.json cases/ login-flow.yaml smoke-test.py images/ login_button.png home_tab.png reports/ 20260703-153000/ result.json run.log screenshots/ airtest-report/ ``` ### 5.3 插件描述 ```java private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder() .id("mobile-ui-auto") .name("手机UI自动化") .version("1.0.0") .description("基于 Python + Airtest + Poco + ADB 的手机 APP 流程自动化测试工具") .order(80) .icon("📱") .build(); ``` 项目里部分中文注释目前存在编码显示异常,实际落地时建议统一以 UTF-8 保存源码和文档,避免 UI 文案乱码。 ## 6. JavaFX 界面设计 插件采用嵌入式 JavaFX 面板,分为 5 个区域: ```text ┌──────────────────────────────────────────────────────────────┐ │ 顶部工具栏:设备刷新 | Python环境检查 | 打开用例目录 | 打开报告目录 │ ├───────────────┬──────────────────────────────────────────────┤ │ 左侧设备区 │ 右侧执行区 │ │ - 设备列表 │ - 被测APP package/activity │ │ - 当前设备 │ - 用例选择 YAML/PY/.air │ │ - 连接状态 │ - 运行参数:超时、失败截图、报告开关 │ ├───────────────┼──────────────────────────────────────────────┤ │ 用例列表 │ 步骤预览 / 脚本预览 │ ├───────────────┴──────────────────────────────────────────────┤ │ 底部日志区:实时日志、进度、失败原因、报告链接 │ └──────────────────────────────────────────────────────────────┘ ``` ### 6.1 页面控件 | 区域 | 控件 | |------|------| | 设备区 | `ComboBox`、刷新按钮、设备状态标签 | | APP 配置 | package 输入框、activity 输入框、启动 APP 按钮 | | 用例区 | 用例表格、新建/导入/删除/复制按钮 | | 执行区 | 运行、停止、保存配置、打开报告按钮 | | 日志区 | `TextArea` 实时追加日志,错误行高亮 | ### 6.2 用户流程 ```text 1. 打开“手机UI自动化”插件 2. 点击“检查环境” 3. 连接手机并打开 USB 调试 4. 点击“刷新设备” 5. 选择设备 6. 填写或选择 APP package 7. 选择流程用例 8. 点击“运行” 9. 查看实时日志、截图、结果 10. 失败时打开报告定位问题 ``` ## 7. Python 执行层设计 ### 7.1 requirements.txt ```text airtest pocoui pyyaml ``` 说明: - `airtest` 提供设备连接、点击、滑动、截图、图片断言、报告能力。 - `pocoui` 提供 Poco UI 层级定位能力。 - `pyyaml` 用于读取流程式 YAML 用例。 ### 7.2 runner.py 入参 Java 通过 `ProcessBuilder` 调用 Python: ```text python runner.py --device Android:/// --package com.xxx.app --activity .MainActivity --case data/mobile-ui-auto/cases/login-flow.yaml --report data/mobile-ui-auto/reports/20260703-153000 --timeout 20 ``` ### 7.3 runner.py 输出协议 Python 进程按行输出 JSON,Java 端解析后更新 UI: ```json {"type":"log","level":"INFO","message":"连接设备成功"} {"type":"step_start","index":1,"name":"点击登录按钮"} {"type":"screenshot","path":"reports/xxx/screenshots/001.png"} {"type":"step_pass","index":1,"elapsedMs":532} {"type":"step_fail","index":2,"message":"元素不存在: username"} {"type":"done","success":false,"reportPath":"reports/xxx/result.json"} ``` 如果输出不是 JSON,则按普通日志展示,避免第三方库日志破坏执行。 ## 8. 流程用例 DSL 设计 首期建议用 YAML 描述流程,便于非 Python 用户维护。 ### 8.1 示例:登录流程 ```yaml name: login-smoke app: package: com.example.app activity: .MainActivity device: connect: Android:/// settings: timeout: 20 screenshotOnFail: true steps: - name: 等待首页 action: wait target: type: poco query: text: 首页 timeout: 15 - name: 点击我的 action: tap target: type: poco query: text: 我的 - name: 输入用户名 action: input target: type: poco query: name: username_input text: test_user - name: 输入密码 action: input target: type: poco query: name: password_input text: "123456" - name: 点击登录 action: tap target: type: image path: images/login_button.png - name: 断言登录成功 action: assert_exists target: type: poco query: text: 退出登录 ``` ### 8.2 支持动作 | action | 说明 | target | |--------|------|--------| | `start_app` | 启动 APP | package/activity | | `stop_app` | 停止 APP | package | | `tap` | 点击 | poco/image/coord | | `double_tap` | 双击 | poco/image/coord | | `input` | 输入文本 | poco | | `clear` | 清空输入框 | poco | | `swipe` | 滑动 | coord/image | | `wait` | 等待元素出现 | poco/image | | `sleep` | 固定等待 | seconds | | `keyevent` | Android 按键 | BACK/HOME/ENTER | | `screenshot` | 截图 | path | | `assert_exists` | 断言存在 | poco/image | | `assert_not_exists` | 断言不存在 | poco/image | ### 8.3 定位方式 ```yaml target: type: poco query: text: 登录 ``` ```yaml target: type: poco query: name: com.example:id/login ``` ```yaml target: type: image path: images/login_button.png threshold: 0.85 ``` ```yaml target: type: coord x: 540 y: 1600 ``` 推荐优先级: ```text poco 元素定位 > 图片定位 > 坐标定位 ``` 坐标定位只做兜底,因为不同分辨率、字体缩放和设备比例会导致脚本脆弱。 ## 9. 录制回放设计 可以实现“手动模拟点击后记录下来,后续按记录自动模拟”的能力。推荐把它设计成“录制器 + 定位增强 + YAML 生成 + 回放校验”,而不是只保存点击坐标。 ### 9.1 录制目标 用户在手机上手动操作一遍 APP,插件自动记录: - 点击、长按、滑动、返回、Home、输入文本等事件。 - 每一步操作前后的截图。 - 当前页面 UI 层级信息。 - 被点击元素的 Poco 定位信息。 - 坐标和屏幕分辨率,作为兜底。 - 步骤间等待时间。 录制结束后生成一份可编辑 YAML 用例,后续可直接运行。 ### 9.2 为什么不能只记录坐标 只记录坐标虽然最容易实现,但稳定性最差: - 不同手机分辨率、状态栏高度、导航栏模式会导致坐标偏移。 - APP 页面布局变化后,坐标仍然能点下去,但可能点错位置。 - 弹窗、键盘、网络慢加载会让固定坐标回放失败。 因此每个录制步骤建议保存“多定位策略”: ```yaml - name: 点击登录按钮 action: tap target: preferred: poco poco: text: 登录 name: com.example:id/login_button image: path: images/step_003_login_button.png threshold: 0.85 coord: x: 524 y: 1680 screenWidth: 1080 screenHeight: 2400 waitAfterMs: 500 ``` 回放时优先使用 Poco 元素定位;找不到元素时用图片识别;图片也找不到时才按比例坐标点击。 ### 9.3 录制实现方案 首选实现路径: ```text Java UI 点击“开始录制” -> Python recorder.py 连接 Android 设备 -> 周期性读取屏幕截图和 UI 树 -> 用户在真实手机上操作 -> recorder 捕获操作差异/辅助确认 -> 生成 recorded-flow.yaml ``` Android 上直接无侵入捕获所有触摸事件有权限限制,因此录制有两种可落地模式: | 模式 | 实现方式 | 优点 | 缺点 | |------|----------|------|------| | 辅助录制模式 | Java/Python 显示手机镜像或截图,用户在插件界面点,插件记录点击 | 最容易落地,可准确记录点击坐标和截图 | 不是直接点手机屏幕 | | 真机旁路录制 | 用户点真机,录制器通过截图/UI树变化推断步骤,必要时让用户确认 | 更贴近真实操作 | 自动推断难度更高 | 建议 MVP 采用“辅助录制模式”:插件显示手机当前截图,用户点击截图上的位置,插件把点击转发到手机,并同时记录步骤。这样既能完成真实操作,又能准确生成脚本。 ### 9.4 辅助录制交互 ```text 1. 用户选择设备和 APP 2. 点击“开始录制” 3. 插件实时刷新手机截图 4. 用户在截图上点击、滑动、输入 5. 插件执行对应手机动作 6. 插件记录动作、截图、UI树和定位候选 7. 用户点击“停止录制” 8. 插件生成 YAML 用例 9. 用户点击“试运行”校验录制流程 ``` ### 9.5 录制事件模型 ```json { "index": 3, "action": "tap", "timestamp": 1783071000123, "screen": { "width": 1080, "height": 2400 }, "point": { "x": 524, "y": 1680 }, "pocoCandidates": [ { "text": "登录", "name": "com.example:id/login_button", "bounds": [420, 1600, 660, 1740] } ], "imageTemplate": "images/step_003_login_button.png", "beforeScreenshot": "screenshots/003_before.png", "afterScreenshot": "screenshots/003_after.png", "waitAfterMs": 500 } ``` ### 9.6 Python 录制器能力 `recorder.py` 需要提供: - 获取当前截图。 - 获取当前 Poco UI 树。 - 接收 Java UI 传来的点击/滑动/输入事件。 - 把屏幕坐标转换为设备坐标。 - 执行动作并记录前后截图。 - 根据点击位置匹配最近的 Poco 元素。 - 裁剪点击区域图片,生成图片模板。 - 输出 `recorded-flow.yaml`。 ### 9.7 回放策略 回放每一步时按以下顺序执行: ```text 1. 如果有 Poco 定位,先用 Poco 查找并点击 2. 如果 Poco 失败,使用图片模板识别并点击 3. 如果图片失败,使用按分辨率比例换算后的坐标点击 4. 每步后等待 UI 稳定,必要时对比 afterScreenshot 或断言元素 5. 任一步失败时截图并写入报告 ``` ### 9.8 录制回放验收标准 - 能在插件界面显示当前手机截图。 - 用户点击截图后,手机同步执行点击。 - 插件能记录点击步骤并生成 YAML。 - YAML 能再次运行并完成同样流程。 - 至少支持点击、输入、滑动、返回。 - 失败时能定位到具体步骤并展示截图。 ## 10. Python 核心伪代码 ```python from airtest.core.api import * from poco.drivers.android.uiautomation import AndroidUiautomationPoco def connect(device_uri): auto_setup(__file__, devices=[device_uri]) return AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False) def tap_target(poco, target): if target["type"] == "poco": node = find_poco(poco, target["query"]) node.wait_for_appearance(timeout=target.get("timeout", 10)) node.click() elif target["type"] == "image": touch(Template(target["path"], threshold=target.get("threshold", 0.85))) elif target["type"] == "coord": touch((target["x"], target["y"])) def input_target(poco, target, text): node = find_poco(poco, target["query"]) node.wait_for_appearance(timeout=target.get("timeout", 10)) node.click() text(text) ``` 注意:实际实现里要避免函数名 `text` 与 Airtest API 冲突,可封装为 `airtest_text` 或显式 import。 ## 11. 环境管理设计 ### 11.1 Java 检查项 | 检查项 | 命令 | 失败提示 | |--------|------|----------| | Python | `python --version` | 未安装 Python 或未加入 PATH | | pip | `python -m pip --version` | pip 不可用 | | airtest | `python -c "import airtest"` | 请安装 airtest | | pocoui | `python -c "import poco"` | 请安装 pocoui | | adb | `adb devices` | ADB 不可用或设备未授权 | ### 11.2 自动安装策略 首期建议提供“复制安装命令”,不自动安装: ```text python -m pip install -U airtest pocoui pyyaml ``` 原因: - 公司内网、代理、镜像源差异大。 - 自动安装可能污染用户全局 Python。 - 后续可以加虚拟环境模式:`data/mobile-ui-auto/.venv`。 ## 12. 报告设计 每次执行生成独立目录: ```text reports/20260703-153000/ result.json run.log screenshots/ 001-start.png 002-login-fail.png summary.html ``` `result.json`: ```json { "caseName": "login-smoke", "device": "Android:///xxx", "package": "com.example.app", "success": false, "startTime": "2026-07-03T15:30:00+08:00", "endTime": "2026-07-03T15:30:18+08:00", "steps": [ { "index": 1, "name": "等待首页", "status": "PASS", "elapsedMs": 1200 }, { "index": 2, "name": "点击登录", "status": "FAIL", "message": "图片不存在: images/login_button.png", "screenshot": "screenshots/002-login-fail.png" } ] } ``` ## 13. Java 服务接口草案 ```java public interface MobileAutomationService { EnvironmentCheckResult checkEnvironment(); List listDevices(); List listCases(); AutomationRunHandle runCase(AutomationRunConfig config, AutomationCallback callback); void stopRun(String runId); } ``` ```java public interface AutomationCallback { void onLog(String level, String message); void onStepStart(int index, String name); void onStepPass(int index, long elapsedMs); void onStepFail(int index, String message, String screenshotPath); void onDone(AutomationRunResult result); } ``` ## 14. 进程控制设计 `PythonRunner` 负责启动、读取和终止 Python 进程: - 使用 `ProcessBuilder`,不要拼接 shell 字符串。 - stdout/stderr 各自独立线程读取,避免进程阻塞。 - 支持超时终止。 - 点击停止时先发送中断标记,必要时 `destroyForcibly()`。 - 每次运行生成 `runId`,UI 只接收当前 `runId` 的回调。 ## 15. 异常和稳定性策略 | 场景 | 策略 | |------|------| | 设备未连接 | Java 层运行前阻断 | | 设备未授权 | 提示手机确认 USB 调试授权 | | APP 启动失败 | 记录 package/activity 和 ADB 输出 | | 元素找不到 | 自动截图,失败步骤写入 result.json | | 图片识别失败 | 提示阈值、模板路径、当前截图 | | Python 依赖缺失 | 环境检查页给出安装命令 | | 运行超时 | Java 层终止 Python 进程并标记 TIMEOUT | | 用户停止 | 标记 CANCELED,不算失败 | ## 16. MVP 迭代计划 ### 16.1 第一阶段:跑通闭环 - 新增 `MobileUiAutomationPlugin` - 新增 JavaFX 嵌入式窗口 - 实现环境检查 - 实现设备刷新 - 实现选择 YAML 用例并运行 - Python Runner 支持 `tap/input/wait/assert/screenshot` - 生成 `result.json` 和截图 ### 16.2 第二阶段:可用性增强 - 用例列表管理 - 新建 YAML 模板 - 图片模板目录管理 - 失败报告 HTML - 支持 `.py` 和 `.air` 直接运行 - 支持停止运行 - 支持辅助录制回放:在插件截图面板点击、输入、滑动并生成 YAML ### 16.3 第三阶段:进阶能力 - 自动安装/管理 Python 虚拟环境 - uiautomator2 兜底定位 - 多设备并发 - logcat 采集 - APK 安装/卸载 - 接入 CI 命令行模式 ## 17. 命令行模式设计 插件实现 `getCommandName()`: ```text mobile-ui ``` 命令示例: ```text java -jar workutils.jar mobile-ui run --device Android:///xxx --case data/mobile-ui-auto/cases/login-flow.yaml java -jar workutils.jar mobile-ui devices java -jar workutils.jar mobile-ui check-env ``` 这方便后续接入 Jenkins、GitLab CI 或本地批处理。 ## 18. 与现有项目的集成点 | 集成点 | 设计 | |--------|------| | 插件注册 | `@Component` + `implements Plugin` | | 可视化入口 | 实现 `getVisualFactory()` | | 配置目录 | 复用 `data/`,新增 `data/mobile-ui-auto/` | | 报告目录 | 复用 `export/` 或插件私有 `reports/` | | 日志展示 | 参考现有 GitSyncWindow 的实时日志模式 | | 命令执行 | 参考 GitSyncPlugin 的 `ProcessBuilder` 和输出读取模式 | ## 19. 风险和规避 | 风险 | 规避 | |------|------| | UI 测试脆弱 | 优先 Poco 元素定位,图片和坐标只兜底 | | 录制坐标不稳定 | 录制时同时保存 Poco 候选、图片模板和比例坐标 | | 手机分辨率差异 | 图片模板分设备维护,坐标按比例转换 | | 输入法影响文本输入 | 支持 ADB 输入、剪贴板输入、Poco set_text 多策略 | | 第三方依赖安装困难 | 先做环境检查和命令提示,后做虚拟环境 | | Python 进程卡死 | Java 层超时和强制终止 | | 报告不可读 | 每步结构化结果 + 失败截图 | ## 20. 首期验收标准 - 能在 Java UI 中检测 Python、Airtest、Poco、ADB 环境。 - 能刷新并选择一台 Android 设备。 - 能启动指定 APP。 - 能运行一个 YAML 流程:等待页面、点击、输入、断言。 - 能实时显示运行日志。 - 失败时能自动截图并展示失败原因。 - 能生成 `result.json`。 - 支持用户手动停止运行。 ## 21. 参考资料 - Airtest 官方文档:https://airtest.readthedocs.io/en/latest/ - Airtest GitHub:https://github.com/AirtestProject/Airtest - Poco GitHub:https://github.com/AirtestProject/Poco