FindNode

FindNode 是一个位于核心 Selector 包之上的外壳程序,其目的是找到预期的一个或多个 UI 元素(或可访问性节点),并从中提取信息或执行操作。

Selector 位于 Accessibility 和 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":"{<error message>}"}

TC 提供了 "device.sendAai()" 或 "devices.sendAai()" 来与 FindNode 进行通信:

  • 发送 "params" 对象。
  • 如果 FindNode 在一定时间内未返回,则生成超时错误。
  • 对于某些耗时超过默认超时时间的命令,FindNode 将延长时间以避免超时错误。
  • 自动处理返回值(错误时为 null,值时为非空)。
  • 对于多个设备 "devices",每个设备将在线程中进行处理。

params 对象包含 3 种类型的属性:query、preAction 和 postAction(s)。提供的命令非常丰富,您可以使用它们编写简单的自动化脚本。

据推测,您可以通过使用 "device.sendAai()" 和 "devices.sendAai()" 来完成大部分任务,FindNode 是一个 "handler",另一个 "invoke" handler 可以使用 Java Reflection 访问 UiDevice、UiObject2、AccessibilityNodeInfo 和 InteractionController 中的方法。

限制:这是 AAI 的第一版,它具有一些限制:

  • 仅支持竖屏模式,横屏模式将在下一个版本中引入。
  • 目前无法识别所有内容,例如水平滚动或某些行模式。
  • 将包含更多功能。

查询

大多数 FindNode 的用法涉及查询,它允许用户通过对可访问性节点进行不同类型的有用查询来定位预期的 UI 元素。整个屏幕由许多节点组成,一个节点可以是最小的 UI 元素或包含较小节点的容器,其中许多是不可见的。整个屏幕是从单个根节点开始的树状结构。查询将被执行以获取满足条件的节点,目的是将大量节点减少到一个或少数几个预期的节点,用户可以获取信息或对节点应用操作。

我们开发了一种小型的查询语言(为缺乏更好的词而设),它在设备之间具有可移植性,并且足够强大,可以定位大多数节点。格式为 JSON:

{query:"<key>:<value>||<key>:<value>||…"}

出于历史原因,尽管我们使用 "||" 来分隔不同的条件,但 "||" 是 AND 关系,这意味着它需要满足所有条件才能成为 "匹配节点"。FindNode 不提供 OR 关系,但底层的 Selector 包提供了这种能力。"templates" 使用了该结构。

查询字符串

有 14 个不同的键,它们是:

  • C:类名(S)
  • R:资源 ID(S)
  • D:描述(S)
  • T:文本(S)
  • IT:输入类型(I/S)
  • CC:子节点数(I/S)
  • ID:其他查询返回的节点 ID(S)
  • BI:边界:[x, y] 或 [left, top, right, bottom],都是整数,如果 x 或 y 为 -1,则会被忽略
  • IX:索引(I)
  • OX:x 上的偏移量(I)
  • OY:y 上的偏移量(I)
  • TP:模板(S)
  • ON:一个节点选择(S)
  • LT:行号(I)
  • LB:行号(I)

所有这些查询都是可选的,您可以选择任何字段,如果您正在对屏幕进行查询。"(S)" 代表字符串,"I" 代表整数,"[x, y]" 代表数组。I/S 可以接受整数和字符串。字符串更强大。

键不区分大小写,我们使用大写来区分键和值。

查询中的特殊字符

对于像 inputType 或 childCount 这样的数字,它可以接受数字或字符串:

  • <number>、"<number>" 或 "=<number>":精确匹配数字。
  • "> <number>":匹配大于 <number> 的数字。
  • "< <number>":匹配小于 <number> 的数字。

字符串可以是以下之一:

  • "<string>":对完整文本进行精确匹配,区分大小写。
  • "(?i)<string>":不区分大小写进行匹配。
  • "*<string>":匹配以字符串结尾,例如 "*Group"。
  • "<string>*":匹配以字符串开头,例如 "android.widget.*"。
  • "*<string>*":匹配任意子字符串 "*widget*"。
  • "/<regex>/":匹配任意正则表达式。例如 "/ImageView|TextView/"

"!" 在字符串开头用作 NOT。"!0" 表示非零。"C:!/Layout/" 表示非布局类。

为了简化示例,标准前缀 {"cmd": "action","action": "findnode", "params":{…}} 将被省略,我们将在 "param" 对象中列出示例,并使用 JavaScript 对象表示法来减少双引号的数量。

快捷方式

有两个快捷方式可用于类名 (C) 和资源 ID (R),使用 "." 作为常用前缀的替代:

  • 类名 (C):使用 "." 作为替代,表示 "android.widget."。例如 ".Button" = "android.widget.Button"。
  • 资源 ID (R):使用 "." 作为替代,表示 "<package name>:id/"。例如 ".text_connect" = "com.sigma_rt.totalcontrol:id/text_connect"。

Bound In (BI):

Bound In 接受屏幕坐标或边界,并根据以下条件返回节点。主要用于屏幕操作,如 UI Explorer 或调试目的。

  • [x, y]:将返回边界包含 (x, y) 的节点。
  • [x, -1] 或 [-1, y] 其中之一为 -1:如果 x = -1,则忽略 x 并返回边界包含 y 的节点,类似地,对于 y = -1,只会匹配 x。就像在 y 和 x 位置上绘制一条水平/垂直线,接触线的节点将被返回。
  • [x1, y1, x2, y2]:将匹配边界在 (x1, y1) - (x2, y2) 矩形范围内的节点。

最后两种模式在 8.0-u40 中受支持。

示例:

