Files
workutils/docs/ui-automation-plugin-design.md
T
2026-07-03 16:45:59 +08:00

22 KiB
Raw Blame History

UI 自动化插件设计方案

插件名称建议:手机 UI 自动化
插件 ID 建议:mobile-ui-auto
核心技术栈:JavaFX 插件壳 + Python Runner + Airtest + Poco(pocoui) + ADB
目标:在 WorkUtils 中提供一个可视化入口,用于连接 Android 手机,对指定 APP 执行自动点击、输入、滑动、断言、截图和流程测试。

1. 设计结论

本插件不建议在 Java 里直接实现手机 UI 自动化细节,而是采用“双层架构”:

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 主链路

Python + Airtest + Poco(pocoui) + ADB
  • Airtest:设备连接、截图、图像识别、触控、断言、报告。
  • Poco(pocoui):UI 树定位、元素点击、输入、等待。
  • ADB:设备列表、安装 APK、启动 APP、logcat、底层命令。
  • AirtestIDE:作为辅助工具,用于新手录制/定位图片/生成初版脚本。

3.2 兜底链路

uiautomator2

首期只做预留,不默认引入。适合 Poco 在部分原生控件上拿不到稳定 UI 层级时,作为元素定位增强。

4. 总体架构

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 包结构

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 资源和脚本结构

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

运行时目录:

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 插件描述

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 个区域:

┌──────────────────────────────────────────────────────────────┐
│ 顶部工具栏:设备刷新 | Python环境检查 | 打开用例目录 | 打开报告目录 │
├───────────────┬──────────────────────────────────────────────┤
│ 左侧设备区     │ 右侧执行区                                    │
│ - 设备列表     │ - 被测APP package/activity                    │
│ - 当前设备     │ - 用例选择 YAML/PY/.air                       │
│ - 连接状态     │ - 运行参数:超时、失败截图、报告开关           │
├───────────────┼──────────────────────────────────────────────┤
│ 用例列表       │ 步骤预览 / 脚本预览                            │
├───────────────┴──────────────────────────────────────────────┤
│ 底部日志区:实时日志、进度、失败原因、报告链接                 │
└──────────────────────────────────────────────────────────────┘

6.1 页面控件

区域 控件
设备区 ComboBox<DeviceInfo>、刷新按钮、设备状态标签
APP 配置 package 输入框、activity 输入框、启动 APP 按钮
用例区 用例表格、新建/导入/删除/复制按钮
执行区 运行、停止、保存配置、打开报告按钮
日志区 TextArea 实时追加日志,错误行高亮

6.2 用户流程

1. 打开“手机UI自动化”插件
2. 点击“检查环境”
3. 连接手机并打开 USB 调试
4. 点击“刷新设备”
5. 选择设备
6. 填写或选择 APP package
7. 选择流程用例
8. 点击“运行”
9. 查看实时日志、截图、结果
10. 失败时打开报告定位问题

7. Python 执行层设计

7.1 requirements.txt

airtest
pocoui
pyyaml

说明:

  • airtest 提供设备连接、点击、滑动、截图、图片断言、报告能力。
  • pocoui 提供 Poco UI 层级定位能力。
  • pyyaml 用于读取流程式 YAML 用例。

7.2 runner.py 入参

Java 通过 ProcessBuilder 调用 Python:

python runner.py
  --device Android:///<serial>
  --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:

