自动化

用项目内 Python 自动化框架对虚拟 iPhone 做非侵入式 E2E 操作——venv 约定、vphone-auto CLI、flow 编排与会话编程。

自动化

automation/ 是宿主侧 Python 自动化包:经 HostControl Unix socket(或远程 agent HTTP 代理)对运行中的虚拟 iPhone 做非侵入式 E2E 自动化——截图 + 视觉感知定位 + 触摸/硬件键/文本注入 + 轮询验证闭环 + flow 编排(步骤/断言/重试/控制流/watchers/多 VM 并行)。不注入被测 App、不依赖 accessibility 接口,所有定位基于截图像素空间。

flow 库(~/.vphone/flows/)与 run 历史(~/.vphone/flow-runs/history.jsonl)由本包与 Manager GUI(Automation tab)双端共享。

架构

  • HostControl 协议:Unix domain socket,一行 JSON 请求一行 JSON 响应,默认 35s 超时,响应上限 4 MiB。
  • socket 路径解析顺序:显式参数 → VPHONE_HOST_CONTROL_SOCKET → vm/vphone.sock。
  • 远程 VM 自动化:传 --agent/--vm-name/--token(或环境变量)后所有命令经节点 agent /api/v1/vms/{name}/host-control 路由,与本地同构。
  • 坐标系统一为 1290×2796 像素空间(Session 以 screen_info 实际尺寸为准),所有识别通道输出直接喂给 tap,调用方无需处理 Retina 缩放或 Y 轴翻转。

venv 约定(重要)

一律使用项目 venv,禁止往全局 Python 装依赖。

source .venv/bin/activate

.venv 由 make setup_venv 创建,依赖固定在 requirements.txt。感知依赖按需补充:OCR 需 PyObjC(pyobjc-framework-Vision),模板/AKAZE 匹配需 opencv-python;缺失时对应通道降级为不可用而非崩溃。

快速上手

# 截图与触摸
vphone-auto screenshot-full /tmp/screen.png
vphone-auto tap 645 400
vphone-auto swipe 645 2000 645 800 --ms 300
vphone-auto key home

# 应用与文本
vphone-auto list                          # 应用列表
vphone-auto launch com.apple.Preferences
vphone-auto type "你好世界 🎉"             # Unicode 需键盘扩展
vphone-auto type "Hello" --field-x 645 --field-y 400

# 感知
vphone-auto perceive --source ocr
vphone-auto perceive --find "Settings"

# 网络观测
vphone-auto status
vphone-auto flows

全局选项:--socket/-s、--json 机器可读输出、--verbose/-v。

感知与定位

识别通道按成本从轻到重级联执行,有结果即融合返回:

通道 机制 默认
Template OpenCV TM_CCOEFF_NORMED 模板匹配,先原始尺度后多尺度 开
AKAZE AKAZE 特征 + BFMatcher + Homography(RANSAC) 开
OCR macOS Vision VNRecognizeTextRequest(PyObjC),中日韩英 开
YOLO / Grounding DINO / OmniParser 深度学习检测(ultralytics / HF / 两阶段) 关(enable_heavy_models)

perceive() 先跑全部轻量通道,有结果即融合返回;为空才按序升级重量通道,某重量通道成功即停。

UIElement 携带像素 bbox、center、confidence 与归一化 bbox_norm(0–1,跨分辨率),Executor 优先按 bbox_norm 中心换算当前像素。

模板自动演进

重量通道命中后,TemplateEvolver.evolve() 裁剪元素区域存 PNG 到 automation/perception/templates/auto/<前台 App 命名空间>/,运行时注册进 Template/AKAZE 匹配器——下次同元素由轻量通道直接命中。

会话编程

在 Python 脚本里用 Session 组合感知、执行、验证三步闭环:

from automation.controller.session import Session

session = Session()
session.key("home")
session.verifier.wait_for_stable(frames=3)      # 连续 3 帧相似判稳
if session.find_and_tap(text="Settings"):
    session.verifier.wait_for_text("Wi-Fi", timeout=10)
element = session.find("搜索")
if element:
    session.tap(element.x, element.y)
    session.type_text("Wi-Fi")
session.account_switch_and_wait("com.example.app", "user2")
session.perceive()
session.show_overlay()   # 把识别结果画到 VM 窗口上调试
session.close()

Verifier 用轮询替代固定 time.sleep():wait_for_text / wait_for_stable / wait_for_app / wait_for_element,超时返回 False 由调用方决定重试。

录制回放与锚点

三层录制

  • 原始录制(HostControl record_start):宿主侧 JSONL,白名单命令(tap/swipe/key/type)。
  • 流式录制(宿主 recordStreamTouch):Console 实时触摸(down/move/up 相位流)也进录制,聚合为语义动作(tap/long_press/swipe)。
  • 锚点录制(Python Session 的 start_recording/stop_recording):为 tap/swipe 附带命中元素的 text/element_type/bbox/bbox_norm。

