Skip to content

进阶篇

掌握变量、条件、循环、分支,写出灵活的自动化流程。


变量与环境变量 env / defineVariables

在配置区定义(env)

yaml
appId: com.example.app
env:
  USERNAME: admin
  PASSWORD: "123456"
  API_URL: "https://api.example.com"
---
- inputText: "${USERNAME}"      # 使用变量

在命令区定义(defineVariables)

yaml
- defineVariables:
    counter: "0"
    greeting: "你好"
    
- inputText: "${greeting}"      # 会输入:你好

区别

envdefineVariables
位置配置区(--- 上方)命令区(--- 下方)
时机流程开始前就生效执行到这一行时生效
用途全局配置(账号密码等)流程中动态定义变量

模板表达式 $

在任何命令参数中使用 ${...} 来引用变量或执行 JavaScript 表达式。

引用变量

yaml
- inputText: "${USERNAME}"

数学运算

yaml
- sleep: ${Math.random() * 5000}         # 随机 0~5 秒

字符串拼接

yaml
- evalScript: "${fullName = firstName + ' ' + lastName}"
- inputText: "${fullName}"

条件表达式

yaml
- assertTrue: "${counter > 0}"

求值时机

${...} 在命令执行时才求值,但有一个重要规则:

  • repeatcommandsbranch 内部的表达式不会被提前计算
  • 它们在每次执行到的时候才计算,所以 ${Math.random()} 每次循环都能得到不同的随机值
yaml
- repeat:
    times: "3"
    commands:
      # 每次循环都会重新求值 Math.random(),得到不同随机数
      - sleep: ${Math.random() * 5000}

转义 ${}

如果你需要输入字面意义的 ${...},在前面加反斜杠:

yaml
- inputText: "价格是 \\${amount} 元"     # 实际输入:价格是 ${amount} 元

元素选择器详解

前面用的 tapOn: "登录" 只是最简单的文字匹配。选择器支持非常多的匹配方式。

文字匹配 text

yaml
- tapOn:
    text: "登录"                # 文字匹配(不区分大小写)

正则匹配

yaml
- tapOn:
    text: "登录.*"              # 匹配以"登录"开头的任何文字
    
- tapOn:
    text: "用户名|Username"     # 匹配"用户名"或"Username"

ID 匹配

yaml
- tapOn:
    id: "btn_login"             # 通过资源 ID 匹配

索引 index

当屏幕上有多个同名元素时,用 index 指定第几个:

yaml
- tapOn:
    text: "删除"
    index: 0                    # 第一个"删除"按钮(从 0 开始)
    
- tapOn:
    text: "删除"
    index: -1                   # 最后一个"删除"按钮

状态匹配

yaml
- tapOn:
    id: "checkbox"
    enabled: true               # 只匹配可用的元素
    checked: false              # 只匹配未勾选的元素

支持的状态:enabledselectedcheckedfocused

空间关系(相对位置)

当一个元素自身没有独特标识时,可以通过它和另一个元素的空间关系来定位。

yaml
# 点击"用户名"下方的输入框
- tapOn:
    below: { text: "用户名" }

# 点击"密码"标签右边的输入框
- tapOn:
    rightOf: { text: "密码" }

四个方向:belowaboveleftOfrightOf

层级关系

yaml
# 点击 container 容器内部的按钮
- tapOn:
    text: "确定"
    childOf: { id: "dialog_container" }

# 点击包含"VIP"子元素的卡片
- tapOn:
    containsChild: { text: "VIP" }

# 点击同时包含多个子元素的容器
- tapOn:
    containsDescendants:
      - text: "标题"
      - text: "副标题"

尺寸匹配

yaml
- tapOn:
    width: 200
    height: 100
    tolerance: 5                # 允许 ±5 像素误差

元素特性 traits

按元素的可交互特性来匹配,多个特性用空格分隔:

yaml
- tapOn:
    traits: "clickable enabled"  # 匹配可点击且可用的元素

optional 标记

yaml
- tapOn:
    text: "跳过广告"
    optional: true              # 找不到就跳过,不报错

label 标记(日志可读性)

yaml
- tapOn:
    id: "btn_submit"
    label: "点击提交按钮"        # 日志中会显示这个描述

完整示例

yaml
- tapOn:
    text: "确定"
    below: { text: "您确定要删除吗?" }
    childOf: { id: "confirm_dialog" }
    enabled: true
    optional: true
    label: "点击确认删除按钮"

