FindNode
FindNode 是基于核心 Selector 包的一个 Shell 程序,其目的是找到一个或多个预期的 UI 元素(或无障碍功能节点)并提取信息或对其执行操作。简而言之,AAI 用查询替代了坐标。
FindNode 是 AAI 项目(无障碍功能和自动化集成)的一部分,由以下组件组成:
Selector 位于无障碍功能和 UI Automator 之上,每个 UI 元素或 UI 元素容器由节点或节点集合标识,每个节点都具有在当前屏幕上唯一的 ID。Selector 提供了各种方式来搜索一个或多个节点。一旦获取到节点,就可以进行许多有趣的操作,例如获取文本/图像或执行操作,如点击按钮或在文本字段中输入文本,所有这些都不需要使用坐标。节点的坐标在运行时获取。
例如:
JS API:使用 devices.click("OK") 而不是 devices.click(100, 200)。通过查找文本为 "OK" 的节点,获取坐标并点击它。所有操作在运行时完成。
MDCC:用户在主设备上点击一个按钮,该按钮的唯一查询(例如 "T:OK")将被发送到其他设备,以搜索并点击该节点。旨在提供各种定位节点的方式,而无需使用屏幕坐标,没有页面上/下的操作,但可以使用 "scrollToView" 定位节点,这样,相同的脚本可以在具有不同分辨率和屏幕大小的手机上运行。
FindNode 使用 Appium 的 JSON 结构执行 JSON 命令,JSON 格式为:
{"cmd": "action", "action": "findnode", "params": {…}}
每个 FindNode 命令都在 "params" 对象中执行。
返回值可以有两种类型之一,成功运行:
{"status":0, "value":"{…}"}
或失败:
{"status":13,"value":"{<错误消息>}"}
TC 提供了 "device.sendAai()" 或 "devices.sendAai()" 来与 FindNode 进行通信:
据推测,您可以通过使用 "device.sendAai()" 和 "devices.sendAai()" 来完成大部分任务,FindNode 是 Appium 的 "handler",另一个 "invoke" handler 可以使用 Java 反射访问 UiDevice、UiObject2、AccessibilityNodeInfo 和 InteractionController 中的方法。
限制
AAI 有一些限制:
查询 + 动作
使用我们的查询语言在屏幕上查找节点并对找到的节点执行操作。在本手册中,我们将介绍查询和动作。
例如,要在关于手机屏幕上获取 Android 版本:
>> device.sendAai({query:"TP:basic&&T:Android version&&OY:1", action:"getText"})
{retval: '13'}
要点击一个标签为 "OK" 的按钮:
{query:"T:OK", action:"click"}
要获取所有股票符号:
{query:"R:.ticker", action:"getText"}
查询
大多数 FindNode 的使用都涉及查询,这是 FindNode 的核心部分(也是 AAI 的一部分)。它允许用户通过对 Accessibility 节点进行不同类型的有用查询来定位所需的 UI 元素。整个屏幕由许多节点组成,一个节点可以是最小的 UI 元素或更小节点的容器,其中许多节点是不可见的。整个屏幕是从单个根节点开始的树状结构。将执行查询以获取满足条件的节点,目的是将大量节点减少到一个或少数几个目标节点,用户可以获取节点的信息或应用操作。
我们开发了一种小型查询语言,可以在设备和脚本之间进行传递,并且足够强大,可以定位大多数节点。其格式为 JSON:
{query:"<key>:<value>&&<key>:<value>&&<key>&&<key>…"}
我们接受 "||" 和 "&&" 作为相同的分隔符,将来可以更改。由于查询是 AND 关系,查询需要满足所有条件才能成为 "匹配节点",使用 "&&" 更为合理。FindNode 不提供 OR 关系,但底层的 Selector 包提供了该功能。"templates" 使用该结构。对于某些键,不需要值。
查询字符串分为 3 个部分:模板(template/TP)、基本查询(basic query/BQ)和扩展查询(extended query/EQ)。"模板"(TP)将生成初始节点,"基本查询"(BQ)将逐个从节点属性执行查询,"扩展查询"(EQ)将使用 TP 和 BQ 的输出,并在多个节点之间执行查询。
获取主存储空闲大小:
可以使用 "查询" + "动作":
>> device.sendAai({query:"T:Main storage&&OX:1", action:"getText"})
{retval: '402 GB free'}
也可以使用 "可选查询"(OQ):
>> device.sendAai({action:"getText(T:Main storage&&OX:1)"})
{retval: '402 GB free'}
类似地,要返回上一个屏幕(左箭头),可以使用 OQ:
>> device.sendAai({action:"click(T:Storage Analysis&&OX:-1)"})
{retval: true}
要获取音频大小,可以混合使用不同的查询,并仍然获得相同的结果:
>> device.sendAai({query:"TP:all&&T:Main storage&&VG:2&&XT:Audio&&OX:1", action:"getText"})
{retval: '77 MB'}
解析器将按以下顺序将查询分为 TP、BQ 和 EQ,并执行它们:
模板 (TP)
模板查询(因缺乏更好的名称而命名)是查询的必需键,它生成用于特定目的的节点。如果缺少模板,则默认为"TP:more",不允许使用多个模板,以下是不同的模板:
名称 | 描述 |
---|---|
all | 返回所有节点。 |
more | "all" 选项,去除布局节点。如果未定义模板,则为默认选项。 |
basic | “more” 选项,其子节点计数为零(叶节点)。 |
reduced | 返回“有趣的”节点(参见 UI 探测 的“优化”模式)。 |
findText,<text> | 返回具有特定文本或描述的节点。findText 需要一个额外的参数来指定要搜索的文本。 |
anyText[,min][,max] | 返回具有文本内容的节点,可选择指定长度范围。 |
anyDescription[,min][,max] | 返回具有描述内容的节点,可选择指定长度范围。 |
textInput[,<hint text>] | 返回可“编辑”的节点,按从左上到右下的顺序排序。与 "IX" 一起使用以获取多个文本输入。可选的文本是可用的提示文本。 |
scrollable,[pos] | 返回可滚动的节点,对于多个可滚动区域的屏幕,可以使用 "position" 来标识要匹配的可滚动区域。 |
line,<top|bottom>,<line number> | 返回滚动节点的顶部或底部的节点。 |
基本查询 (BQ)
基本查询是完全基于单个节点信息的查询。BQ不是必需的,如果未定义BQ,将把所有来自模板的节点传递给EQ。
以下是基本查询,用于匹配从节点本身获取的信息:
名称 | 描述 |
---|---|
P:<package name> | 包名(字符串) |
C:<class name> | 类名(字符串) |
R:<resource ID> | 资源ID(字符串) |
D:<text> | 内容描述(字符串) |
T:<text> | 文本(字符串) |
IT:<number> | 文本输入类型(整数) |
CC:<number> | 子节点数量(整数) |
ID:<hex string> | 节点ID(字符串) |
BI:[x, y] | 返回包含点 (x,y) 的节点 |
BI:[x1, y1, x2, y2] | 返回被矩形包围的节点,-1 表示忽略 x 或 y 坐标 |
BP:<prop name> | 返回与布尔属性匹配的节点 |
TD:<text> | 匹配文本或描述 |
HT:<text> | 匹配提示文本(如果不支持,则匹配 getText()) |
由于查询始终在屏幕上的现有应用程序上执行,不应使用 "P"。"(S)"代表字符串,"I"代表整数,"[x, y]"表示位置,"[x1, y1, x2, y2]"表示矩形(或边界)。I/S可以接受整数和字符串。字符串更加强大。
键不区分大小写,我们使用大写字母来区分键和值。
扩展查询 (EQ)
以下是扩展查询的键,通常适用于由BQ或模板返回的多个节点。扩展查询从左到右依次执行,同一个键可以多次应用。EQ是可选的。以下是EQ的键:
名称 | 描述 |
---|---|
BQ:<basic query> | 在BQ中对多个键执行基本查询。 |
IX:<number> | 基于位置从匹配节点列表中返回一个节点。 |
OX:<number> | 水平偏移到相邻节点(正数表示右侧,负数表示左侧)。 |
OY:<number> | 垂直偏移到相邻节点(正数表示向下,负数表示向上)。 |
ON:<type> | 从匹配节点列表中选择一个节点的不同方法。 |
RN | 从匹配节点列表中返回优化的节点。 |
ST:<sort type> | 根据节点在屏幕上的位置返回排序后的节点。 |
TX | 返回与参考节点水平相交的节点。 |
TY | 返回与参考节点垂直相交的节点。 |
VG:[level number] | 返回相同视图组中的节点。 |
"X"<BQ key>:<value> | 在BQ中对特定键执行基本查询。 |
所有这些查询都是可选的,您可以选择任何字段,如果您正在对屏幕进行查询。"(S)"表示字符串,"I"表示整数,"[x, y]"表示数组。I/S可以接受整数和字符串。字符串更加强大。
键不区分大小写,我们使用大写字母来区分键和值。
匹配列表 (ML)
在执行查询后,匹配的节点将被存储在匹配列表 (ML) 中,ML中的节点通过节点ID进行标识。许多操作使用ML中的节点,一些操作可以改变ML。"save"和"load"以及较新的"push"和"pop"可用于保存和检索保存的ML。使用action:"getIds"来获取ML的ID。TX/TY/VG接受一个节点并生成多个节点。
另一种获取ML的方法是使用"elements"数组。
"elements" 属性
"elements"接受一个节点ID数组,节点ID必须是屏幕上的有效节点ID,否则会生成错误。当指定"elements"时,所有元素将存储在ML中,不会执行搜索。它可以用于按特定顺序对节点应用操作。每个带有"elements"的查询将遍历所有节点以定位相应的节点,尝试一次性将多个ID放入数组中,而不是逐个放入。相同的节点ID可以出现在elements中。"elements"是唯一可以直接组成"ML"的方法,它与"forEach"结合使用非常有用。
示例:
device.sendAai({elements:["1234a", "5678b"], action:"getNodes"})
这个查询数字按钮并构建elements数组,并使用"forEach"点击所有按钮并打印结果。
>> ids = device.sendAai({query:"T:/^[1-9]$/"}).ids
a71e,aadf,aea0,b622,b9e3,bda4,c526,c8e7,cca8
>> device.sendAai({elements:ids, action:"forEach(nsClick);getText(+R:.editText_result)"})
{count: 2, list: [{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]},{retval: '789456123'}]}
要检索一个节点,可以使用"ID:<节点ID>"。
device.sendAai({query:"ID:1234a", action:"getNodes(all)"})
测试
通过查询找到节点并不是很困难,你需要使用"UI 探测"和脚本终端:
模板(TP)详细介绍
模板(TP)是一个必需的字段,TP基于数值返回节点列表,它是BQ、EQ或操作的初始节点列表。TP是Selector的一个子类,将来可能允许用户定义新的模板。TP有几个规则:
TP:all, more, basic, reduced
这些模板遍历整个屏幕上的节点:
UI 探测有3个选择:
例如,第一个不带参数的getCount将获取"ML"的节点数量,没有"query"时,默认为"TP:more",因此与"TP:more"的节点数量相同。"+TP:all"将修改ML,这就是为什么第三个"getCount"返回由"+TP:all"构建的ML中的节点数量。
>> device.sendAai({actions:[
"getCount",
"getCount(+TP:all)",
"getCount",
"getCount(+TP:more)",
"getCount(+TP:basic)",
"getCount(+TP:reduced)"
]})
{count: 6, list: [{count: 237},{count: 242},{count: 242},{count: 237},{count: 76},{count: 58}]}
TP:anyText[,min[,max]], anyDescription[,min[,max]]
该模板将返回任何文本字段中具有非空内容的文本,或者返回任何描述字段中具有非空内容的描述。可选地,可以指定找到的文本的最小和最大长度(包括在内)。如果未指定最大长度,则不会强制执行最大长度。不允许使用零作为最小/最大值。
示例:
>> device.sendAai({query:"TP:anyText", action:"getText"})
{retval: ['Calculator','AC','÷','×','DEL','7','8','9','-' ,'4','5','6','+','1','2','3','%','0','.','±','=']}
>> device.sendAai({query:"TP:anyText,1,1", action:"getText"})
{retval: ['÷','×','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}
TP:findText,<text>
返回具有与指定文本匹配的文本或描述的节点。文本参数是必需的,否则会生成错误。它接受与"BQ"中的"T:<…>"类似的参数。
示例:
>> device.sendAai({query:"TP:findText,/[0-9]/", action:"getText"})
{retval: ['7','8','9','4','5','6','1','2','3','0']}
>> device.sendAai({query:"TP:findText,/^[A-Z]{1,4}$/", action:"getText"})
{retval: ['TGT','INTC','WMT','T','AAPL','SBUX','TSLA']}
>> device.sendAai({query:"TP:findText,*T", action:"getText"})
{retval: ['TGT','WMT','T']}
TP:textInput[,<提示文本>]
此模板返回可编辑的节点,如果可用,则返回可选的提示文本(需要Android 8.0或更高版本)。如果找不到节点,将生成错误。或者,您可以使用"BP:editable"获得相同的结果。
示例:
获取所有文本字段:
>> device.sendAai({query:"TP:textInput"})
{count: 6, ids: ['e135','e8b7','f3fa','fb7c','106bf','10e41']}
>> device.sendAai({query:"BP:editable"})
{count: 6, ids: ['e135','e8b7','f3fa','fb7c','106bf','10e41']}
// Show hint text as text value, use "getHintText" to obtain hint text
>> device.sendAai({query:"TP:textInput", action:"getHintText"})
{retval: ['text','text','Number','Email','text password','Person Name']}
通过索引或提示文本找到文本字段:
>> device.sendAai({query:"BP:editable&&IX:-1", action:"getHintText"})
{retval: 'Person Name'}
>> device.sendAai({query:"TP:textInput,text", action:"getText"})
{retval: ['text','text']}
Based on the hint of the text field: query:"TP:textInput&&T:name"
使用“setText”更改内容:
>> name = "john@noname"
>> device.sendAai({query:"TP:textInput,Person Name", action:`setText(${name})`})
{retval: true}
>> device.sendAai({query:"BP:editable&&T:Email", action:"setText(john@noname.com)"})
{retval: true}
>> device.sendAai({query:"TP:textInput&&IX:-1", action:"setText(John)"})
{retval: true}
>> device.sendAai({query:"TP:textInput&&T:text ", action:"setText(Hello)"})
{retval: true}
TP:line,<top|bottom>,<行号>
目前,此功能仅适用于具有可滚动容器的应用程序,否则TP:line将返回null。FindNode将定位滚动节点之外的UI元素,行模式将将UI元素分组为一系列行,每行可以有一个或多个节点。许多应用程序在屏幕顶部和底部都有固定的UI元素,特定的节点无法与BQ或EQ结合定位。例如,"TP:line,top,1&&IX:2"将返回第一行的第二个项目,或者"TP:line,bottom,-1&&T:Chats"将返回带有Chats文本的行的底部。
示例:
>> device.sendAai({query:"TP:line,top,1"})
{count: 6, ids: ['1902b0','191936','192fbc','1cf0bc','19373e','194a03']}
To go back click the first item "IX:0":
>> device.sendAai({query:"TP:line,top,1", action:"click(IX:0)"})
{retval: true}
>> device.sendAai({query:"TP:line,bottom,-1"})
{count: 5, ids: ['2be5b8','2c0b42','2c1a46','2c30cc','2c3fd0']}
>> device.sendAai({query:"TP:line,bottom,-1", action:"setText(BP:editable, Hello)"})
{retval: true}
TP:scrollable[,<位置>]
此模板返回由"position"标识的可滚动项(叶节点)的屏幕上的项目。"position"是从0开始的数字,从顶部到底部。在可滚动节点中使用资源ID可以获得更好的结果。默认位置为零。如果在屏幕上找不到可滚动节点,则会引发错误。"TP:scrollable"返回一个可滚动节点上的叶节点,"BP:scrollable"将匹配屏幕上的所有可滚动节点。
对于可滚动的每个项(或行/列),可能包含多个值,可以使用资源ID与之配合以返回项的特定值。例如,查询:"TP:scrollable&&R:.tv_name"。
示例:
获取当前屏幕上的所有可滚动节点:
>> device.sendAai({query:"TP:scrollable"})
{count: 5, ids: ['1e434','5bbba','5bf7b','5d9c2','6f70e']}
"BP:scrollable" 返回屏幕上所有可滚动的节点:
>> device.sendAai({query:"BP:scrollable", action:"getNodes(B)"})
{count: 5, list: [{bounds: '[0,268][1440,2738]', id: '1e434'},{bounds: '[42,457][1440,1303]', id: '5bbba'},{bounds: '[42,457][1440,1303]', id: '5bf7b'},{bounds: '[0,1303][1440,1742]', id: '5d9c2'},{bounds: '[0,1958][1440,2738]', id: '6f70e'}]}
返回设置中的所有标题(在三星手机中):
>> device.sendAai({actions:["openAndroidSetting(Settings)", "newQuery(TP:scrollable&&R:android:id/title)", "getText"]})
{count: 3, list: [{retval: true},{count: 10},{retval: ['My Name','Connections','Connected devices','Modes and Routines','Sounds and vibration','Notifications','Display','Wallpaper and style','Themes','Home screen']}]}
基本查询(BQ)详细介绍
基本查询会使用来自TP的ML,并逐个节点进行节点级别匹配,创建一个较短的ML,丢弃不匹配的节点。BQ从可访问性中提取数据,它包含单个节点的信息和操作。扩展查询中的更多功能以实现更强大的匹配(特殊字符)并添加快捷方式以使查询更简洁。
查询中的特殊字符
对于数字,例如inputType或childCount,可以接受数字或字符串:
字符串可以是以下之一,"\n"匹配换行符:
"!"用于字符串开头表示NOT,"!0"表示非零。"C:!/Layout/"表示非布局类。
为了简化示例,标准前缀{"cmd": "action","action": "findnode", "params":{…}}将被省略,我们将在"param"对象中列出示例,并使用JavaScript对象表示法以减少引号的使用。
快捷方式
有两个快捷方式可用于类名(C)和资源ID(R),使用"."作为常用前缀的替代:
"."符号只能用于普通匹配和"!"匹配,其他类型的匹配(例如正则表达式和通配符)需要使用完整形式。
P:<package name>,C:<class name>,R:<resource ID>
所有节点都包含包名和类名以及可选的资源ID。由于匹配是在运行应用程序的屏幕上进行的,"P"将默认为运行的应用程序,其他"P"没有意义。因此,"P"不是必需的。
T:<text>,D:<text>,TD:<text>
两个最重要的键,带有信息的节点通常包含文本或描述,这比OCR更准确和更快速。"until"命令可以监视文本或描述是否发生了变化。经常使用带有特殊字符的T:<text>。"TD"匹配文本或描述,如果其中任何一个匹配成功,则匹配成功。
device.sendAai({query:"T:OK", action:"click"})
点击“确定”按钮。
边界范围匹配(BI):
边界范围匹配接受屏幕坐标或边界,并根据以下条件返回节点。主要用于屏幕操作,如UI探测或调试目的。FindNode的对象模式试图在不同分辨率和尺寸的设备上查找节点,而基于坐标的"BI"将无法在所有设备上工作。
例子:
{query:"BI:[-1, 1000]&&IX:-1", action:"click"}
{query:"BI:[0, 0, 1000, 1000]", action:"getBounds"}
CC:<child count>, IT:<input type>
这些是节点中的属性,很少使用。CC是子节点的数量,IT通常是一个数字文本字段。
BP:<property>
布尔属性在节点中很重要,这些属性具有true或false的值。允许多个值,属性名称前面的"!"将匹配false。以下是这些属性:
允许多个布尔属性,用逗号分隔,选择是否将其包含在引号中是可选的。
用法:
{query:"BP:<prop name>, !<prop name>, ..."}
例子:
>> device.sendAai({query:"TP:textInput&&BP:clickable"})
{count: 1, ids: ['27ebc0']}
>> device.sendAai({query:"TP:textInput&&BP:'clickable,enabled'"})
{count: 1, ids: ['27ebc0']}
>> device.sendAai({query:"TP:textInput&&BP:clickable,enabled"})
{count: 1, ids: ['27ebc0']}
使用"getNodes(BP)"来输出所有布尔属性:
>> device.sendAai({query:"TP:textInput&&BP:clickable,enabled", action:"getNodes(BP)"})
{count: 1, list: [{booleanProperties: {checkable: false, checked: false, clickable: true, editable: true, enabled: true, focusable: true, focused: false, longClickable: true, multiLine: true, scrollable: false, selected: false, visibleToUser: true}, id: '27ebc0'}]}
从API 14开始,支持"not" (!),您不仅可以找到true值,还可以使用"!"找到false值。
假设通过循环获取值,直到没有进一步匹配(箭头显示为灰色):
>> device.sendAai({query:"R:.btn_next_period&&BP:!enabled", action:"getCount"})
{count: 1}
反过来不成立:
>> device.sendAai({query:"R:.btn_next_period&&BP:enabled", action:"getCount"})
null
>> lastError()
找不到匹配项
通常"checkable"与"clickable"相同,要从列表中删除"checkable":
>> device.sendAai({query:"BP:clickable", action:"getCount"})
{count: 26}
>> device.sendAai({query:"BP:checkable", action:"getCount"})
{count: 10}
>> device.sendAai({query:"BP:clickable,!checkable", action:"getCount"})
{count: 16}
基本查询(BQ)示例
device.sendAai({}) → device.sendAai({query:"TP:more", action:"getIds"})
>> device.sendAai({})
{count: 9, ids: ['95b0','b779','bb3a','9d32','a0f3','a4b4','a875','ac36','aff7']}
>> device.sendAai({query:"TP:more", action:"getIds"})
{count: 9, ids: ['95b0','b779','bb3a','9d32','a0f3','a4b4','a875','ac36','aff7']}
>> device.sendAai({query:"T:/./", action:"getCount"})
{count: 25}
>> device.sendAai({query:"D:/./", action:"getCount"})
{count: 44}
>> device.sendAai({query:"TD:/./", action:"getCount"})
{count: 69}
可以包含更复杂的查询,例如:
>> device.sendAai({query:"T:/^.$/&&C:.Button&&R:.button_*", action:"getText"})
{retval: ['÷','×','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}
如果找不到匹配项,它将返回null,使用"lastError()"来显示错误消息:
>> device.sendAai({query:"T:X", action:"getText"})
null
>> lastError()
No match found
>> device.sendAai({query:"X:X", action:"getText"})
null
>> lastError()
Error:Cannot find the key for "X"
可以通过编程方式检查null。
扩展查询(EQ)详细介绍
还有一些在BQ或TP中未涉及的查询,我们称之为EQ。EQ中的键通常是涉及多个节点的计算和启发式方法。EQ按照从左到右的顺序执行键入的函数,同时进行操作,希望最终的ML包含用户想要的节点。没有操作的ML是无趣的,生成的ML将传递给操作以应用操作或提取信息。可以多次应用键,允许"OX:1&&OX:1"。
索引 (IX) M → O
IX:<number>。根据从零开始的位置从ML节点返回一个节点(IX:0)。如果IX是负值,则位置按相反的顺序排列(例如,-1是最后一个节点)。
例子:
>> device.sendAai({})
{count: 9, ids: ['95b0','b779','bb3a','9d32','a0f3','a4b4','a875','ac36','aff7']}
>> device.sendAai({query:"IX:1"})
{count: 1, ids: ['b779']}
>> device.sendAai({query:"IX:-2"})
{count: 1, ids: ['ac36']}
单节点选择 (ON) M → O
ON:<option> 从ML中返回一个节点,目前有3个选项可用。目前只有有限的选项,将来会扩展更多选项。
这些是选项:
偏移量 (OX & OY) O → O
OX:<整数> 和 OY:<整数>。使用偏移量基于一个参考节点查找相邻节点,OX表示水平位置,正整数向右移动,负整数向左移动。对于OY,正整数向下移动,负整数向上移动。
例子:
在关于手机中:
>> device.sendAai({query:"T:Model name&&OX:1", action:"getText"})
{retval: 'Galaxy S22 Ultra'}
>> device.sendAai({query:"T:Model number&&OX:1", action:"getText"})
{retval: 'SM-S908U1'}
>> device.sendAai({query:"T:Android version&&OY:1", action:"getText"})
{retval: '12'}
另一个例子:
>> device.sendAai({query:"T:5&&OX:1", action:"getText"})
{retval: '6'}
>> device.sendAai({query:"T:5&&OY:-1", action:"getText"})
{retval: '8'}
// OX & OY orders may get different results
>> device.sendAai({query:"T:0&&OY:-1&&OX:2", action:"getText"})
{retval: '×'}
>> device.sendAai({query:"T:0&&OX:2&&OY:-1", action:"getText"})
{retval: '−'}
ViewGroup (VG) O → M
ViewGroup: VG 或 VG:<level>。此命令可选地接受一个级别(level),如果未指定级别,则使用1作为级别。它接受一个节点(来自查询),会进行父级遍历,直到找到一个“组”为止,级别用于指定何时停止。一个“组”是指占据屏幕宽度85%的布局,或者在类中是“ViewGroup”。ML将包含组内的节点,其中第一个节点是ViewGroup/Layout节点本身,包含了所有的节点。这对于逻辑上将感兴趣的节点分组并使用“addQuery”或BQ在组内搜索节点非常有用。可以使用“setConfig(selector:viewGroupWidthRatio,
相关操作:“getViewGroup”。
例子:
在充电之前选择正确的当前,第一级VG未达到预期节点:
>> device.sendAai({query:"T:Start Charging&&VG"})
{count: 2, ids: ['53b16','53ed7']}
要获取充电组,请使用级别2。第一个节点将是包围组中所有节点的节点(绿色矩形)。第一个节点对于“showOnScreen”非常有用,以确保整个组在屏幕上可见。
>> device.sendAai({query:"T:Start Charging&&VG:2"})
{count: 24, ids: ['4a8af','4ac70','4b031','4b3f2','4bb74','4bf35','4c2f6','4c6b7','4ca78','4ce39','4d1fa','4d5bb','4d97c','4dd3d','4e0fe','4ec41','4f002','4f3c3','53755','53b16','53ed7','54298','54659','54a1a']}
获取当前的安培数:
>> device.sendAai({query:"T:Start Charging&&VG:2&&XT:/^[0-9]+ A$/", action:"getText"})
{retval: '32 A'}
获取并减少 1 安培:
>> device.sendAai({query:"T:Start Charging&&VG:2&&XT:/^[0-9]+ A$/", actions:"getText;click(OX:-1);refresh;getText"})
{count: 4, list: [{retval: '32 A'},{retval: true},{retval: true},{retval: '31 A'}]}
使用 "repeat" 减少 5 安培:
>> device.sendAai({query:"T:Start Charging&&VG:2&&XT:/^[0-9]+ A$/", actions:"getText;repeat(5,nsClick(OX:-1));refresh;getText"})
{count: 4, list: [{retval: '32 A'},{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]},{retval: true},{retval: '27 A'}]}
交叉 (TX & TY) O → M
交叉:TX 和 TY。这些命令以一个节点作为参考节点,并将 ML 更改为包含所有水平交叉(intersectX)或垂直交叉(intersectY)的节点。匹配器将利用参考节点的边界(intersectX 的上/下边界,intersectY 的左/右边界)搜索交叉的节点。
参考节点的大小(宽度或高度)非常重要,如果选择了一个宽的参考节点,TY 将匹配屏幕上的大多数节点。明智地选择参考节点。
该命令将以第一个节点为基础,在 ML 中生成多个节点,您可以使用 "addQuery" 对它们设置更多的约束条件。
参见:"intersectX" 和 "intersectY"。
示例:
在上面的偏移计算器示例中:
>> device.sendAai({query:"T:2&&TX"})
{count: 6, ids: ['bb4b','bf0c','c2cd','e0d5','e496','111a2']}
>> device.sendAai({query:"T:2&&TY"})
{count: 6, ids: ['1d115','1d4d6','a886','b3c9','bf0c','ca4f']}
但是,如果您选择宽度滚动整个屏幕的结果作为参考节点,OX 将获取一个节点(即它本身),而 OY 将获取所有节点。
>> device.sendAai({query:"R:.result&&TX"})
{count: 1, ids: ['1d4d6']}
>> device.sendAai({query:"R:.result&&TY"})
{count: 23, ids: ['1d897','1d4d6','d592','f39a','a4c5','a886','ac47','dd14','1029e','b008','b3c9','b78a','e0d5','bb4b','bf0c','c2cd','111a2','e496','c68e','ca4f','ce10','120a6','e857']}
减少节点(RN) M → M
减少节点:RN。这是 FindNode 的一个重要功能之一,它接受节点 ID 列表,旨在减少可见节点的数量。"TL"、"BL"、"OX/OY" 和 "intersect" 都使用了 reduceNodes。请注意,如果更大的节点完全包含较小的节点,则 reduceNodes 将选择较小的节点。例如,如果较大的“.Button” 节点包含较小的“.TextView” 节点,则将选择“.TextView” 节点而不是“.Button” 节点,对 ".TextView" 节点的 "click" 仍然有效。请参阅 UI 探测 的 "Optimize" 模式。
示例:
>> device.sendAai({}).count
242
>> device.sendAai({query:"RN"}).count
55
相关操作:"reduceNodes"。
排序(ST) M → M
排序:ST:X|Y|YX。根据节点的边界进行排序,通常按照从上到下的方式进行结构和搜索,很可能已经按照顺序排序。除非排序很重要,否则很少有理由进行排序。
X – 比较节点的左边界
Y – 比较节点的顶部边界
YX – 比较节点的顶部边界,然后比较左边界
>> device.sendAai({query:"C:.Button", action:"getText"})
{retval: ['AC','÷','×','DEL','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}
>> device.sendAai({query:"C:.Button&&ST:Y", action:"getText"})
{retval: ['AC','÷','×','DEL','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}
>> device.sendAai({query:"C:.Button&&ST:YX", action:"getText"})
{retval: ['AC','÷','×','DEL','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}
>> device.sendAai({query:"C:.Button&&ST:X", action:"getText"})
{retval: ['AC','7','4','1','0','÷','8','5','2','.','×','9','6','3','±','DEL','-','+','%','=']}
相关操作:排序
基本查询(BQ) M → M
基本查询:BQ:'<基本查询>' 或 "<基本查询>"。BQ 是 EQ 中的命令。考虑以下查询(从 UI 探测自定义查询中获取):
查询:T:Overall Cpu Usage&&VG
在该查询中,使用了"VG"来获取组,如果我们想要获取"Cpu Cores"的数量,以下查询将不起作用:
T:Overall Cpu Usage&&VG&&T:Cpu Cores&&OX:1 → T:Cpu Cores&&OX:1&&VG
两个"T"命令都是基本命令,第二个"T"会覆盖第一个"T",因为 BQ 命令在 EQ 之前应用。如果我们需要在 EQ 中使用基本查询的功能,以便可以匹配文本,有以下三种方法:
以下两种方式都可以:
>> device.sendAai({query:"T:Overall Cpu Usage&&VG&&XT:Cpu Cores&&OX:1", action:"getText"})
{retval: '8'}
>> device.sendAai({query:"T:Overall Cpu Usage&&VG&&BQ:'T:Cpu Cores'&&OX:1", action:"getText"})
{retval: '8'}
从版本14开始,有一个标志允许在EQ命令之后使用任何BQ命令,在本示例中,"T:Cpu Cores"被视为EQ的一部分
>> device.sendAai({action:"setConfig(selector:allowBqAfterEq, true)"})
{retval: true}
>> device.sendAai({query:"T:Overall Cpu Usage&&VG&&T:Cpu Cores&&OX:1", action:"getText"})
{retval: '8'}
动作(Actions)
FindNode提供了许多动作命令,使用"function"可以创建更多的动作。"action"可以接受单个字符串命令或包含多个命令的字符串数组。查询是可选的,如果没有指定查询,它将包含默认模板。如果指定了查询,查询的输出将保存到ML中,命令/动作所需的节点将通过查询或可选查询从底层的ML中获取,使用action:"getIds"来获取ML内容。如果未指定任何动作,将使用action:"getIds"。"action"和"actions"的使用方式是相同的。
使用"device.sendAai()"或"devices.sendAai()"与FindNode进行通信。对于多个设备,将为每个设备创建一个线程来执行查询和动作。
当sendAai遇到来自FindNode的错误或超时时,它将返回null,并包含错误消息的"lastError()"。
>> device.sendAai({query:"T:Not there", action:"getText"})
null
>> lastError()
No match found
对于只需要一个节点的动作(例如"click"),将选择ML中的第一个节点(ON、IX、OX和OY可以改变这一点)。动作中有许多命令,在新版本中将添加改进,可能会引入不兼容性,请使用action:"version"来确保版本支持所需的命令。某些命令需要附加参数(参数将被括在括号中)。目前支持以下类型的参数:
"function"可以创建更多的动作,有关说明和示例,请参见"function"命令。
单个动作
在单个动作中,动作中只有一个命令。在这种情况下,返回的结果在"retval"中(如果发生错误,则为null)。
device/devices.sendAai({query:"...", action:<action>})
示例:
>> device.sendAai({query:"T:Product name&&OX:1", action:"getText"})
{retval: 'Galaxy S22 Ultra'}
// Absence of query, the default query of "TP:more" will be applied
>> device.sendAai({action:"getText(T:Product name&&OX:1)"})
{retval: 'Galaxy S22 Ultra'}
Error message:
>> device.sendAai({action:"getText(T:Product name&&OX:2)"})
null
>> lastError()
Query Error: Node not found on offset
多个动作
在FindNode中允许多个动作,许多FindNode操作都是通过多个动作完成的,多个动作将逐个从左到右执行,结果是一个数组,其中存储了每个动作的输出,如果其中一个动作失败,它将返回null,并且lastError()将显示失败的动作的索引(从零开始)和错误消息。有两种定义多个动作的方法:
数组:
actions:["动作1", "动作2", …]
字符串中的分号(版本14+):
actions:"<动作1>;<动作2>"
device/devices.sendAai({query:"...", actions:[<动作1>, <动作2>, …]})
device/devices.sendAai({query:"...", actions:"<动作1>;<动作2>, …"})
示例:
下面是执行多个动作的两种方法,count将显示命令的数量,list将显示动作的输出列表。List[0]是"setText"的输出,list[1]是refresh的输出,list[2]是gettext的输出。
>> device.sendAai({query:"TP:textInput", actions:["setText(Hello)", "refresh", "getText"]})
{count: 3, list: [{retval: true},{retval: true},{retval: 'Hello'}]}
>> device.sendAai({query:"TP:textInput", actions:"setText(Hello);refresh;getText"})
{count: 3, list: [{retval: true},{retval: true},{retval: 'Hello'}]}
Error message will show the position (zero-based) of the error:
>> device.sendAai({query:"TP:textInput", actions:["setText(Hello)", "refresh", "noAction", "getText"]})
null
>> lastError()
[2 - noAction] Invalid action: noAction
动作和查询中的变量
如果动作的参数是在运行时确定的,需要进行变量替换,有三种方法可以实现:
如果想在动作命令中使用变量,可以使用"+"来连接多个项,在加上引号后可能会变得繁琐:
JavaScript字符串函数:
var input = "Hello"
>> "setText('" + input + "')"
setText('Hello')
>> 'setText("' + input + '")'
setText("Hello")
>> 'setText(' + input + ')'
setText(Hello)
>> device.sendAai({query:"TP:textInput", action:["setText('" + input + "')", "click(OX:2)"]});
{count: 2, list: [{retval: true},{retval: true}]}
JavaScript模板字面量
从版本14开始,可以使用反引号轻松地对字符串进行变量替换(或表达式),因为它是JavaScript的基本构造,可以应用于查询和动作。通过在字符串中引入多个动作(用";"分隔),替换变得更简单:
下面是一个示例,在输入input包含空格的情况下,建议用引号括起来:
>> device.sendAai({query:"TP:textInput", action:`setText("${input}");click(OX:2)`})
{count: 2, list: [{retval: true},{retval: true}]}
获取计算器中的所有数字:
>> var text = "/^[0-9]$/"
>> device.sendAai({query:`T:${text}`, action:"getText"})
{retval: ['7','8','9','4','5','6','1','2','3','0']}
调用函数以获取类和描述:
>> var c=()=>".CheckBox"
>> var x="T,D"
T,D
>> var d=()=>"Sunday"
>> device.sendAai({query:`C:${c()}&&D:${d()}`,action:`getNodes("${x}")`})
{count: 1, list: [{description: 'Sunday', id: '509a7', text: 'S'}]}
>> var disp=()=>"getNodes('${x}')"
>> disp()
getNodes('T,D')
>> device.sendAai({query:`C:${c()}&&D:${d()}`,action:`${disp()}`})
{count: 1, list: [{description: 'Sunday', id: '509a7', text: 'S'}]}
aaix:
aaix是在版本14之前的一个简单辅助函数,用于简化动作的编写。由于引入了JavaScript模板字面量,使用"aaix"没有必要。
>> aaix("setText", input)
setText("Hello")
>> aaix("sendKey", tcConst.keyCodes.KEYCODE_HOME, 0)
sendKey(3, 0)
>> device.sendAai({query:"TP:textInput", action:[aaix("setText", input), "click(OX:2)"]})
{count: 2, list: [{retval: true},{retval: true}]}
>> device.sendAai({action:aaix("sendKey", tcConst.keyCodes.KEYCODE_HOME, 0)})
{retval: true}
如果参数是字符串,以下三个例子是相同的:
action:["intersectX(OY:1, IX:2)", "getText"]
action:["intersectX('OY:1', 'IX:2')", "getText"]
action:[aaix("intersectX", "OY:1", "IX:2"), "getText"]
大多数操作的第一个参数都是可选的查询字符串,例如:
{query:"T:John", action:"click"}
{actions:["newQuery(T:John)", "click"]} → {action:"click(+T:John)"}
{actions:["addQuery(T:Mary)", "click"]} → {action:"click(T:Mary)"}
添加的查询字符串会使命令更加简洁。以"+"为前缀的"newQuery"将清除ML,进行搜索并重建ML。"addQuery"将基于现有的ML进行搜索。
同步操作
为了确保操作是同步的,FindNode付出了很大的努力。当调用"click"时,不仅会确保点击操作是同步进行的,还会等待特定的"事件"来确保渲染"开始"发生,但无法确保渲染完成。FindNode提供了几种方法来执行这种检查:
可选查询 (OQ)
需要节点的操作将从ML中获取节点,需要一个节点的操作将从ML中获取第一个节点。但是,用户可以通过在操作中添加可选查询 (OQ) 来更改这种行为。
例如:{query:"T:OK", action:"click"} → {action:"click(T:OK)"}
所有的OQ都是可选的,如果未指定OQ,操作将使用ML中的节点。如果操作需要一个节点(例如,click操作),将使用ML中的第一个节点;如果操作支持多个节点(例如,getNodes或getText操作),将使用整个ML。
OQ是普通查询语言,不同类型的查询由不同的前缀标识,OQ与其他查询类似,可以生成一个或多个节点,其中一些查询类型将修改ML。以下是支持的3个前缀:
假设有一个计算器,分为3行,分别是数字1到9,分别是"7,8,9"、"4,5,6"和"1,2,3"(从上到下):
>> device.sendAai({query:"T:/[3-9]/", actions:["getText(T:5)", "getText"]})
{count: 2, list: [{retval: '5'},{retval: ['7','8','9','4','5','6','3']}]}
>> device.sendAai({query:"T:/[3-9]/", actions:["getText(+T:5)", "getText"]})
{count: 2, list: [{retval: '5'},{retval: '5'}]}
>> device.sendAai({query:"T:/[3-9]/", actions:["getText(*T:5)", "getText"]})
{count: 2, list: [{retval: '5'},{retval: '5'}]}
这是常规查询和"*"查询之间的区别:
>> device.sendAai({query:"T:/[3-9]/", actions:["getText(OX:1)", "getText(OX:1)", "getText"]})
{count: 3, list: [{retval: '8'},{retval: '8'},{retval: ['7','8','9','4','5','6','3']}]}
>> device.sendAai({query:"T:/[3-9]/", actions:["getText(*OX:1)", "getText(*OX:1)", "getText"]})
{count: 3, list: [{retval: '8'},{retval: '9'},{retval: '9'}]}
看起来"*"和"+"是类似的,考虑下面的例子,"*"匹配来自"ML"的列表(T:2不在ML中),"+"搜索整个屏幕内容。
>> device.sendAai({query:"T:/[3-9]/", actions:["getText(+T:1)", "getText"]})
{count: 2, list: [{retval: '1'},{retval: '1'}]}
>> device.sendAai({query:"T:/[3-9]/", actions:["getText(*T:1)", "getText"]})
null
>> lastError()
[0 - getText] Query Error:No match found
由于常规查询(无前缀)和更新ML的查询("*"前缀)将使用ML,因此只允许在"newQuery"或"+"前缀中使用"TP":
>> device.sendAai({query:"T:/[3-9]/", actions:["getText(TP:all)", "getCount"]})
null
>> lastError()
[0 - getText] Query Error:Key "TP" is not permitted in this query
>> device.sendAai({query:"T:/[3-9]/", actions:["getText(+TP:all)", "getCount"]})
{count: 2, list: [{retval: [{},{},{},{},{},{},{},'Calculator',{},{},{},{},'AC','÷','×','DEL','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']},{count: 32}]}
>> device.sendAai({query:"T:/[3-9]/", actions:["getText(*TP:all)", "getCount"]})
null
>> lastError()
[0 - getText] Query Error:Key "TP" is not permitted in this query
Get 命令
getBounds (M)
返回ML中节点的边界(格式:[左,上,右,下]),它返回两个数组,一个包含所有ID,另一个包含所有边界。
用法:
getBounds[(<查询>)]
返回:
边界数组,每个边界都是一个由4个元素构成的数组:[x1, y1, x2, y2]。
>> device.sendAai({query:"T:/[0-9]/", action:"getBounds"})
{bounds: [[18,1158,370,1521],[370,1158,722,1521],[722,1158,1074,1521],[18,1521,370,1884],[370,1521,722,1884],[722,1521,1074,1884],[18,1884,370,2247],[370,1884,722,2247],[722,1884,1074,2247],[18,2247,370,2610]], count: 10, ids: ['98d1','9c92','a053','a7d5','ab96','af57','b6d9','ba9a','be5b','c5dd']}
getBoolProp (O)
返回节点的布尔属性,它将返回"editable"、"progressible"、"clickable"、"checkable"、"scrollable"或"longClickable"。如果找不到属性,则返回null。
用法:
getBoolProp [(<查询>)]
返回:
字符串属性数组。
>> device.sendAai({query:"T:/12:30\\sPM/&&VG", action:"getBoolProp(T:Every day&&OX:1)"})
{retval: ['checkable','clickable']}
>> device.sendAai({query:"T:/12:30\\sPM/&&VG&&PQ:'T:Every day&&OX:1'", action:"getBoolProp"})
{retval: ['checkable','clickable']}
>> device.sendAai({query:"T:ci:calculator", action:"getBoolProp"})
null
>> lastError()
No boolean property is found
getBoolPropInAncestors(O)
与getBoolProp类似,不同之处在于它遍历父节点直到达到顶级节点。
用法:
getBoolPropInAncestors [(<查询>)]
返回:
属性和节点ID的数组数组,如果未找到,则返回null。
>> device.sendAai({query:"T:John", action:"getBoolPropInAncestors"})
{boolProp: [['clickable','3622e'],['scrollable','105cd']], count: 2}
getChecked (O/M)
这些命令适用于具有"checkable"属性的节点。通常,切换控件、复选框或单选按钮都是可选的。例如,类名为".Switch"或".Checkbox"的节点是可选的。
用法:
getChecked [(<查询>)]
返回:
如果输入是一个节点,则在"retval"中返回true/false,如果是多个节点,则在"retval"中返回true/false的数组,对于不可选的节点,显示为"N/A"。如果未找到任何可选节点,则返回null。使用"BP:checkable"查找可选节点,使用"BP:checked"查找已选中的节点。
另请参阅: setChecked
>> device.sendAai({action:"getChecked(D:Sunday&&TX)"})
{retval: [false,true,true,true,true,true,false]}
如果不想使用描述,可以使用类来进行搜索,没有其他UI元素的类名是“.CheckBox”:
>> device.sendAai({action:"getChecked(C:.CheckBox&&IX:0&&TX)"})
{retval: [false,true,true,true,true,true,false]}
要找到所有选中的描述:
>> device.sendAai({action:"getDescription(D:Sunday&&R:.day_button_0&&TX&&XBP:checked)"})
{retval: ['Monday','Tuesday','Wednesday','Thursday','Friday']}
getDescription/getText/getHintText (O|M)
这些是“getNodes”命令的简化版本,更容易获取文本、描述和提示文本的内容。
这些命令会检测一个或多个节点。如果检测到一个节点,它将返回相应的值。否则,它将返回文本/描述或提示文本的数组。
用法:
getDescription [(<query>)]
getHintText [(<query>)]
getText [(<query>)]
返回值:
如果找到多个节点,它会返回包含文本/描述/提示文本的数组。如果找到一个节点,它会返回文本/描述。
>> device.sendAai({query:"TP:line,bottom,-1", postAction:"getText"})
{retval: ['Chats','Calls','Contacts','Notifications']}
// One value will not return an array
>> device.sendAai({query:"TP:line,bottom,-1&&IX:1", postAction:"getText"})
{retval: 'Calls'}
>> device.sendAai({query:"BP:editable", action:"getText"})
{retval: ['text','text','Number','Email','text password','Person Name']}
>> device.sendAai({query:"BP:editable", action:"setText(T:Number, '100');setText(T:text password, secretWord)"})
{count: 2, list: [{retval: true},{retval: true}]}
>> device.sendAai({query:"BP:editable", action:"getText"})
{retval: ['text','text','100','Email','••••••••••','Person Name']}
// getHintText will maintain the value of the original hints, getText does not
>> device.sendAai({query:"BP:editable", action:"getHintText"})
{retval: ['text','text','Number','Email','text password','Person Name']}
获取焦点(getFocus)
返回当前获取焦点的节点(在getNodes(BP)中为"focusable")(通常是文本字段或滑块),如果没有节点获取焦点,则返回null。如果找到节点,将设置ML为找到的节点。
用法:
getFocus
返回:
如果找到,将返回节点ID,否则返回null。
示例:
输入下一行以从现有行输入:
>> device.sendAai({action:["getFocus", aaix("setText", username), "addQuery(OY:1)", aaix("setText", password)]})
{count: 4, list: [{node: '67693'},{retval: true},{count: 1},{retval: true}]}
获取ID(getIds)
如果未定义action,则默认使用此命令,该命令将返回ML的计数和ID数组。
用法:
getids [(<query>)]
返回:
计数和ID数组。
>> device.sendAai({})
{count: 26, ids: ['7708','d8a2','e7a6','ef28','824b','860c','89cd','8d8e','914f','9510','98d1','9c92','a053','a414','a7d5','ab96','af57','b318','b6d9','ba9a','be5b','c21c','c5dd','c99e','cd5f','d120']}
>> device.sendAai({action:"getIds(C:.Button)"})
{count: 4, ids: ['8dc5','9547','9908','9cc9']}
获取节点(getNodes)
此命令用于检索节点的信息。字段可以确定要显示的内容。"getNodes"将在检索信息之前刷新节点。
用法:
getNodes
getNodes([<query>] [,<fields>])
参数:
fields:可选的"fields"用于定义要返回的字段,多个字段使用逗号分隔。由于"field"中包含逗号,因此需要使用引号。字符串。有效的字段标识符包括:
返回:
节点信息的计数和数组。
>> device.sendAai({action:"getNodes(T:/[0-9]/,'R,T')"})
{count: 10, list: [{id: 'a773', resourceId: '.button_seven', text: '7'},{id: 'ab34', resourceId: '.button_eight', text: '8'},{id: 'aef5', resourceId: '.button_nine', text: '9'},{id: 'b677', resourceId: '.button_four', text: '4'},{id: 'ba38', resourceId: '.button_five', text: '5'},{id: 'bdf9', resourceId: '.button_six', text: '6'},{id: 'c57b', resourceId: '.button_one', text: '1'},{id: 'c93c', resourceId: '.button_two', text: '2'},{id: 'ccfd', resourceId: '.button_three', text: '3'},{id: 'd47f', resourceId: '.button_zero', text: '0'}]}
>> device.sendAai({query:"C:.SeekBar", action:"getNodes(RI)"})
{count: 1, list: [{id: 'b6f0', rangeInfo: {current: 0, max: 100, min: 0, type: 0, typeString: 'int'}}]}
>> device.sendAai({query:"C:.SeekBar", action:"getNodes(BP)"})
{count: 1, list: [{booleanProperties: {checkable: false, checked: false, clickable: false, editable: false, enabled: true, focusable: true, focused: false, longClickable: false, multiLine: false, scrollable: false, selected: false, visibleToUser: true}, id: 'b6f0'}]}
获取包名(getPackageName)
返回当前正在运行的包名。
用法:
getPackageName
返回:
包名在"retval"中。
>> device.sendAai({action:"getPackageName"})
{retval: 'com.google.android.gm'}
获取进度(getProgress)
返回UI元素的进度类型的值,例如滑块("getNodes(RI)"的简写):
用法:
getProgress [query]
返回:
进度节点的RangeInfo在"retval"中,如果节点不是可进度节点,则返回null:
RangeInfo具有3种类型之一:"float" - 在最小值和最大值之间的浮点数,"int" - 在最小值和最大值之间的整数,"percent" - 在0和1之间。"current"包含当前值。
>> device.sendAai({action:"getProgress(C:.SeekBar)"})
{retval: {current: 41, max: 100, min: 0, type: 0, typeString: 'int'}}
另请参阅: setProgress
获取查询字符串(getQuery/getUniqQuery)
接受一个节点并尝试生成匹配该节点(或多个节点)的查询字符串。从"getQuery"返回的查询字符串可以匹配多个节点,有助于获取多个节点的文本或描述信息。从"getUniqQuery"返回的查询字符串只能匹配一个节点,获取到的查询字符串将被发送到其他设备以定位节点。目前,这还比较基础,我们将在将来开发更好的查询字符串:
用法:
getUniqQuery/getQuery - 默认情况下,使用文本和描述作为查询的一部分。
getUniqQuery/getQuery(<true/false>) - 第一个参数为true时,忽略搜索查询中的文本。
getUniqQuery/getQuery(<true/false>, <true/false>) - 第二个参数为true时,忽略搜索查询中的描述。
返回:
返回查询字符串。
获取每个节点的查询字符串:
>> device.sendAai({query:"T:Controls&&TX"}).ids.forEach(
x => print(device.sendAai({elements:[x],action:"getUniqQuery"}).query)
)
T:Controls&&OX:-1
T:Controls
T:Controls&&OX:1
>> var query = device.sendAai({query:"T:AAPL", action:"getQuery(true)"}).query
>> query
C:.TextView&&R:.ticker&&CC:0
>> device.sendAai({query:query, action:"getText"})
{retval: ['MSFT','INTC','TGT','T','AAPL','SBUX','TSLA','CSCO']}
查询命令
以下命令(除了waitQuery)将涉及到ML。
push/pop & load/save (M)
这些命令用于存储和检索ML,仅在多个命令中有用。push/save命令用于保存ML状态,newQuery + actions,pop/load命令用于检索最后保存的ML状态并继续。save/load命令是为了兼容性的原因,因为它们没有限制存储ML的数量。实际上,在实现中,"save"和"push"、"load"和"pop"是相同的。可以使用多个"save"和"load"。
用法:
push
pop
save
load
返回值:始终返回true的"retval",如果堆栈中没有可弹出的内容,将生成错误。
intersectX/intersectY (O → M)
如果指定了查询字符串,则将使用查询字符串的第一个节点作为交叉节点;否则将使用ML的第一个节点。此命令将接收一个节点并生成更多节点,并存储在ML中。
用法:
intersect?[(<query>)]
返回:
true/false:如果操作成功。如果操作成功,将返回"count"。
如果指定了"post query",则将存储"post query"的输出(参见"addQuery")。如果未指定"post query",则交叉操作的输出将替换ML。
参见:"TX"和"TY"查询的更多解释。
示例:
假设是计算器应用程序
>> device.sendAai({actions:["intersectX(T:3)", "getText"]})
{count: 2, list: [{count: 4},{retval: ['1','2','3','%']}]}
"intersect Y"可能包括其他不可见的节点,例如结果或视图组。
>> device.sendAai({actions:["intersectY(T:3)", "getText"]})
{count: 2, list: [{count: 8},{retval: [{},{},{},'×','9','6','3','±']}]}
添加一个正则表达式以确保至少有一个字符可见:
>> device.sendAai({actions:["intersectY(T:3)", "addQuery(T:/.+/)", "getText"]})
{count: 3, list: [{count: 8},{count: 5},{retval: ['×','9','6','3','±']}]}
这也可以起作用:
>> device.sendAai({actions:["intersectY(T:3)", "getText(T:/.+/)"]})
{count: 2, list: [{count: 8},{retval: ['×','9','6','3','±']}]}
getViewGroup (O → M)
这个命令类似于"VG"查询,它接受一个层级和查询。如果定义了查询,它将用于标识要搜索组的第一个节点。类似于"VG",它会更改ML。
用法:
getViewGroup
getViewGroup([<查询>,] <层级>)
返回:
如果找到ViewGroup,它将返回ML中的计数,如果未找到ViewGroup,它将返回null。
另请参阅:查询"VG"。
>> device.sendAai({query:"T:Battery&&VG", action:"getText(T:/\\d%/)"})
{retval: '92% available'}
>> device.sendAai({actions:["getViewGroup(T:Battery)","getText(T:/\\d%/)"]})
{count: 2, list: [{count: 6},{retval: '92% available'}]}
reduceNodes (M → M)
与"RN"查询完全相同,没有参数。将返回结果后的节点数量作为"count"。
用法:
reduceNodes
返回:
在"count"中返回结果节点的数量。
另请参阅:查询"RN"。
>> device.sendAai({query:"TP:reduced"}).count
54
>> device.sendAai({action:"reduceNodes"})
{count: 54}
Sort (M → M)
基于节点边界进行排序。与"ST"查询完全相同,具有相同的参数。
用法:
sort(X|Y|YX)
返回:
在"retval"中返回true/false。
另请参阅:查询"ST"。
newQuery/addQuery/waitQuery
所有命令都接受查询字符串。"newQuery"将启动新的查询并重新创建ML(类似于OQ中的"+")。"addQuery"在ML中执行搜索,并将结果节点放入ML中(类似于OQ中的"*")。"waitQuery"不会更改ML,它具有超时设置,确保在超时到期之前完成查询,否则将返回null。由于waitQuery不会更改ML,如果您想像"newQuery"那样更改ML,请在"newQuery"中使用超时设置。
例如:"VG"通常会跟随"addQuery"而不是"newQuery"。另一方面,点击以打开新窗口,将需要使用"newQuery"。
newQuery(<query>[, <timeout>])
addQuery(<query>)
waitQuey(<query>[, <timeout>]) // 默认超时设置为2000毫秒
返回:
newQuery和addQuery将以"count"形式返回结果ML的大小。当超时到期时,waitQuery将返回null,否则"retval"将包含true。
示例:
以下命令将获得相同的结果,但"newQuery"将更改ML并影响后续使用ML的操作:
>> device.sendAai({action:`waitQuery(T:${text},10000);click(T:${text})`})
{count: 2, list: [{retval: true},{retval: true}]}
>> device.sendAai({action:`newQuery(T:${text},10000);click`})
{count: 2, list: [{count: 1},{retval: true}]}
动作命令
此部分的命令对节点执行操作。
click, nsClick, click2, longClick
对节点执行不同类型的点击操作,这是同步点击,会确保在控制权返回时屏幕开始刷新。
上述所有点击操作都会监视点击后屏幕变化的事件,如果屏幕没有变化,这些点击操作将失败并最终超时。如果点击后屏幕没有发生变化,可以使用"nsClick":
用法:
click/nsClick/click2/longClick [(<query>)]
返回:
true或false,表示操作是否成功。
click, nsclick, click2, longClick
click(<query>)、nsclick(<query>)、click2(<query>)、longClick(<query>)
示例:
>> device.sendAai({query:"T:5", action:"click"})
{retval: true}
>> device.sendAai({action:"nsClick(T:5)"})
{retval: true}
forEach (M)
此操作循环遍历ML中的每个节点,并逐个执行提供的操作。有几个规则适用:
对于多个操作,"forEach"使用JSON对象的名称,其值定义了操作,可以是数组或以分号分隔的字符串。对于单个操作,可以直接将操作名称放在参数中。
用法:
forEach(<操作名称>)
forEach(<JSON名称>)
返回:
示例:
第一行运行了一次"getText",第二行运行了4次"getText"。
>> device.sendAai({query:"T:1&&TX", action:"getText"})
{retval: ['1','2','3','%']}
>> device.sendAai({query:"T:1&&TX", action:"forEach(getText)"})
{retval: [{retval: '1'},{retval: '2'},{retval: '3'},{retval: '%'}]}
>> device.sendAai({query:"R:.ticker&&T:/TSLA|AAPL|INTC|CSCO/", actions:"forEach(getTicker)", getTicker:"refresh(OX:1);getDescription(OX:1)"})
{getTicker: [[{retval: true},{retval: '160.25'}],[{retval: true},{retval: '190.41'}],[{retval: true},{retval: '50.51'}],[{retval: true},{retval: '29.36'}]]}
>> device.sendAai({query:"R:/.day_button_[0-6]/", action:"getText"})
{retval: ['S','M','T','W','T','F','S']}
>> device.sendAai({query:"R:/.day_button_[0-6]/", action:"forEach(setChecked(true))"})
{retval: [{changedCount: 0},{changedCount: 0},{changedCount: 0},{changedCount: 1},{changedCount: 1},{changedCount: 0},{changedCount: 1}]}
在示例文档中,对于计算器,它填入所有的操作在"elements"中,并使用"forEach(nsClick)"来输入计算。
function/funcExists/funcDelete/funcReset
这个动作命令将创建一个由以下组成的新动作:
用法:
创建新函数:
function(<JSON中的动作名称>(参数)) <JSON名称>:<动作>,用[]或";"或单个动作
删除已创建的函数:
funcDelete(<函数名称>)
返回函数是否存在(true/false):
funcExists(<函数名称>)
重置 - 删除所有函数
funcReset
返回值:
如果发生错误,返回null,否则在retval中返回true。
示例:
假设我们想要使用计算器来添加两个个位数的值:
>> device.sendAai({action:"function(add(1,2))", add:"click(T:%1);click(T:+);click(T:%2);click(T:=);getText(R:.editText_result)"})
{retval: true}
这个命令接受两个替换参数%1和%2,%1的默认值是"1",%2的默认值是"2"。
以下命令将使用%1("3")替换第一个参数值,使用%2("4")替换第二个参数值,从而点击"3","+","4","="。
>> device.sendAai({action:"add(3,4)"})
{add: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: '=7'}]}
以下命令没有指定参数,将使用两个默认值,因此点击"1","+","2","="。
>> device.sendAai({action:"add"})
{add: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: '=3'}]}
指定了一个参数,%1将为"9",%2将为默认值"2",点击"9","+","2","="。
>> device.sendAai({action:"add(9)"})
{add: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: '=11'}]}
检查函数是否存在并删除一个函数。
>> device.sendAai({action:"funcExists(add)"})
{retval: true}
>> device.sendAai({action:"funcDelete(add)"})
{retval: true}
>> device.sendAai({action:"funcExists(add)"})
{retval: false}
>> device.sendAai({action:"add(9)"})
null
>> lastError()
Invalid action: add
另一个示例:这个函数"sendText"用于在文本字段中输入文本并点击发送按钮:
>> device.sendAai({action:"function(sendText(Hello))", sendText:[
"newQuery(TP:textInput)",
"setText(%1)",
"click(OX:2)"
]})
{retval: true}
>> device.sendAai({action:"sendText(How are you?)"})
{sendText: [{count: 1},{retval: true},{retval: true}]}
>> var input = "Good morning"
>> device.sendAai({action:`sendText(${input})`})
{sendText: [{count: 1},{retval: true},{retval: true}]}
下面的函数将查找一个名称,点击进入聊天界面,使用刚刚创建的"sendText"函数发送文本,然后点击返回键回到主界面:
>> device.sendAai({action:"function(findAndSend)", findAndSend:"scrollIntoView(T:%1);click;sendText(%2);sendKey(Back)"})
{retval: true}
>> device.sendAai({action:"findAndSend(John Doe, Nice to meet you)"})
{findAndSend: [{retval: true},{retval: true},{sendText: [{count: 1},{retval: true},{retval: true}]},{retval: true}]}
另一个例子:获取来自mySolarEdge应用程序的SolarEdge产量:
>> device.sendAai({actions:"function(getProduction)", getProduction:[
"openApp(mySolarEdge)",
"waitQuery(T:Lifetime,20000)",
"newQuery(R:.pv_power_now)",
"getText",
"sendKey(Back)",
"sendKey(Back)"]});
{retval: true}
>> var info = device.sendAai({action:"getProduction"})
{getProduction: [{retval: true},{retval: true},{count: 1},{retval: '5.89 kW
Solar Power Now'},{retval: false},{retval: true}]}
要获取产量,请使用info.getProduction[3].retval。SolarEdge将根据输出量返回W或kW,可以编写一个简单的函数来获取太阳能产量:
function getProduction() {
var p = device.sendAai({action:"getProduction"});
if (p != null) {
var prodText = p.getProduction[3].retval;
var matches = /^(\d+(\.?\d+)?) (W|kW)/.exec(prodText);
if (matches != null) {
return matches[3] == 'kW' ? Math.round(matches[1]*1000) : matches[1];
}
}
return -1;
}
>> getProduction()
5890
refresh (M)
无障碍功能节点信息被缓存,如果屏幕已更新,缓存信息可能不准确,使用"refresh"强制重新读取节点信息。
用法:
refresh [(<查询>)]
返回值:
返回计数和true(始终返回true)
示例:
>> device.sendAai({query:"T:Overall Cpu Usage&&OX:1", actions:["getText", "sleep(2000)", "refresh", "getText"]})
{count: 4, list: [{retval: '29'},{retval: true},{retval: true},{retval: '78'}]}
repeat (M)
该命令用于重复执行操作,它与"forEach"共享相同的操作输入(JSON或单个操作)和类似的返回值,它将重复执行给定次数的操作/操作集。最大重复次数为10。
用法:
repeat (<重复次数>, <操作名称>)
repeat (<重复次数>, <JSON名称>)
返回值:
示例:
该命令在计算器中点击5次"1":
>> device.sendAai({action:"repeat(5,click(T:1))"})
{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]}
设置电动汽车的充电电流:
减少1安培:
>> device.sendAai({action:"click(T:/[0-9]+ A/&&OX:-1)"})
{retval: true}
减少5安培:
>> device.sendAai({action:"repeat(5, click(T:/[0-9]+ A/&&OX:-1))"})
{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]}
减少并验证:
>> device.sendAai({query:"T:/[0-9]+ A/", action:"getText;repeat(5,click(OX:-1));refresh;getText"})
{count: 4, list: [{retval: '27 A'},{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]},{retval: true},{retval: '22 A'}]}
scrollIntoView → (M)
接受查询字符串和滚动方向,根据提供的方向向上/向下滚动以尝试匹配查询。当找到一个或多个节点时,它将返回结果。如果找不到匹配项,将返回空值和错误。滚动将使用页面向上/向下滚动,最多滚动30页(由setConfig(navi:maxPageScroll,
用法:
scrollIntoView(<查询>) // 默认向下滚动
scrollIntoView(<查询>, <方向>)
参数:
<方向> 可以是以下之一,如果未指定,则默认为 "down"。
"down": 从当前位置开始向下滚动搜索。
"up": 从当前位置开始向上滚动搜索。
"top": 快速滚动到页面顶部,然后向下滚动搜索。
"bottom": 快速滚动到页面底部,然后向上滚动搜索。
返回值:
如果无法找到查询结果,则返回null,否则返回true,ML将设置为第一个匹配节点的ID。
示例:
将当前应用程序滚动直到找到 "John",然后点击找到的节点。
>> device.sendAai({actions:["scrollIntoView(T:John)", "click"]})
{count: 2, list: [{retval: true},{retval: true}]}
sendKey
sendKey接受键码或元状态,并将键发送到屏幕(而不是节点),焦点所在的UI元素将接收键码和元状态,例如文本字段或屏幕键盘。sendKey还提供了快捷方式,可以使用文本字符串表示常见的键码。并非所有键码都会生成字符,一些特殊的键码会打开新窗口,例如返回、应用切换或回到主屏幕。键码和元状态在Android的KeyEvent类中显示。"sendKey"发送到屏幕,而不是特定节点,因此不涉及ML。
快捷方式实际上是将不同的键码映射为文本字符串,以下是所有快捷方式(不区分大小写):"home"、"back"、"backspace"、"enter"、"appswitch"(切换到上一个应用程序),对于旧应用程序还有"search"和"menu"。
用法:
sendKey(<键码>)
sendKey(<键码>, <元状态>)
sendKey(<快捷方式>)
参数:
键码和元状态:为整数,"快捷方式"为单个字符串。关于返回键,如果处于Sigma输入法输入模式中,单个"back"键将向窗口发送2个"back"键,第一个back键用于关闭输入法,第二个back键用于关闭窗口。使用"setConfig(sigmaInput:backKeyAutoDismiss, false)"来更改行为。
以下是快捷方式(根据Android版本的不同,某些快捷方式可能无效):
appswitch 切换应用程序
back 发送返回键
backspace 发送退格键
delete 发送删除键
enter 发送回车键
home 发送主页键
menu 发送菜单键
search 发送搜索键
其他3个快捷方式不是"sendKey"的一部分,但效果类似:
notifications 显示通知屏幕
recentApps 显示最近使用的应用程序屏幕
quickSettings 显示快速设置屏幕
返回值:
返回操作的状态,为true/false。
示例:
tcConst包含键码和元状态信息。例如,以下3个示例将得到相同的结果:
sendKey('back')
sendKey(4)
sendKey(tcConst.keyCodes.BACK)
这将输入"A"键
>> device.sendAai({preAction:aaix("sendKey", tcConst.keyCodes.A, tcConst.keyCodes.META_SHIFT_ON)})
{retval: true}
setChecked (M)
此命令适用于具有"checkable"属性的节点。通常,切换控件是可选中的,例如复选框或单选按钮,其类名可以是".Switch"或".Checkbox"。对于单选按钮,setChecked(false)将不起作用,因为FindNode无法确定要单击以禁用现有单选按钮。
用法:
setChecked(true|false)
setChecked([查询], true|false)
参数:
"setChecked(true|false)",由于没有权限设置选中值,如果需要更改值,它将单击节点以切换值。它接受多个节点,将忽略非可选中的节点。
返回值:
setChecked返回"changedCount",表示有多少个可选中的节点已更改。
setProgress (O)
用于设置滑块的值,可以通过"getNodes('RI’)"获取最小值、最大值、类型和当前值。
用法:
setProgress(<数字>)
setProgress(<查询>,<数字>)
参数:
<数字>:整数或小数,根据"type"、"min"和"max"确定要设置的值
返回值:
true/false:如果成功设置了值,则返回true;如果设置为相同的值或超出范围,则返回false。如果节点不是进度节点,则返回null。
参见:getProgress
示例:
将显示亮度设置为50%:
>> device.sendAai({query:"T:Brightness&&VG&&PQ:'C:.SeekBar'", action:"getProgress"})
{retval: {current: 85983232, max: 267386880, min: 0, type: 0, typeString: 'int'}}
>> var max = device.sendAai({query:"T:Brightness&&VG&&PQ:'C:.SeekBar'", action:"getProgress"}).retval.max
267386880
>> device.sendAai({query:"T:Brightness&&VG&&PQ:'C:.SeekBar'", action:aaix("setProgress", max/2)})
{retval: true}
在声音设置中,将所有4个声音音量设置为50%:
>> device.sendAai({query:"C:.SeekBar"}).ids.forEach(
x => {
var max = device.sendAai({elements:[x], action:"getProgress"}).retval.max;
device.sendAai({elements:[x], action:`setProgress(${max/2})`});
}
)
setText (O)
如果节点是文本字段,则将"输入字符串"的值以编程方式输入到输入文本字段中,不涉及Sigma输入法。
对于Android 11或更高版本,如果消息的后缀为"\n"或"\r\n":
用法:
setText([查询], <输入字符串>)
setText([查询], +<输入字符串>)
参数:
输入字符串:默认情况下,在输入字符串之前,文本字段将被清除,如果第一个字母为"+",则将其添加到现有字符串中。对于多行文本字段,请使用"\n"跳到下一行。
返回值:
true或false,表示操作是否成功。
示例:
>> device.sendAai({query:"BP:editable", action:"getText"})
{retval: ['text','text','Number','Email','text password','Person Name']}
>> device.sendAai({query:"BP:editable", action:"setText(T:Number, '100');setText(T:text password, secretWord)"})
{count: 2, list: [{retval: true},{retval: true}]}
>> device.sendAai({query:"BP:editable", action:"getText"})
{retval: ['text','text','100','Email','••••••••••','Person Name']}
>> device.sendAai({query:"BP:editable", action:"getHintText"})
{retval: ['text','text','Number','Email','text password','Person Name']}
>> device.sendAai({query:"TP:textInput&&IX:1", action:"setText('Hello')"})
{retval: true}
>> device.sendAai({query:"TP:textInput&&IX:0", action: "setText('+ World')"})
{retval: true}
>> device.sendAai({query:"TP:textInput", action: "setText('Hello\n')"})
{retval: true}
通过String.fromCodePoint输入表情符号:
>> device.sendAai({query:"TP:textInput",action:aaix("setText", String.fromCodePoint(0x1f60a))})
{retval: true}
或与普通消息混合使用:
>> device.sendAai({query:"TP:textInput", action:aaix("setText",
"hello" + String.fromCodePoint(0x1f643, 0x1f644) + "world")})
{retval: true}
showOnScreen (O)
这将确保第一个节点完全显示在屏幕上,如果关联的UI元素部分显示,则会滚动直到UI元素完全可见在屏幕上。如果节点太大而无法适应可滚动区域,则会生成错误。
用法:
showOnScreen [(<query>)]
返回值:
true表示屏幕已滚动,false表示节点已完全显示在屏幕上,不需要进行任何操作。如果节点太大而无法适应可滚动区域,则返回null。
sleep
此命令将暂停执行指定的毫秒数,用于在执行操作之前进行时间相关的等待。
用法:
sleep(<time>)
参数:
time:指定等待的时间,单位为毫秒。
返回值:
返回true
until
until会根据指定的选项等待特定的条件,如果条件满足,它将返回true,如果超时,则返回带有错误的null。目前支持两种类型:
until([query], gone, <timeout>)
"gone"将等待指定的节点(ML或OQ中的第一个元素)消失。
示例:
以下示例将等待重置按钮消失,支持两种格式:
>> device.sendAai({query:"R:.btn_img_reset_time", action:"until(gone, 20000)"})
{retval: true}
>> device.sendAai({action:"until(R:.btn_img_reset_time, gone, 20000)"})
{retval: true}
until([query], changed, T|D, <timeout>)
将检查直到超时或文本(T)或描述(D)内容发生变化。
示例:
以下示例检查纳斯达克指数是否发生变化:
>> device.sendAai({query:"T:Nasdaq*&&OY:1", actions:["getDescription", "until(changed,D,30000)","getDescription"]})
{count: 3, list: [{retval: '10,989.25'},{retval: true},{retval: '10,988.50'}]}
>> device.sendAai({actions:["until(T:Nasdaq*&&OY:1, changed, D, 30000)","getDescription"]})
{count: 2, list: [{retval: true},{retval: '10,988.50'}]}
其他命令
openApp/restartApp/closeApp 版本 11
指定的应用程序名称可以是包名或应用程序在启动器上显示的名称。如果存在多个匹配的应用程序名称,将使用第一个匹配的应用程序。不允许部分匹配,匹配不区分大小写。不带参数的 "closeApp" 将关闭当前应用程序。"openApp" 和 "restartApp" 可以接受可选的查询和超时参数,在启动应用程序后,它将进入 "waitQuery" 状态,等待查询匹配,然后将控制权返回给调用者。超时参数以毫秒为单位。
已经进行了工作,以确保 openApp 或 restartApp 在应用程序处于稳定状态时返回控制权(渲染完成,等待用户输入)。在终端中尝试 "restartApp" 和 "openApp",如果打开/重启需要很长时间,请使用关联的查询和超时参数。
"restartApp" = "closeApp" + "openApp"
"restartApp" 在自动化脚本中非常有用,因为它总是以已知状态启动应用程序。
"openApp" 将运行应用程序,如果应用程序已经在内存中,它将将应用程序显示在前台;如果应用程序不在内存中,它将重新启动应用程序。"closeApp" 将关闭应用程序(强制关闭)。
用法:
openApp(<name>[,<query>[,<timeout>]])
restartApp(<name>[,<query>[,<timeout>]])
closeApp(<name>)
closeApp
返回值:
retval:成功返回 true,未找到应用程序、无法启动应用程序或查询不匹配时返回 null 并附带 lastError()。
示例:
打开时间测量(以毫秒为单位)
>> device.sendAai({action:"openApp(Skype)"})
{retval: true, timeFindNode: 3040}
>> device.sendAai({action:"openApp(Skype)"})
{retval: true, timeFindNode: 672}
>> device.sendAai({action:"restartApp(Skype)"})
{retval: true, timeFindNode: 2859}
"mySolarEdge" 打开应用程序始终需要很长时间,使用查询和超时参数:
>> device.sendAai({action:"openApp(mySolarEdge,T:Lifetime,15000)"})
{retval: true, timeFindNode: 13098}
openAndroidSetting
将根据指定的设置名称打开 Android 系统设置窗口,设置名称将转换为大写字母,空格将转换为“_”,如果名称尚未添加“_SETTINGS”后缀,则会添加该后缀:
在 https://developer.android.com/reference/android/provider/Settings 上提供部分设置,使用 "listAndroidSetting" 列出此命令中可用的设置。此命令将打开设置窗口。如果找不到该设置,则返回 null。
用法:
openAndroidSetting(<设置名称>)
例如:以下命令打开无线设置:
device.sendAai({action:"openAndroidSetting(wireless)"})
以下命令打开应用程序设置:
>> device.sendAai({action:"openAndroidSetting(manage applications)"})
{retval: true}
"MANAGE_APPLICATIONS_SETTINGS"、"manage applications setting "、"Manage Application" 都是相同的设置。
listAndroidSettings
此命令将以数组形式返回设置列表。
用法:
listAndroidSettings
返回值:
设置的有效列表。
>> device.sendAai({action:"listAndroidSetting"})
{list: ['SETTINGS','ACCESSIBILITY_SETTINGS','ACTION_CONDITION_PROVIDER_SETTINGS', ..., 'ZEN_MODE_PRIORITY_SETTINGS']}
>> device.sendAai({action:"listAndroidSetting"}).list.length
70
setConfig, getConfig, unsetConfig
更改FindNode(或Selector)的值,各种值通过名称来标识,使用“getConfig”获取值,“setConfig”更改值,“unsetConfig”恢复原始值。
getConfig(<name>) → name: <name>, value:<value>
setConfig(<name>, <value>) → retval: true
unsetConfig(<name>) → retval: true
如果找不到名称,将返回 null。
Name | Type | Default | Description |
---|---|---|---|
text:maxQueryLength | Integer | 30 | 替换为“*”的文本和描述的最大长度。 |
navi:maxPageScroll | Integer | 30 | 查找节点时的最大页面滚动次数。 |
navi:scrollVertSteps | Integer | 40 | 页面滑动的步数,较小的值快速但不准确,较大的值慢速但准确。 |
navi:slowScrollSteps | Integer | 100 | 使节点完全可见的滑动步数。 |
sigmaInput:backKeyAutoDismiss | Boolean | true | Sigma输入法,发送额外的返回键以隐藏键盘。 |
selector:defaultTemplate | String | more | 默认模板,如果在查询中未指定模板。 |
selector:allowBqAfterEq | Boolean | false | 如果设置为true,将允许在扩展查询后定义的基本查询作为扩展查询的一部分按顺序执行(从左到右)。 |
命令参考
FindNode 命令参考 |
---|
动作名称 | OQ | 参数 | 输入 | 输出 | ML变化 | 属性 |
---|---|---|---|---|---|---|
Get 命令 | ||||||
getBounds | 是 | - | 所有节点 | ids:[<id>], bounds:[<bounds>] | - | - |
getBoolProp | 是 | - | 第一个节点 | retval:[<prop>] | - | - |
getBoolPropInAncestors | 是 | - | 第一个节点 | retval:[<prop>] | - | - |
getChecked | 是 | - | 一个节点 所有节点 |
retval:<boolean> retval:[boolean|N/A] |
- | - |
getCount | 是 | - | 所有节点 | count:<count> | - | - |
getDescription | 是 | - | 一个节点 所有节点 |
retval:<text> retval:[<text>] |
- | - |
getHintText | 是 | - | 一个节点 所有节点 |
retval:<text> retval:[<text>] |
- | - |
getFocus | 否 | - | 无 | node:<ID> | - | - |
getIds | 是 | - | 所有节点 | ids:[<ID>] | - | - |
getNodes | 是 | [fieldList(S)] | 所有节点 | list:[<info>] | - | - |
getPackageName | 否 | - | 无 | retval:<PackageName> | - | - |
getProgress | 是 | - | 第一个节点 | retval:<rangeInfo> | - | - |
getQuery | 否 | [ignoreText(B)] [ignoreText(B)] |
第一个节点 | query:<query String> | - | - |
getText | 是 | - | 一个节点 所有节点 |
retval:<text> retval:[<text>] |
- | - |
getUniqQuery | 否 | [ignoreText(B)] [IgnoreDesc(B)] |
第一个节点 | query:<query String> | - | - |
查询命令 | ||||||
save/push | 否 | - | 所有节点 | retval:<boolean> | - | - |
load/pop | 否 | - | 无 | retval:<boolean> | 是 | - |
getViewGroup | 是 | [level(I)] | 第一个节点 | count:<count> | 是 | VG |
intersectX | 是 | - | 第一个节点 | count:<count> | 是 | TX |
intersectY | 是 | - | 第一个节点 | count:<count> | 是 | TY |
reduceNodes | 否 | - | 所有节点 | count:<count> | 是 | RN |
sort | 否 | <type(S)> | 所有节点 | retval:<boolean> | 是 | ST |
addQuery | 是 | - | 所有节点 | count:<count> | 是 | - |
newQuery | 是 | - | 无 | count:<count> | 是 | - |
waitQuery | 是 | [duration(I)] | 无 | retval:<boolean> | - | - |
动作命令 | ||||||
click | 是 | - | 第一个节点 | retval:<boolean> | - | - |
click2 | 是 | - | 第一个节点 | retval:<boolean> | - | - |
forEach | 否 | <action|JSON名称> | 所有节点 | retval:<name>:[[...]] | - | - |
function | 否 | <名称 + 参数> | 无 | retval:<boolean> | - | - |
longClick | 是 | - | 第一个节点 | retval:<boolean> | - | - |
nsClick | 是 | - | 第一个节点 | retval:<boolean> | - | - |
repeat | 否 | <次数, 动作|JSON> | 无 | retval:<name>:[[...]] | - | - |
refresh | 是 | - | 所有节点 | retval:<boolean> | - | - |
scrollIntoView | 是 | [方向(S)] | 无 | retval:<boolean> | 是 | - |
sendKey | 否 | <代码(I)> [meta(I)] | 无 | retval:<boolean> | - | - |
setChecked | 是 | <boolean> | 所有节点 | changedCount:<count> | - | - |
setProgress | 是 | <值(F/I)> | 第一个节点 | retval:<boolean> | - | - |
setText | 是 | <文本(S)> | 第一个节点 | retval:<boolean> | - | - |
showOnScreen | 是 | - | 第一个节点 | retval:<boolean> | - | - |
sleep | 否 | <持续时间(毫秒)(I)> | 无 | retval:<boolean> | - | - |
until | 是 | <选项> <参数> | 无 | retval:<boolean> | - | - |
其他命令 | ||||||
version | 否 | - | 无 | retval:<版本号> | - | - |
openApp | 否 | <名称(S)> | 无 | retval:<boolean> | - | - |
restartApp | 否 | <名称(S)> | 无 | retval:<boolean> | - | - |
closeApp | 否 | [<名称(S)>] | 无 | retval:<boolean> | - | - |
openAndroidSetting | 否 | <设置(S)> | 无 | retval:<boolean> | - | - |
listAndroidSettings | 否 | - | 无 | list:[<设置列表>] | - | - |
getConfig | 否 | <名称(S)> | 无 | name: <名称>, value: <值> | - | - |
setConfig | 否 | <名称(S)> <值(*)> | 无 | retval:<boolean> | - | - |
unsetConfig | 否 | <名称(S)> | 无 | retval:<boolean> | - | - |
(S) - 字符串, (I) - 整数, (B) - 布尔值, (F) - 浮点数, (*) - 所有类型 [可选参数] 或 <名称>:[<返回数组>] |
||||||
device/devices.sendAai({query:<查询>, action:<命令>})
device/devices.sendAai({query:<查询>, actions:[<命令>]}) |
支持
请发送电子邮件至 support@sigma-rt.com 寻求支持和提供反馈意见。