锚点智能回放

# 录制(带锚点)
vphone-auto record start --anchors
vphone-auto record stop

# 回放动作管线
vphone-auto script --file actions.json
vphone-auto script --file actions.json --smart    # 锚点智能回放

回放时先按锚点 find() 重定位:命中则用当前中心(swipe 保持向量平移),未命中回退录制坐标。锚点支持 timeout(超时前轮询)、policy(prefer 超时回退 / required 超时步骤失败)、threshold(模板置信度下限)。

模板锚点

OCR 定位不到的图标/图片按钮,用截图框选存为模板图(~/.vphone/flows/templates/<flow>/<name>.png),回放时 TemplateMatcher(OpenCV NCC,多尺度)在当前屏幕找到该区域,点击落在九宫格点位(小键盘布局:1=左下 … 5=中心 … 9=右上;也接受 nw/n/ne/… 别名)。典型用法:返回箭头做模板点 3、列表行做模板点 6。

flow 编排

flow 是带元数据的步骤序列(JSON 文档),在裸动作管线之上补齐编排能力:

控制流

  • exists(探测):单目标轮询探测,结果记入 run context,不连坐 run 失败——「探测未登录 → 跳去登录」的正常分流路径不应被误判失败。
  • wait_any(多目标等待):按序探测,首个命中即过;超时按断言语义失败,弹窗分流/多分支落地页的核心原语。
  • jump_if(条件跳转):{"probe": "check", "not": true, "goto": "do_login"}——引用 exists 步骤结果或内联查找,goto 跳到目标步骤 id(可后跳成循环,步数预算兜底)。
  • watchers(flow 级前置钩子):每步执行前做一次轻量匹配(text → OCR、template → 模板匹配),命中即执行 action(如「弹窗出现了就点它」),不进步骤列表,不影响 run 状态。

多 VM 并行

vphone-auto flow run-batch <name> -s <socket> -s <socket> ... [--workers N]

同一 flow 在多个 VM 上并行执行,汇总通过率与锚点命中。

定时触发

vphone-auto flow schedule <name> --every 30m | --cron '*/5 * * * *' [--max-runs N]

间隔或五字段 cron 定时触发,Ctrl-C 停止。

命令族

命令 用途
flow run <name> [-p k=v …] [--evidence-dir] 单 VM 运行,失败步骤自动截图留证,退出码 0/1
flow run-batch <name> -s <socket> … [--workers N] 多 VM 并行,汇总
flow schedule <name> --every 30m | --cron '…' 定时触发
flow list / show / save / delete 库管理
flow history [--flow f] / stats run 历史与聚合
templates list / prune 演进模板库管理

Python 示例

from automation.flow.models import Flow, FlowStep
from automation.flow.runner import FlowRunner
from automation.controller.session import Session

flow = Flow(
    id="demo",
    steps=[
        FlowStep(id="1", action="key", params={"name": "home"}),
        FlowStep(id="2", action="app_launch", params={"bundle_id": "com.apple.Preferences"}),
        FlowStep(id="3", action="wait_text", params={"text": "Wi-Fi", "timeout": 10}),
        FlowStep(id="4", action="exists", params={"text": "登录", "timeout": 3}),
        FlowStep(id="5", action="jump_if", params={"probe": "4", "not": True, "goto": "7"}),
        FlowStep(id="6", action="tap", params={"x": 400, "y": 500}),
        FlowStep(id="7", action="delay", params={"ms": 1000}),
    ],
    watchers=[{"when": {"text": "允许"}, "action": "tap", "max_fires": 3}],
)
session = Session()
runner = FlowRunner(session, flow)
result = runner.run()
print(result.status, result.step_details)

Unicode 文本输入(键盘扩展)

iOS 虚拟机无物理键盘,Host 端 HID 不支持中文/Emoji。文本输入走自定义键盘扩展:HostControl type → vsock → vphoned keyboard broker → App Group UDS → 键盘扩展 insertText()。所有非 less 变体默认安装(--no-keyboard 可关)。

首次使用需在 guest 内手动配置三步:

  1. 设置 → 通用 → 键盘 → 添加新键盘 → VPhoneKeyboard;
  2. VPhoneKeyboard → 打开"允许完全访问"(否则无法连 App Group);
  3. 输入时长按 globe 键切换到 VPhoneKeyboard。

与其他模块的关系

模块 交互
HostControl socket JSON-line UDS 服务端,本模块是主要消费方
VM 运行时 tap/swipe 最终由 VPhoneVirtualMachineView 合成鼠标事件注入
vphoned app_*/type/HID 键等 capability 的 guest daemon
CFW installer 变体安装器跨变体构建并部署 VPhoneKeyboard.app 到客体
账号 Profile 切换 account_* 命令的设计细节

测试

source .venv/bin/activate
python -m pytest automation/tests/          # 单元测试(mock socket)
python -m pytest automation/tests/ --e2e    # e2e,需 VM socket 在线