{query:"BI:[-1, 1000]||IX:-1", postAction:"click"}
{query:"BI:[0, 0, 1000, 1000]", postAction:"getBounds"}

Index (IX), Offset (OX/OY) and Line (LT & LB)

这些功能是非常有用的:

Index:IX:<number>。.根据从零开始的位置,从生成的节点列表中返回节点。IX可以是负值,在这种情况下,位置顺序相反(-1是最后一个节点)。

Offset:OX:<integer> 和 OY:<integer>。使用偏移量根据结果节点查找相邻节点,OX查找水平位置,正整数向右移动,负整数向左移动。OY同理,正整数向下移动,负整数向上移动。如果两个选项都存在,则这些选项将根据查询上的位置应用。例如:“OX:1 | | OY:2”将在垂直偏移之前应用水平偏移,其中“OY:2 | | OX:1”将在水平偏移之前应用垂直偏移。顺序对于定位节点的不对称排列非常重要。

Line:LT:<Integer> 和 LB:<Integer>。对于使用行模式的查询,FindNode将分析屏幕,尝试定位可滚动区域之外的UI元素,行模式将UI元素分组为一系列行,每行可以有一个或多个节点。许多应用程序在屏幕的顶部和底部都有固定的UI元素,可以通过使用诸如“LT:1 | | IX:2”(顶行的第二个节点)或“LB:1 | | T:Chats”等查询来轻松定位,并将"Chats"作为text。

  • 对于计算器等不可滚动的应用程序,LT 和 LB 将始终返回 null。
  • 在状态行的底部和可滚动区域的顶部之间被视为顶部区域,LT从1开始,“LT:1”是第一行。LT:-1是该区域的最后一行。
  • 可滚动区域的底部和导航栏的顶部之间被视为底部区域。同样,LB 从 1 开始,LB:1 是第一行。LB:-1 是该区域的最后一行(也是应用程序的最后一行)。
  • 使用越界行号将得到空值。(例如,底部有两行,LB:3将得null)。
  • 以后将支持中间内容以行来解析。
  • 如果一个节点的高度与同一行上的两个垂直节点的高度相似,则较低的节点将放置在下一行中。

这可能导致复杂的查询,例如

{query:"LB:-1||IX:2||OX:1||OY:-1"}

对于行模式,第一个节点的“intersectY”应用于每条直线,通常可以,但在以下情况下,它将返回所有节点:

所以“LT:1”将返回所有 6 个节点。

对于不可滚动的应用程序,“LB”将返回 null,但获取节点并不难:

您可以使用文本作为锚点:

>> device.sendAai({query:"T:Speed||ON:last", postActions:["intersectX", "getText"]})
{count: 2, list: [{count: 4},{retval: ['Speed','Video','VPN','Map']}]}
>> device.sendAai({query:"T:Speed||ON:last||OY:-1", postActions:["intersectX", "getIds"]})
{count: 2, list: [{count: 4},{count: 4, ids: ['181ce','19c15','1b65c','1d0a3']}]}

Template (TP)

由于没有更好的名称,我们开发了几个预定义的模板,这些模板是内置的FindNode,默认模板是“more”。

  • all:无约束,返回所有节点。
  • more:删除布局节点。如果未定义模板或查询为空,则这是默认值。
  • basic:仅保留more模式下的所有不包含子节点的node。
  • anyText:搜索具有文本或描述内容的节点。
  • textInput:用于尝试查找输入框。
  • findText:在文本或描述中查找某些文本,要搜索的文本需要在 JSON“text”中定义。例子:

例子:

{query:"TP:findText", text:"OK", postAction:"click"}

单节点选择 (ON)

有些查询需要多个节点(例如检索股票报价),有些时候需要单个节点,例如,单击节点或输入文本,FindNode提供“IX”从特定位置选择节点,ON提供多种方法选择出一个节点。

这里有几种选项:

  • first:从匹配列表中取出第一个节点(类似于 IX:0)。
  • last:从匹配列表中取出最后一个节点(类似于 IX:-1)。
  • min:从匹配列表中选择最小的节点(基于面积)。
  • top:对于重叠节点,选择最顶部的节点。对于 使用x 和 y 坐标的“BI”查询很有用。

ON 和 IX 不可以同时使用。

Query 执行的先后顺序

下面是query执行的先后顺序:

  1. 所有节点先进行行模式操作 (LT/LB)。
  2. Template (TP)。
  3. Index (IX) 或者单节点 (ON).。
  4. Offset OX and OY.
  5. 如果有多个节点,排序。

“elements”属性

“elements”接受节点ID数组,节点ID必须是屏幕上显示的有效节点ID,否则将生成错误。当指定“elements”时,将不执行搜索,但IX、OX、OY、ON仍能工作。它对于调试目的以及对同一节点应用多个操作的脚本非常有用。每个带有“elements”的查询将遍历所有节点以定位相应的节点,尝试在数组中放置多个ID,而不是一次一个。

device.sendAai({elements:["1234a", "5678b"], postAction:"getNodes"})

var ids = device.sendAai({query:"C:.Button"}).ids;
var retval = device.sendAai({elements:ids, postAction:"getNodes"});

同步动作

为了保证actions的同步已经付出了很大的努力,当一个“click”被调用的时候,不仅保证click被同步按下,还需要等待一些事件来确保按下事件真的执行成功了。 FindNode 提供了几种执行点击的方法:

  • “click”:它应该大部分时间都可以工作。
  • “clickForNewWindow”:如果单击打开一个新窗口并需要时间来显示,请使用此操作。
  • “waitSelector”:如果新窗口需要很长时间才能完成渲染,使用该命令检查节点是否可用,添加postAction:“click”,当找到节点并准备好时会点击。
  • "waitQuery" 可以包含在 postActions 中,等待节点出现后再执行操作。

