自动化和无障碍功能集成(AAI)(Total Control 8+)
AAI 将屏幕上的 UI 元素识别为对象,传统的 (x, y) 坐标方式将屏幕视为一个巨大的对象,因此需要使用图像/颜色查找和光学字符识别 (OCR) 来识别屏幕上的对象。
无障碍功能是一种将屏幕上的 UI 元素表示为底层节点的功能,一个节点包含许多属性,如文本/描述、尺寸、布尔属性(可点击、可编辑或可滚动)、底层类名等。文本/描述可以轻松访问(无需使用 OCR),尺寸(和可点击性)确保按钮可以在特定位置被点击,即使节点被移动到另一个位置也能有效。
一个节点可以表示一个 UI 元素(例如按钮)、一组 UI 元素或某些元素的布局。节点(元素、组或布局)可以通过节点 ID(用十六进制字符串表示)进行标识。我们将无障碍功能、TC 脚本框架和 UI Automator 库进行集成,以实现以下目标:
- 与坐标无关使脚本在不同分辨率、多种尺寸和品牌的设备上更具可移植性。
- 同步 API 将等待屏幕重绘,使脚本更简单,无需猜测休眠时间。
- 可以轻松地从应用程序中检索字符串,而无需使用容易出错的 OCR。
AAI 的最简单案例:
- 如果找到屏幕上的 "OK",点击它,远比特定分辨率的 click(100, 100) 更好:devices.clickSync("OK")
- 输入文本到文本输入框,AAI 可以找到当前屏幕上的所有文本输入行。
devices.inputTextSync([位置], "文本") // 输入文本,位置用于多个输入
- 运行或重新启动应用程序,无需查询,它会在屏幕刷新后返回;使用查询,它会在屏幕刷新后匹配查询。
devices.runAppSync(<包名>, [查询])
devices.restartAppSync(<包名>, [查询])
整个屏幕由许多节点组成,一个节点可以是最小的 UI 元素或许多节点的容器,有些节点是不可见的。整个屏幕是从单个根节点开始的树状结构。根据应用程序的复杂程度,一个屏幕可能包含50-300个节点。
由于用户只对节点的一个小子集感兴趣,挑战在于找到用户想要的正确节点并从中提取信息或执行操作。
如何找到节点是一个挑战?我们发明了一种查询语言来查找节点,FindNode 程序安装在每个设备上,查询语言将被执行以获取满足条件的节点,意图是将大量节点减少为一个或少数几个目标节点,用户可以获取信息或对节点应用操作。
例如:Java 中的 UI Automator 提供了 "UiSelector" 和 "BySelector" 在 UiDevice.findObject() 或 findObjects() 中定位节点,对于多个条件可能会很复杂:
new UiSelector().className("android.widget.TextView").text("OK")
我们创建了一个简单的查询语言,它更短且可移植,因为查询将发送到许多设备,上述代码可以用我们的查询语言重写为:
"C: android.widget.TextView&&T:OK"
AAI 项目包括以下内容:
- 查询语言,简单的单行语法语言,用于搜索目标节点。AAI 的核心。
- FindNode 在每个设备上执行查询或操作。所有的查询和某些操作都在 FindNode 中完成,它包含几十个命令。有关更多信息,请参阅FindNode 文档。
- 一对多同步中的对象模式,将节点(或 UI 对象)发送到所有设备,而不是协调,"OK" 的点击可以在具有不同分辨率的所有设备上运行,而不是 click(100,100)。
- UI Explorer 用于获取节点信息,可以直观地测试查询语言,是学习和探索工具。
- AAIS,一种在多个设备上执行自动化的简单语言。捕获和重放生成此语言,有关更多信息,请参阅AAIS 文档。
- REST 和 JS API 包括对 FindNode 的无障碍功能。
- UiElement 类位于 FindNode 之上,方便访问节点。
查询
每个查询包含一个或多个 "<key>:<value>" 对,多个键可以使用 "&&" 作为分隔符添加。
每个节点由一个节点 ID 来标识。查询可以分为三个阶段:
- 模板("TP")。该类别用于"生成"初始节点。例如,"TP:textInput" 将返回一个可编辑文本字段的列表。此类别是必需的,如果未指定,则将使用默认模板。
- 基本查询(BQ)。每个节点包含有关自身的信息,例如类别、文本/描述、属性等。BQ 将逐个匹配节点,拒绝不符合条件的节点,并不会传递到下一个阶段。如果未指定 BQ,则从 TP 生成的节点将传递到 EQ。
- 扩展查询(EQ)。一组通常与多个节点一起使用的键。多个 EQ 从左到右执行,同一个键可以指定多次。EQ 的示例:"OX:1" 查找当前节点右侧的元素/节点。
查询执行后,找到的一个或多个节点将列在"ML"(匹配列表)中,可以在 ML 上应用一系列操作,这些操作可以是获取信息或对 ML 执行操作。
模板:
为 BQ 或 EQ 生成初始节点。
TP:all -所有节点
TP:more -除了以"Layout"结尾的节点外的所有节点
TP:basic -所有叶子节点(子节点数为零)
TP:reduced -优化"TP:more",返回屏幕上重要的节点
TP:anyText[,<min>[,<max>]] -具有特定长度的"text"内容的节点。
TP:anyDescription[,<min>[,<max>]] -具有特定长度的"description"内容的节点。
TP:textInput -从左上到右下排序的所有可编辑字段。
TP:findText,<text> -具有参数中的文本的节点,可以包含 "*" 和 "/…/"。
TP:line,top|bottom,<number> -返回位于可滚动节点之外的前/后节点。
TP:scrollable,<position> -滚动容器内的节点,对于多个可滚动节点,使用位置参数。
基本查询(BQ):
用于获取节点级别信息的查询,从 TP 中的每个节点将与 BQ 中的节点匹配(如果提供)以继续执行。
P:<package name> - 不应使用,默认为正在运行的应用程序
C:<class name> 类名(字符串)
R:<resource ID> 资源 ID(字符串)
D:<text> 描述(字符串)
T:<text> 文本(字符串)
IT:<number> 文本输入类型(整数)
CC:<number> 子节点数量(整数)
ID:<ID> 十六进制格式的节点 ID(字符串)
BI:[x, y] 节点包含坐标 (x,y)
BI:[x1, y1, x2, y2] 矩形范围内的节点,如果 x 或 y 为 -1,则忽略
BP:<prop name> 布尔属性(字符串)
- checkable、checked、clickable、editable、enabled、focused、longClickable
- scrollable、visibleToUser。
TD:<text> 匹配文本或描述(字符串)
扩展查询(EQ):
这里的查询通常涉及多个节点。
扩展查询的顺序很重要,所有的扩展查询从左到右执行。允许具有相同键的命令。
IX:<number> 基于位置从匹配节点列表中获取一个节点
OX:<number> 水平偏移到相邻节点(正数表示向右,负数表示向左)
OY:<number> 垂直偏移到相邻节点(正数表示向下,负数表示向上)
ON:<type> 从匹配节点列表中选择一个节点的不同方法
- first(第一个)、last(最后一个)、min(最小值)
ST:<sort type> 基于节点在屏幕上的位置返回排序后的节点
- x(按水平位置排序)、y(按垂直位置排序)、yx(按水平和垂直位置排序,或者全部排序)
TX 返回与参考节点水平相交的节点。
TY 返回与参考节点垂直相交的节点。
VG:[level number] 从 ML 中的第一个节点返回一个视图组中的一组节点。
RN 从匹配节点列表中返回优化后的节点。
BQ:<query> 执行基本查询。
X:<key in BQ> 以“X”为前缀的基本查询键
对于 BQ,查询语法可以包含 "!" 表示非,">" 和 "<" 表示大于和小于,"*" 表示通配符匹配,"/<regexp>/" 表示正则表达式。它可以匹配包名、类名、资源ID、文本、描述、子节点数和输入类型。
FindNode 已安装在每个设备上(作为 Total Control 应用的一部分),它是唯一能识别查询语法的程序,它解析查询、定位节点并对找到的节点执行操作。FindNode 将复杂的 JavaScript 和 Total Control 的 CPU 利用率卸载到设备上,所有的搜索都在设备上进行。
device.sendAAi() 和 devices.sendAai() 是与 FindNode 直接通信的方式,可以向一个或多个设备发送 JS 对象,发送前会将其转换为 JSON 格式,返回值以 JS 对象格式返回。如果遇到错误,返回值为 null,lastError() 中包含错误消息。
一个简单的查询示例,用于获取型号名称的文本,使用 X 偏移 1(右侧):
>> device.sendAai({query:"T:Model name&&OX:1", action:"getText"})
{retval: 'Galaxy S10+'}
FindNode 甚至可以检测屏幕顶部/底部的固定图标:
>> device.sendAai({query:"TP:line,bottom,-1", action:"getText"})
{retval: ['Chats','Calls','Contacts','Notifications']}
以下 3 个命令都可以点击 "Calls" 文本:
>> device.sendAai({query:"TP:line,bottom,-1&&T:Calls", action:"click"})
{retval: true}
>> device.sendAai({query:"TP:line,bottom,-1&&IX:1", action:"click"})
{retval: true}
>> device.sendAai({query:"TP:line,bottom,-1&&T:Chats&&OX:1", action:"click"})
{retval: true}
点击 "Contacts" 图标:
>> device.sendAai({query:"TP:line,bottom,-1&&T:Contacts&&OY:-1", action:"click"})
{retval: true}
>> device.sendAai({query:"TP:line,bottom,-1&&IX:2", action:"click"})
{retval: true}
// 在屏幕上找到多个 "Contacts",IX:-1 是选择最后找到的节点
>> device.sendAai({query:"T:Contacts&&IX:-1&&OY:-1", action:"click"})
{retval: true}
请阅读 FindNode 用户指南获取完整信息。