自动化
自动化
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 内手动配置三步:
- 设置 → 通用 → 键盘 → 添加新键盘 → VPhoneKeyboard;
- VPhoneKeyboard → 打开"允许完全访问"(否则无法连 App Group);
- 输入时长按 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 在线