长按 longPressOn

长按某个元素。

yaml
- longPressOn: "设置"

- longPressOn:
    text: "文件.txt"
    waitToSettleTimeoutMs: 3000

双击 doubleTapOn

双击某个元素。

yaml
- doubleTapOn: "图片"

- doubleTapOn:
    text: "收藏"
    delay: 250                  # 两次点击之间的间隔(默认 100 毫秒)
    waitToSettleTimeoutMs: 3000 # 双击后等待界面稳定,最多等 3 秒

滚动查找 scrollUntilVisible

不停滚动页面,直到目标元素出现为止。

yaml
- scrollUntilVisible:
    element:
      text: "关于本机"          # 要找的元素
    direction: DOWN             # 滚动方向
    timeout: "30000"            # 最多滚动 30 秒

所有参数

yaml
- scrollUntilVisible:
    element:                    # 必填:要找的元素选择器
      text: "目标元素"
    direction: DOWN             # 方向:UP / DOWN / LEFT / RIGHT,默认 DOWN
    timeout: "20000"            # 超时:毫秒,默认 20000
    speed: "40"                 # 速度:0(最慢)~ 100(最快),默认 40
    visibilityPercentage: 100   # 元素可见比例 0~100,默认 100
    centerElement: false        # 是否尝试将元素滚动到屏幕中央,默认 false
    from:                       # 选填:在指定容器内滚动(见高级篇-容器内滚动)
      id: "list_container"
    waitToSettleTimeoutMs: 3000 # 选填:每次滚动后等待界面稳定的超时

实际例子:在设置中找到"关于本机"

yaml
- launchApp
- scrollUntilVisible:
    element:
      text: "关于本机"
    direction: DOWN
    timeout: "30000"
- tapOn: "关于本机"

条件执行 when

给任何命令添加 when 条件——只有条件成立时才执行。

元素可见时执行

yaml
- tapOn:
    text: "跳过"
    when:
      visible: { text: "广告" }  # 只有屏幕上有"广告"时,才点击"跳过"

元素不可见时执行

yaml
- tapOn:
    text: "登录"
    when:
      notVisible: { text: "欢迎" }  # 没有"欢迎"文字时,才点击"登录"

脚本条件

yaml
- tapOn:
    text: "下一页"
    when:
      true: "${counter < 10}"    # counter 小于 10 时才执行

when 支持的所有字段

字段类型说明
visible选择器元素可见时为 true
notVisible选择器元素不可见时为 true
true字符串JS 表达式结果为真时为 true
label字符串日志标签

多个字段同时写时,全部满足才执行(AND 逻辑)。


概率执行 chance

让命令以一定概率执行——适用于需要随机化操作的测试场景。

yaml
- tapOn:
    text: "跳过"
    optional: true
    chance: 0.3                  # 30% 概率点击跳过
yaml
- tapOn:
    text: "查看详情"
    optional: true
    chance: 0.1                  # 10% 概率查看详情

chance 取值范围 0.0 ~ 1.0:

  • 0.0 = 永远不执行
  • 0.5 = 50% 概率
  • 1.0 = 总是执行

提示chance 通常搭配 optional: true 使用。因为如果概率命中但元素不存在,不加 optional 会报错。


循环 repeat

固定次数循环

yaml
- repeat:
    times: "5"
    commands:
      - scroll
      - sleep: 1000

执行 5 次:滚动 → 等待 1 秒。

按时间循环

yaml
- repeat:
    duration: "60000"            # 循环 60 秒(1 分钟)
    commands:
      - swipe: { direction: UP }
      - sleep: [3000, 8000]

持续执行 1 分钟。

随机时长循环

yaml
- repeat:
    duration: [300000, 600000]   # 随机 5~10 分钟(启动时随机取一个值)
    commands:
      - scroll
      - sleep: [3000, 8000]

注意duration 的数组形式在流程启动时随机选定一个值,整个循环过程使用同一个时长。

次数 + 时间(取先到者)

yaml
- repeat:
    times: "50"                  # 最多 50 次
    duration: "600000"           # 或最多 10 分钟
    commands:
      - scroll

条件循环(while)

yaml
- repeat:
    while:
      visible: { text: "加载中..." }
    commands:
      - sleep: 1000              # 只要"加载中..."还在,就一直等
