视觉断言
当传统 UI 树(dump_ui / findElement)抓不到元素(Canvas、图片、游戏、WebView、自绘控件),或者你要判断的是"看起来对不对"而不是"有没有某个节点",用视觉断言。
Tapilot 提供两条路径:
assertVisualDSL action — Gemini 多模态模型读一张截图 + 你的 prompt,返回 pass/fail + 原因analyze_screenMCP tool — 返回结构化分析(元素列表 / 文字块 / 异常标记)
DSL:assertVisual
yaml
- saveScreenshot: login-page
- assertVisual: "页面显示了登录表单(邮箱输入框、密码输入框、登录按钮)"
- assertVisual:
prompt: "验证码倒计时显示为 59 秒"
model: gemini-2.5-pro # 可选,默认 gemini-2.5-flash机制:
- 截图(优先用刚才 saveScreenshot 存的,否则即时截)
- 构造请求:
{ image, prompt: "..." }给 Gemini - 要求 Gemini 返回
{ pass: boolean, reason: string } pass: false则 step 失败,reason 写进run.md
适合 / 不适合
| 场景 | 适合度 |
|---|---|
| Canvas 渲染(游戏、地图、图表) | ✅ 主场 |
| 图片/广告内容判断 | ✅ |
| 布局/样式是否对(颜色、对齐) | ✅ |
| 动画完成判断 | ✅(配合 waitStable 更稳) |
| 简单文字存在 / 按钮可点 | ❌ 用 assert / assertVisible / assertClickable 更快更准 |
| 性能指标 / 元素数量 | ❌ 用 metrics / assertCount |
规则:能用传统断言就用传统,视觉断言贵(Gemini API 调用)+ 慢(~2-5 秒/次)+ 偶尔误判。仅视觉才能判断的情况才上。
MCP:analyze_screen
yaml
# MCP 调用伪代码
analyze_screen({
serial: "<device>",
hints: "找登录按钮、输入框、错误提示",
focus: "interactive" # elements | text | interactive | layout
})返回 JSON:
json
{
"elements": [
{ "type": "button", "label": "登录", "x": 540, "y": 1800, "confidence": 0.95 },
{ "type": "input", "placeholder": "邮箱", "x": 540, "y": 1200 }
],
"text_blocks": [...],
"anomalies": []
}Claude 拿 JSON(~500 token)决定下一步,不需要自己看图。
其他 AI 视觉工具
| Tool | 用途 |
|---|---|
ocr | 纯文字识别(Gemini OCR) |
get_elements | 结构化元素列表(比 dump_ui 精简) |
detect_anomaly | 专查 UI 异常(错位/空白/报错) |
compare_screens | 前后两屏对比找差异 |
smart_tap | 描述元素让 AI 自己找坐标点(比 find_element 强) |
配置
需要 Gemini API key:
bash
# 在仓库根目录 .env.local
GEMINI_API_KEY=your-key或在桌面应用的「账号」页填。
价格和速度
Gemini 2.5 Flash:
- 约 $0.075 / 百万输入 tokens,一张 screenshot ≈ 400-1000 token
- 单次
assertVisual调用 ~1500 token(含 prompt + 图)≈ $0.0001 - 响应时间 1-3 秒
Gemini 2.5 Pro:
- 贵 10x,但复杂场景(多元素/细节判断)更准
- 响应 3-8 秒
- 仅在 Flash 判断不稳时才切
典型用法
配合 waitStable 确保画面稳定
yaml
- tap: 加载
- waitStable: { timeoutMs: 10000 } # 等画面不变
- assertVisual: "加载 spinner 已消失,内容区出现 3 张卡片"失败时附带 reason
run.md 里会记录:
❌ step 5: assertVisual 失败 [gemini-2.5-flash]:'页面显示了登录表单'
— 页面实际显示注册表单(有昵称字段),不是登录表单方便人看原因,定位快。