{"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 示例:登录流程

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 定位方式

target:
  type: poco
  query:
    text: 登录
target:
  type: poco
  query:
    name: com.example:id/login
target:
  type: image
  path: images/login_button.png
  threshold: 0.85
target:
  type: coord
  x: 540
  y: 1600

推荐优先级:

poco 元素定位 > 图片定位 > 坐标定位

坐标定位只做兜底,因为不同分辨率、字体缩放和设备比例会导致脚本脆弱。

9. 录制回放设计

可以实现“手动模拟点击后记录下来,后续按记录自动模拟”的能力。推荐把它设计成“录制器 + 定位增强 + YAML 生成 + 回放校验”,而不是只保存点击坐标。

9.1 录制目标

用户在手机上手动操作一遍 APP,插件自动记录:

  • 点击、长按、滑动、返回、Home、输入文本等事件。
  • 每一步操作前后的截图。
  • 当前页面 UI 层级信息。
  • 被点击元素的 Poco 定位信息。
  • 坐标和屏幕分辨率,作为兜底。
  • 步骤间等待时间。

录制结束后生成一份可编辑 YAML 用例,后续可直接运行。

9.2 为什么不能只记录坐标

只记录坐标虽然最容易实现,但稳定性最差:

  • 不同手机分辨率、状态栏高度、导航栏模式会导致坐标偏移。
  • APP 页面布局变化后,坐标仍然能点下去,但可能点错位置。
  • 弹窗、键盘、网络慢加载会让固定坐标回放失败。

因此每个录制步骤建议保存“多定位策略”:

- 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 录制实现方案

首选实现路径:

Java UI 点击“开始录制”
  -> Python recorder.py 连接 Android 设备
  -> 周期性读取屏幕截图和 UI 树
  -> 用户在真实手机上操作
  -> recorder 捕获操作差异/辅助确认
  -> 生成 recorded-flow.yaml

Android 上直接无侵入捕获所有触摸事件有权限限制,因此录制有两种可落地模式:

模式 实现方式 优点 缺点
辅助录制模式 Java/Python 显示手机镜像或截图,用户在插件界面点,插件记录点击 最容易落地,可准确记录点击坐标和截图 不是直接点手机屏幕
真机旁路录制 用户点真机,录制器通过截图/UI树变化推断步骤,必要时让用户确认 更贴近真实操作 自动推断难度更高

建议 MVP 采用“辅助录制模式”:插件显示手机当前截图,用户点击截图上的位置,插件把点击转发到手机,并同时记录步骤。这样既能完成真实操作,又能准确生成脚本。

9.4 辅助录制交互

1. 用户选择设备和 APP
2. 点击“开始录制”
3. 插件实时刷新手机截图
4. 用户在截图上点击、滑动、输入
5. 插件执行对应手机动作
6. 插件记录动作、截图、UI树和定位候选
7. 用户点击“停止录制”
8. 插件生成 YAML 用例
9. 用户点击“试运行”校验录制流程

9.5 录制事件模型

{
  "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 回放策略

回放每一步时按以下顺序执行:

1. 如果有 Poco 定位,先用 Poco 查找并点击
2. 如果 Poco 失败,使用图片模板识别并点击
3. 如果图片失败,使用按分辨率比例换算后的坐标点击
4. 每步后等待 UI 稳定,必要时对比 afterScreenshot 或断言元素
5. 任一步失败时截图并写入报告

9.8 录制回放验收标准

  • 能在插件界面显示当前手机截图。
  • 用户点击截图后,手机同步执行点击。
  • 插件能记录点击步骤并生成 YAML。
  • YAML 能再次运行并完成同样流程。
  • 至少支持点击、输入、滑动、返回。
  • 失败时能定位到具体步骤并展示截图。

10. 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 自动安装策略

首期建议提供“复制安装命令”,不自动安装:

python -m pip install -U airtest pocoui pyyaml

原因:

  • 公司内网、代理、镜像源差异大。
  • 自动安装可能污染用户全局 Python。
  • 后续可以加虚拟环境模式:data/mobile-ui-auto/.venv。

12. 报告设计

每次执行生成独立目录:

reports/20260703-153000/
  result.json
  run.log
  screenshots/
    001-start.png
    002-login-fail.png
  summary.html

result.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 服务接口草案

public interface MobileAutomationService {
    EnvironmentCheckResult checkEnvironment();

    List<DeviceInfo> listDevices();

    List<AutomationCase> listCases();

    AutomationRunHandle runCase(AutomationRunConfig config, AutomationCallback callback);

    void stopRun(String runId);
}
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():

mobile-ui

命令示例:

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. 参考资料