弹出窗口

有两种类型的弹出窗口,它们看起来相同,但实现方式不同:

  • 弹出窗口更改节点树的根节点,这将被正确处理(例如Whatsapp)。
  • 弹窗在节点树中新建了一个分支,生成的节点会是主窗和弹窗的累积节点,可能会找错节点。 FindNode 将在发生这种情况时监视事件,并在打开和关闭弹出窗口时正确预测节点树的顶部分支。启动 FindNode 时,请确保应用程序未处于弹出模式(Google Mail、Google 搜索)。

对于第二种弹窗,FindNode 可以识别一些弹窗(很多谷歌应用程序都使用这种弹窗),如果弹窗破坏了 FindNode,它将表现出以下行为之一:

  • 在查询中未找到节点(甚至是“TP:all”)。
  • 部分节点显示在 UI Explorer 中。
  • 从弹出窗口和主窗口中累积节点。 UI Explorer 会显示很多没有意义的边界(主窗口中的节点)。

发生这种情况时,关闭(从内存中删除)并重新打开应用程序,如果弹出窗口不起作用,请联系支持。

这个版本,水平滚动(左右滑动显示新页面)将不起作用(例如Slack),它将在下一个版本中提供。

测试

通过 Query 找到节点并不是很困难,你需要的是“UI Explorer”和脚本执行终端:

  • 节点 ID 很重要。
  • 脚本终端: 通过 device.sendAai({query:"…"}) 去找到对应的nodes. 有许多搜索选项,查找节点的方法不止一种。
  • UI Explorer:
    • 可以看到node的边框。
    • 查找类名、资源ID、描述和文本。可以帮助您开发查询语句。
    • UI Explorer中的“代码”是定位节点的最佳方式,而不是最优化的方式。如果节点没有分配资源ID或文本(使用索引“IX”有问题),则可能会中断,有关更多信息,请参阅postAction“getUniqQuery”。
  • 脚本终端: 如有疑问,请使用 device.sendAai({elements:["<ID>","<ID>"...], postAction:"getNodes"}),这将帮助您获取更多信息。
  • 从使用“device.sendAai”的一个设备开始,通过“devices.sendAai”更改为多个设备,您可以通过Device.searchObject()获取到多个设备对象(例如,所有设备、组中的设备、特定设备。

Query 实战1

在TC中,用的最多的query为{}:

var output = device.sendAai({})

这将返回屏幕上的大多数节点,默认返回的是节点ID列表:

{count: 89, ids: ['80006cbe','7440','7bc2','7f83','8344','8705','8ac6','8e87','1f6e7',…]}

你还可以做其他的事情,就像下面的这样:

{query:"CC:!=0"} 或 {query:"CC:>0}:子节点个数大于0的nodes。

{query:"IT:>10000"},inputType大于10000的nodes。

{query:"CC:!=0||IT:>10000"},子节点个数大于0 并且 inputType大于10000的nodes。

{query:"TP:textInput||IX:2", postAction:"setText", input:"Hello"},找到第三个输入框并在输入“Hello”。

{query:"T:Input text here", postAction:"setText", input:"Hello"},找到里面默认值为“Input text here”的输入框:输入框并在输入“Hello”。

{query:“C:.TextView||T: 联系人”} 点击类名为TextView,并且text字段为”Contacts“

{elements:["abcd", "123e", "234f"], query:"IX:1||OY:1", postAction:"click"}

Query 实战2

示例 1:在屏幕底部查找节点:

>> device.sendAai({query:"LB:-1", postAction:"getText"})
{retval: ['Chats','Calls','Contacts','Notifications']}

通过使用偏移量 (OX)、索引 (IX) 或诸如“T:”之类的查询,您几乎可以定位每个节点,例如点击“Contacts”图标,您可以通过下面的 3 个示例都可以达到目的:

>>  device.sendAai({query:"LB:1||IX:2", postAction:"click"})
{retval: true}
>> device.sendAai({query:"LB:-1||T:Contacts||OY:-1", postAction:"click"})
{retval: true}
>> device.sendAai({query:"LB:-1||IX:0||OX:2||OY:-1", postAction:"click"})
{retval: true}

行模式的目的是控制顶部/底部的图标。与“Charts”图标相交的红色计数器将被排除在外。所以对于图标行,它是 4 个节点而不是 5 个节点:

>>  device.sendAai({query:"LB:1"})
{count: 4, ids: ['14735','17080','18ac7','1a50e']}

如果您想要得到计数器上面的数字,请使用其他查询(IX:-1 是为了防止在屏幕上找到其他“聊天”),底部的第二行将起作用,因为偏移量不会忽略相交的节点:

>>  device.sendAai({query:"T:Chats||IX:-1||OY:-1||OX:1", postAction:"getText"})
{retval: '1'}
>> device.sendAai({query:"LB:-1||T:Chats||OY:-1||OX:1", postAction:"getText"})
{retval: '1'}

示例 2:从 关于中获取信息:

获取信息:

>> device.sendAai({query:"T:Model name||OX:1", postAction:"getText"})
{retval: 'Galaxy S10+'}

或者,您可以使用 UiElement 类:

>> var obj = UiElement.findObject(device, "T:Model name||OX:1")
UiElement: 3d012
>> obj.getText()
Galaxy S10+

示例 3:假设您想从 Yahoo Finance 获取股票代码的首页:

device.sendAai({query:"R:.ticker", fields:"T", postAction:"getNodes"}).list.forEach(
    function(p) {
    print(p.text + ":" + 
        device.sendAai({query:"OX:1||T:" + p.text, fields:"D", 
            postAction:"getNodes"}).list[0].description);
})
INTC:53.14
WMT:142.00
XOM:65.93
BIDU:146.53
PEP:173.23

示例 4:在 Skype 文本框中输入文本:

输入文字前:

输入文字后:

这可以通过一条命令来完成所有操作:

var input = "Hello";
device.sendAai({query:"LB:1||TP:textInput", postActions:[aaix("setText", input), "addQuery('OX:2')", "click"]});

aaix("setText", input) 将返回 'setText("Hello")',比 'setText("'+input+'")' 好得多,对多个参数很有用。

下面的函数找人,点击,发消息,返回。 需要两个“返回”键,第一个返回键是关闭键盘,第二个返回键是返回主页面。

function sendAai(device, obj) {
    var retval = device.sendAai(obj);
    if (retval == null) {
        throw "Error on: " + JSON.stringify(obj) + ":" + lastError();
    }
    print("$ " + JSON.stringify(obj) + "\n" + JSON.stringify(retval));
    return retval;
}

function sendSkype(device, name, text) {
    sendAai(device, {query:"T:" + name, preAction:"scrollToView", postAction:"click"});
    sendAai(device, {query:"TP:textInput", postActions:[aaix("setText", text), "addQuery('OX:2')", "click", "sendKey('back')", "sendKey('back')"]})
}

sendSkype(device, "John", "I will get back to you");

示例 5:在第一个示例中,您可以使用 "T:<text>" 和偏移量来单击图标,某些应用程序不提供文本:

由于这是显示在最后一行,我们可以使用“LB:-1”返回节点,然后使用“IX”从节点列表中定位单个节点,点击搜索图标:

device.sendAai({query:"LB:-1||IX:2", postAction:"click"})

示例 6:在某个聊天窗口中,它显示了姓名(或电话号码)。

>> device.sendAai({query:"LT:1"})
{ids: ['1637ca','163f4c','165d54','166115','1664d6']}

返回:

>> device.sendAai({query:"LT:1||IX:0", postAction:"click"})
{retval: true}

获取名字:

>> device.sendAai({query:"LT:1||IX:1", postAction:"getText"})
{retval: '86278'}

示例 7:轻松找到并单击。

要单击箭头,请使用:

>> device.sendAai({query:"T:Channels||OX:1", postAction:"click"})
{retval: true}

Query 实战3

示例 1:考虑 UI Explorer 中的以下计算器:

使用这个计算器可以很简单:

function calc(device, app, resultQuery, formula) {
    device.runAppSync(app);
    formula.split("").forEach((n)=>device.clickSync(n));
    return device.aaiGetText(resultQuery)[0].text;
}
var result = calc(
    Device.getMain(),
    "com.dalviksoft.calculator",
    "R:*result",
    "1230÷567="
);
print(result);

这将为您提供 1230÷567 的结果。

要检索所有数字按钮:

>> device.sendAai({query:"T:/[0-9]/||C:.Button", preAction:"doSort", postAction:"getText"})
{retval: ['7','8','9','4','5','6','1','2','3','0']}

检索数字 5-9,排序基于节点的位置:

>> device.sendAai({query:"T:/[5-9]/||C:.Button", postActions:["sort","getText"]})
{count: 2, list: [{retval: true},{retval: ['7','8','9','5','6']}]}

要检索算术运算 + DEL 按钮等 5 个按钮,有几种方法可以做到,一种方法是使用 x 上的边界:

function getNodes() {
    var ret = device.sendAai({query:"T:DEL||C:.Button", postAction:"getNodes", fields:"B"});
    if (ret == null) {
        print("Error:" + lastError());
        return null;
    }
    var rect = new Rect(ret.list[0].bounds);
    var ret = device.sendAai({query:"BI:[" + rect.centerX() + ",-1]||C:.Button"})
    if (ret == null) {
        print("Error:" + lastError());
        return null;
    }    
    return ret.ids;
}

这里可以手动进行2+3的操作,不要每次都搜索节省CPU,使用"elements","="没有字符串表示,所以使用OX:1 from "0"。

var opIds = getNodes();
var numIds = device.sendAai({query:"T:/[0-9]/||C:.Button", preAction: "doSort"}).ids;
device.sendAai({elements:numIds, query:"IX:-2", postAction:"click"});
device.sendAai({elements:opIds, query:"IX:2", postAction:"click"});
device.sendAai({elements:numIds, query:"IX:8", postAction:"click"});
device.sendAai({query:"T:0||C:.Button||OX:1", postAction:"click"});

或者,您可以在文本字段中输入文本。可以使用 UiElement 来做到这一点:

var obj = UiElement.findObject(device, "TP:textInput");
var equal = UiElement.findObject(device, "T:0||C:.Button||OX:1");
obj.setText("2+3");
equal.clickSync();
var result = obj.getText();
print(result);

示例 2

使用“选中”来操作上述复选框。

将 Sunday 从 on 改为 off,查看前后的值:

>> device.sendAai({query:"D:Sunday||C:.CheckBox", postActions:["getChecked", "setChecked(false)", "getChecked"]})
{count: 3, list: [{retval: true},{checked: false, retval: true},{retval: false}]}

"intersectX" 将返回 Y 坐标与匹配节点相交的所有节点,在这种情况下为所有复选框。使用“setChecked()”设置所有节点为真/假(开/关),它只适用于可检查的节点,没有可检查的节点将被忽略。如果需要更改值,请仅单击复选框。

>> device.sendAai({query:"D:Sunday", postActions:["intersectX","getIds","setChecked(true)"]})
{count: 3, list: [{count: 7},{count: 7, ids: ['9c52d','9c8ee','9ccaf','9d070','9d431','9d7f2','9dbb3']},{changedCount: 7, retval: true}]}

示例 3:OY 和 OX 的顺序很重要,下面是开发者选项:

“Disable adb authorization timeout”右侧没有节点,因此首先在 X 上应用偏移量将变为空:

>> device.sendAai({query:"T:Disable adb authorization*||OX:1||OY:1"})
null
>> lastError()
Offset out of range

如果首先在 Y 上应用偏移量将获得正确的节点:

>> device.sendAai({query:"T:Disable adb authorization*||OY:1||OX:1"})
{count: 1, ids: ['40e9c4']}
>> device.sendAai({query:"T:Disable adb authorization*||OY:1||OX:1", postAction:"click"})

示例 4:您可以使用一个 AAI 命令输入 emoji 符号,这里是 Messenger 的示例:

>> device.sendAai({query:"LB:-1||TP:textInput||OX:1", postActions:["click", "addQuery('OY:1')","intersectX", "addQuery('IX:3')", "click", "addQuery('OY:1')", "intersectX", "addQuery('IX:0')", "addQuery('OX:2||OY:3')", "click", "sendKey('back')"]})
{count: 11, list: [{retval: true},{count: 1},{count: 5},{count: 1},{retval: true},{count: 1},{count: 7},{count: 1},{count: 1},{retval: true},{retval: true}]}

要点击表情符号,需要点击 3 次:

query:"LB:-1||TP:textInput||OX:1" - 选择最后一行文本字段右侧的图标(蓝色图标)。

"click" – 单击节点以打开页面。

"addQuery('OY:1')" – 使用 "OY" 跳到下一行。在这种情况下,它很可能会落入 emoji 图标中,为了安全起见,我们使用以下方式确保找到 emoji 图标。

"intersectX" – 将返回该行的节点:

"addQuery('IX:3')" – 选择该行中的第 4 个节点(表情符号图标)。

"click" – 单击节点以更改页面。

“addQuery('OY:1')”——转到下一行。

"intersectX" – 返回该行的节点:

"addQuery('IX:0')" – 找到表情符号的第一个节点(左上角)作为参考节点。

"addQuery('OX:2||OY:3')" – 第 4 行和第 3 个节点的兴趣。

"click" – 单击节点以输入选定的表情符号。

"sendKey('back')" – 发送返回键。

想知道选择或生成了哪个或哪些节点,打开 UI Explorer 并添加“getIds”以在 UI Explorer 中找出节点 ID。

preAction、postAction 和 postAction

FindNode 开发查询的主要目的是查找节点,“preAction”发生在搜索“postAction”之前,“postActions”发生在搜索或由 preAction 提供的节点之后,执行的操作将在结果节点上。对于需要一个节点的操作(例如“单击”),将使用结果节点中的第一个节点(ON、IX、OX 和 OY 可以更改)。pre 和 post 操作中有许多命令,一些命令需要额外的参数(参数将括在括号中)。目前,支持以下类型的参数:

  • -"String" 或 'String':用单引号或双引号括起来的字符串。
  • -true/false:布尔值。
  • -Number:带或不带小数的数字(取决于操作的类型)。

几个例子:

postAction:"getNodes('C,R')"

如果要在操作命令中使用变量,请使用以下 3 个选项之一:

var input = "Hello"
>> "setText('" + input + "')"
setText('Hello')
>> 'setText("' + input + '")'
setText("Hello")
>> aaix("setText", input)
setText("Hello")

postActions:[aaix("setText", input), "addQuery('OX:2')", "click"]

preAction

“preAction"用于下面的情形之一。如果结果输出是一个或多个节点,然后这些节点将进行 postAction操作, 否则将把输出结构直接给给调用者。

  • 向现有查询添加更多条件(例如 getCount 或 doSort)。
  • 获取无法通过查询完成的节点(例如getFocus)。
  • 与节点或查询无关的操作或信息(例如 waitForWindowUpdate、sendKey)。


bgQuery:*

这些命令用于后台查询,TC 使用它来支持“addQueryListener()”,当找到匹配时会调用回调。



getFocus

返回接收焦点的节点(通常是文本输入框),如果没有节点获得焦点,则返回 null 并返回错误。如果找到节点,它将继续进行 postAction。

返回:如果没有找到焦点,则返回 null,否则将处理 postAction(s)。

示例:从现有行的下一行进行输入

{preAction:"getFocus", query:"OY:1", postAction:"setText('Hello')"}


getCount

返回匹配节点的数量,而不是节点 ID 的列表。应该在 postAction 中,但是,getCount 比 list 节点 ID 快,这个命令用来确定 count 或者节点搜索。返回:返回匹配节点的数量,不会执行 postAction(s)。

返回:返回匹配节点的数量,不会执行 postAction(s)。

例子:

>> device.sendAai({preAction:"getCount", query:"R:.ticker"})
{count: 6}


scrollToView

接受查询字符串(在“query”中),将通过上下滚动尝试匹配查询,当找到一个或多个节点时,将进行postAction,如果找不到匹配,则返回错误。滚动将使用向上/向下翻页,最多 10 页,滚动可能需要一些时间,该命令会自动延长“sendAai”中的超时时间,直到完成。确定如何执行滚动的几个选项,postAction 中的“showOnScreen”可用于确保节点完全可见。失败时,要返回原始位置,请反向运行 scrollToView。

preAction:"scrollToView()"

参数:

<from-direction> 可以是以下之一,如果不指定,默认为“fromCurrentDown”。

“currentDown”:从当前位置开始向下滚动搜索。

“currentUp”:从当前位置开始向上滚动搜索。

“topDown”:快速滚动到页面顶部并通过向下滚动进行搜索。

“bottomUp”:快速滚动到页面底部并通过向上滚动进行搜索。

返回:只有在找不到节点时才会返回 null,否则将继续执行 postAction 或 postActions。

例子:

{query:"T:Bob", preAction:"scrollToView('fromTop')", postActions:["showOnScreen", "getNodes('all')", "click"]}


waitForWindowUpdate

等待窗口内容更新事件发生。如果指定了窗口的包名,但当前窗口没有相同的包名,则函数立即返回。

waitForWindowUpdate(<package name>, <timeout>)

waitForWindowUpdate(<timeout>)

参数:

"packageName: <string>" (可选):如果没有指定包名,则使用当前包名。

"timeout:<integer>" (可选):等待超时,以毫秒为单位。如果未指定超时,则使用 3000 毫秒。最大超时限制为 20 秒。

返回:

如果发生窗口更新,则为 true,如果超时或当前窗口没有指定的包名称,则为 false。



waitSelector

在这种模式下,如果对指定“query”的搜索失败,它将每隔 500 毫秒重试一次,直到找到节点并继续,如果超时,它将返回错误。

waitSelector(<timeout>)

参数:

timeout :超时时间,以毫秒为单位。“timeout”的限制不超过 20 秒。

例子:

device.sendAai({query:"T:Done", preAction:"waitSelector(10000)"})

postAction 和 postActions

搜索后(或来自preAction的节点),如果搜索不到,就会产生错误。如果指定了 preAction:"doCount",则返回匹配数。匹配的节点将可用于“postAction”或“postActions”,这些命令将获取结果节点并对它们执行操作,某些操作只接受一个节点,将应用第一个节点。“postAction”将接受一个命令并将输出作为 JSON 对象返回。“postActions”将接受一个命令数组并以 JSON 对象的形式返回一个输出数组。一些命令可以与多个节点(M)一起工作,一些命令生成更多节点(相交),如果命令只在一个节点(O)中工作,则将使用第一个节点。对于单节点操作,如果不需要第一个节点,请使用带 IX、ON 的 addQuery来改变它。postActions 顺序很重要。

device.sendAai({query:"T:OK||IX:-1", postAction:"click"});
device.sendAai({query:"R:.tickers", postActions:["refresh", "getNodes('T,D')"]});
device.sendAai({query:"R:.tickers", postActions:["getNodes", "sortX", "getNodes"]});

请参阅上面的示例以查看实际的 postAction/postActions。



getIds (M)

如果没有定义 postAction,这是默认的 postAction,该命令将返回匹配节点列表和匹配节点的数量:

返回: 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']}


getNodes (M)

这用于检索节点的信息。这些字段可以确定要显示的内容。“getNodes”将在检索信息之前刷新节点。

getNodes

getNodes(<fields>)

参数:

fields:可选的“fields”用于定义要返回的字段,“fields”中的多个字段用逗号分隔。有效的字段标识符是:

  • P:包名(S)
  • C:类名(S)
  • R:资源 ID (S)
  • D:描述(S)
  • T:文字(S)
  • IT:输入类型(I)
  • CC:子节点计数(I)
  • RI:RangeInfo,提供有关SeekBar(滑块)等widget的类型,最小值,最大值,电流的更多信息,使用“setProgress”命令更改值。
  • BP:节点中的所有布尔属性。
  • B:[左上][右下]定义节点区域。(S)

返回:节点信息的计数和数据信息数组。

>> device.sendAai({query:"T:/[0-9]/", postAction:"getNodes('R,T')"})
{count: 10, list: [{id: '98d1', resourceId: '.button_seven', text: '7'},{id: '9c92', resourceId: '.button_eight', text: '8'},{id: 'a053', resourceId: '.button_nine', text: '9'},{id: 'a7d5', resourceId: '.button_four', text: '4'},{id: 'ab96', resourceId: '.button_five', text: '5'},{id: 'af57', resourceId: '.button_six', text: '6'},{id: 'b6d9', resourceId: '.button_one', text: '1'},{id: 'ba9a', resourceId: '.button_two', text: '2'},{id: 'be5b', resourceId: '.button_three', text: '3'},{id: 'c5dd', resourceId: '.button_zero', text: '0'}]}


getBounds(M)

返回节点的边界(格式:[left, top, right, bottom]),它返回 2 个数组,一个具有所有 ID,另一个具有所有边界。

返回: bounds数组,每个bounds是一个4元组数组:[x1, y1, x2。y2]。

>> device.sendAai({query:"T:/[0-9]/", postAction:"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']}


refresh (M)

accessibility节点信息被缓存,如果屏幕已经更新,缓存信息可能不准确,使用“

refresh”强制重新读取节点信息。

返回:返回计数和真(总是返回真)

例子:

>> device.sendAai({query:"T:/[0-9]/", postAction:"refresh"})
{count: 10, retval: true}


click和longClick (O)

节点上的各种点击,这是同步点击,当控件返回时会保证屏幕开始刷新。

  • click:在节点中心单击。
  • longClick:长按(500ms)模拟长按。
  • click2:点击节点中的随机位置。

返回: true或false表示操作是否成功。

例子:

>> device.sendAai({query:"T:5", postAction:"click"})
{count: 1, retval: true}


clickForNewWindow (O)

通常“click”就足够了,但如果单击会打开一个新窗口并需要一段时间才能达到稳定状态,请使用此命令。 此命令等待比“click”更严格的事件。 如果此命令不起作用,请使用“waitSelector”。 如果单击没有创建新窗口,请不要使用此命令。

clickForNewWindow

返回:true或false表示操作是否成功。

例子:

device.sendAai({query:"T:Scan Now", postAction:"clickForNewWindow"})


showOnScreen (O)

这将确保节点完全出现在屏幕上,如果关联的 UI 元素部分显示,它将滚动直到 UI 元素完全出现在屏幕上。根据 UI 的设计方式,它可能会移动到容器的顶部。

返回: true表示屏幕已经滚动,false表示在屏幕上找到完整节点,什么也不做。



setText(O)

如果节点是文本字段,它将输入所需的字符串值。

setText(<input string>)

参数:

输入字符串:默认情况下,输入字符串之前会清除文本字段,如果第一个字母是“+”,它将添加到现有字符串。对于多行文本字段,使用“\n”跳到下一行。

返回: true或false表示操作是否成功。

例子:

>> device.sendAai({query:"TP:textInput||IX:1", postAction:"setText('Hello')"})
{count: 1, retval: true}
>> device.sendAai({query:"TP:textInput||IX:1", postAction: "setText('+ World')"})
{count: 1, retval: true}


sort/sortX/sortY

根据边界对节点进行排序:

  • "sort":从左上到右下对 y 和 x 进行排序。
  • “sortX”:忽略 Y,从左到右对 X 进行排序。
  • “sortY”:忽略 X,从上到下对 Y 进行排序。


reduceNodes(M)

这是 AAI 的核心功能,reduceNodes 接受节点 ID 列表,旨在将节点数量减少到可见节点。“TL”、“BL”、“OX/OY”和“intersect”都使用reduceNodes。请注意,如果较大的节点完全包含较小的节点,reduceNodes 将选择较小的节点,如果“.Button”节点包含较小的“.TextView”节点,则“.TextView”节点将被选中“.Button”节点, .TextView 节点上的“单击”仍然有效。请参阅 UI Explorer“优化”。

返回:

retval: true/false 表示 reduceNodes 是否减少了任何节点。

count:节点数(如果为false,则保持与原始节点相同的节点)。使用“getIds”检索生成的节点 ID。

例子:

>> device.sendAai({}).count
217
>> device.sendAai({postAction:"reduceNodes"}).count
63

对于上面的屏幕,尝试获取与 相交的节点,reduceNodes 将节点从 29 个减少到 3 个。

>> var rect = new Rect(device.sendAai({query:"T:Schedule", postAction:"getNodes", fields:"B"}).list[0].bounds)
>> device.sendAai({query:'BI:[-1,'+rect.centerY()+']', postActions:["getIds", "reduceNodes", "sortX", "getIds"]})
{count: 4, list: [{count: 29, ids: ['7fbc','873e','8aff','9281','9642','a185','b089','b80b','bf8d','c34e','c70f','45b03','ce91','d613','d9d4','dd95','146b1','e156','e517','e8d8','ec99','28205','285c6','28987','28d48','2a78f','2ab50','2af11','2b2d2']},{count: 3, retval: true},{retval: true},{count: 3, ids: ['28987','2ab50','2af11']}]}


setProgress

设置滑动条的值,可以通过“getNodes('RI')”获取最小值、最大值、类型、值,

setProgress(<number>)

参数:

:整数或小数来设置值。

返回:

true/false:如果操作成功。

示例:将滑块设置为 50%:

>> device.sendAai({query:"C:.SeekBar", postActions:["getNodes('RI')", "setProgress(50)", "getNodes('RI')"]})
{count: 3, list: [{count: 1, list: [{id: '4cf6e', rangeInfo: {current: 17, max: 100, min: 0, type: 0, typeString: 'int'}}]},{retval: true},{count: 1, list: [{id: '4cf6e', rangeInfo: {current: 50, max: 100, min: 0, type: 0, typeString: 'int'}}]}]}


getChecked (O) & setChecked (M)

这些命令适用于具有“可检查”节点的节点。通常,切换控件是可检查的,复选框或单选按钮,类的示例可以是“.Switch”或“.Checkbox”。

setChecked(true|false)

getChecked

参数:

“getChecked”将返回节点检查状态的真或假。如果节点不是一个多选框,则生成错误。

"setChecked(true|false)",由于没有设置checked的权限,如果需要更改值,它会点击节点切换值。它接受多个节点,将忽略不可检查的节点。

返回: getChecked 在可检查节点的状态上返回 true 或 false。setChecked 返回“changedCount”,有多少可检查节点已被更改。



addQuery (O|M)

addQuery 在结果节点上执行子查询,这样您可以从 addQuery 派生节点并对其进行操作。例如,您可以在一个查询中输入文本并单击发送(例如 addQuery('OX:2'))。不支持 LT 和 LB。您可以添加尽可能多的查询。

addQuery(<string>)

参数:

查询字符串:与主查询行完全相同的查询。

返回:匹配节点的数量。

示例:大多数聊天程序需要输入文本并单击发送按钮,使用 addQuery,您可以在一个命令中完成:

device.sendAai({query:"TP:textInput", postActions:["setText('Hello')","addQuery('OX:2')","getIds","click"]})

在 Skype 中,如果您想在不使用“LB”的情况下单击“通话”图标:

>> device.sendAai({query:"TP:anyText||IX:-1", postActions:["intersectX", "addQuery('T:Calls||OY:-1')", "click"]})
{count: 3, list: [{count: 4},{count: 1},{retval: true}]}


intersectX/intersectY (O)

这些命令使查询更有趣,这两个只是生成更多节点的命令。它们以一个节点为参考,返回所有水平(intersectX)和垂直(intersect)相交的节点。该节点可以是相交路径上的任何节点,它将利用参考节点边界(intersectX 为 top/bottom,intersectY 为 left/right)来搜索相交节点。此命令将更改内部匹配列表,因此您可以使用“addQuery”设置约束或操作以对其进行操作。该命令将占用一个节点并生成更多节点,更多示例请参见上面的“Query实战3”。

返回:

true /false:如果操作成功。

例子:

>> device.sendAai({query:"TP:textInput", postActions:["getIds", "intersectX", "getIds"]})
{count: 3, list: [{count: 1, ids: ['379ea6']},{count: 5},{count: 5, ids: ['37845f','379ea6','37a9e9','37c06f','37d6f5']}]}


waitQuery

此命令可用于等待某个节点出现在屏幕上,然后再进行其他发布操作。 此命令等待指定的查询和超时。

waitQuery(<query string>, <timeout>)

waitQuery(<query string>)

参数:

查询字符串:与主查询行完全相同的查询。

timeout:可选,如果没有指定,

返回:如果找到匹配则返回true,如果没有找到则生成错误。

例子:



sleep

此命令将等待指定的毫秒数,这对于简单的等待操作很有用。

sleep(<time>)

参数:

time:指定等待的时间(以毫秒为单位)。

返回:返回真



getText (O|M)

如果您不打算知道节点 ID以及其他信息,那么他会比“getNodes('T')”用起来更简单。它将检测一个或多个节点。

返回:如果找到多个节点,则返回一个文本数组,如果找到一个节点,则返回文本。

例子:

>> device.sendAai({query:"LB:2", postAction:"getText"})
{retval: ['Chats','Calls','Contacts','Notifications']} 
>> device.sendAai({query:"LB:2||IX:1", postAction:"getText"})
{retval: 'Calls'} 

preAction 和 postAction/postactions 的命令

这些命令适用于 preAction 和 postAction(s)。对于需要节点的 preAction,使用“elements”来提供节点。



sendKey

sendKey 接受键码或元状态并将键发送到屏幕(不是节点),具有焦点的 UI 元素将接收键码和元,例如文本字段或虚拟键盘。sendKey 还提供了快捷方式,一个文本字符串,一些常用的键码。并非所有的键码都会生成字符,一些特殊的键码会带来新的窗口,例如返回、应用程序切换或转到主屏幕。键码和元状态显示在 Android KeyEvent 类中。

快捷键只不过是对不同键码的映射,以下是所有快捷键(不区分大小写):“home”、“back”、“backspace”、“enter”、“appswitch”(切换到最后一个应用程序),对于旧的应用程序、“搜索”和“菜单”。

sendKey(<key code>)

sendKey(<key code>, <meta state>)

sendKey(<shortcut>)

参数:

键码和元状态:为整数,“shortcut”为字符串。关于返回键,有时您需要发送 2 个返回键才能返回上一页,第一个返回键是关闭键盘(Sigma 在屏幕上不可见,但输入框在窗口顶部可见),第二个 返回键是返回上一屏幕。

返回:以真/假返回操作的状态。

例子:

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}


getQuery/getUniqQuery (O)

接受一个节点并尝试生成一个查询以匹配该节点(或多个节点)。从“getQuery”返回的查询可以匹配多个节点,有助于从应用程序中获取管状信息。从“getUniqQuery”返回的查询将只匹配一个节点,这对于点击等操作很有用。目前这是一个初步的,我们将在未来开发更好的查询字符串:

  • 将使用匹配列表(查询或元素)的第一个节点作为参考节点。
  • 输出没有完全优化,例如,它不使用偏移量和模板。
  • 如果发现多个节点,会包含“IX”来标识节点,这很容易出错,尤其是对于不使用资源ID的应用程序,IX可能很大,UI的任何变化都可能影响准确性。
  • UI Explorer 的“代码”按钮使用此命令显示查询字符串。
  • 对于文本和描述,如果长度超过 30 个字符,它将列出前 30 个字符并在末尾添加“*”。例如,如果节点的文本是“The quick brown fox jumps over the lazy dog”,则生成的查询将是:“T:The quick brown fox jumps over*”。
  • 如果文本正在更改(例如时钟),创建一个查询以忽略文本,在第一个参数上使用“true”,忽略描述,在第二个参数上使用“true”。

getUniqQuery/getQuery - 默认使用文本和描述作为查询的一部分。

getUniqQuery/getQuery(<true/false> 忽略文本) – true 忽略搜索查询中的文本。

getUniqQuery/getQuery(<true/false> 忽略文本,<true/false> 忽略描述) – 第二个参数为 true 以忽略搜索查询中的描述。

参数:

true or false:忽略查询中的文本。

true or false:忽略查询中的描述。

返回:返回查询字符串。

>> device.sendAai({elements:["123554"], preAction:"getUniqQuery"})
{query: 'T:INTC'}
>> device.sendAai({query:"T:INTC", postAction:"getUniqQuery"})
{query: 'T:INTC'}
>> device.sendAai({query:"T:INTC", postAction:"getUniqQuery(true)"})
{query: 'C:.TextView||R:.ticker||CC:0||IX:0'}
>> var ret = device.sendAai({query:"R:.ticker", postAction:"getQuery(true)"})
{query: 'C:.TextView||R:.ticker||CC:0'}
>> device.sendAai({query:ret.query, postAction:"getNodes('T,D')"})
{count: 5, list: [{id: '411a4', text: 'INTC'},{id: '3ec1a', text: 'WMT'},{id: '3c690', text: 'TGT'},{id: '3a106', text: 'AAPL'},{id: '37b7c', text: 'PEP'}]}
>> device.sendAai({query:ret.query, postAction:"getText"})
{retval: ['INTC','WMT','TGT','AAPL','PEP']} 

支持

请发送电子邮件至 support@sigma-rt.com 以获得支持。