yaml
- defineVariables:
    counter: "0"
    
- repeat:
    while:
      true: "${counter < 10}"    # counter < 10 时持续循环
    commands:
      - evalScript: "${counter = counter + 1}"
      - tapOn: "下一个"

限制:单个 repeat 最多执行 1000 次循环,防止无限循环。默认最大时长 30 分钟


条件分支 branch

类似编程语言中的 if / else if / else——从上到下检查条件,执行第一个满足条件的分支。

yaml
- branch:
    # if:屏幕上有"从图片库中选择"
    - when: { visible: "从图片库中选择" }
      commands:
        - tapOn: "从图片库中选择"
        
    # else if:屏幕上有"更换照片"
    - when: { visible: "更换照片" }
      commands:
        - tapOn: "更换照片"
        - tapOn: "从图片库中选择"
        
    # else:以上都不满足
    - commands:
        - back

搭配脚本条件

yaml
- branch:
    - when: { true: "${counter > 10}" }
      commands:
        - tapOn: "完成"
    - when: { true: "${counter > 5}" }
      commands:
        - tapOn: "继续"
    - commands:
        - tapOn: "开始"

子流程 runFlow

把一组命令包装成子流程,可以搭配条件、概率使用。

内联命令

yaml
- runFlow:
    commands:
      - tapOn: "设置"
      - tapOn: "账户"
      - back

引用外部文件

yaml
- runFlow: "subflow/common-auth.yaml"

# 或对象形式
- runFlow:
    file: "subflow/common-auth.yaml"

注意filecommands 不能同时使用,二选一。

条件执行的子流程

yaml
- runFlow:
    when:
      visible: { text: "登录" }
    commands:
      - tapOn: "登录"
      - inputText: "${PASSWORD}"
      - tapOn: "确定"

概率执行的子流程

yaml
- runFlow:
    chance: 0.1                  # 10% 概率执行反馈流程
    commands:
      - tapOn: "反馈"
      - inputText: "测试反馈"
      - pressKey: Enter
      - back

子流程带变量

yaml
- runFlow:
    env:
      TOKEN: "${authToken}"
    commands:
      - httpRequest:
          url: "https://api.example.com"
          headers:
            Authorization: "Bearer ${TOKEN}"

异常处理器 exceptionHandlers

自动处理意外弹窗(权限请求、广告、系统提示等)。在配置区定义,全局生效。

工作原理

  1. 某条命令执行失败(比如找不到元素)
  2. 引擎检查当前屏幕是否有匹配异常处理器的弹窗
  3. 如果有,自动点击它来关闭弹窗
  4. 然后重新尝试原来失败的命令

简写形式

yaml
appId: com.example.app
exceptionHandlers:
  - "允许"                       # 自动点击"允许"
  - "我知道了"                    # 自动点击"我知道了"
  - "以后再说"                    # 自动点击"以后再说"
---
- launchApp

对象形式(高级)

yaml
exceptionHandlers:
  - text: "跳过"
    maxTriggerCount: 3           # 最多触发 3 次,之后不再处理

  - id: "close_button"
    below: { text: "广告" }      # 支持空间关系选择器

自定义等待 extendedWaitUntil

等待某个元素出现或消失,可以自由设定超时时间(assertVisible 的超时是固定的 17 秒,这里可以自定义)。

等待元素出现

yaml
- extendedWaitUntil:
    visible:
      text: "加载完成"
    timeout: "30000"             # 最多等 30 秒

等待元素消失

yaml
- extendedWaitUntil:
    notVisible:
      id: "loading_spinner"
    timeout: "15000"             # 等"加载中"消失,最多 15 秒

进阶实战:信息流滑动 2 分钟

yaml
appId: com.example.demo
name: 信息流滑动 2 分钟
---
# 启动 Demo App 并确认进入推荐页
- launchApp
- assertVisible: "推荐"

# 持续滑动 2 分钟
- repeat:
    duration: "120000"
    commands:
      # 向上滑动,切换到下一条内容
      - swipe:
          direction: UP
          duration: [200, 500]
          waitToSettleTimeoutMs: 0

      # 每条内容停留 3~8 秒
      - sleep: [3000, 8000]

# 结束
- stopApp

掌握了流程控制?接下来解锁全部能力 → 高级篇

VMOS Edge 团队出品