脚本
Total Control 的最大优势之一是脚本功能。Total Control 提供丰富的 API 来控制一个或多个 Android 设备。它提供了 2 套 API:JavaScript 和 REST API。JavaScript 将在本地运行脚本,REST API 提供语言和主机的灵活性。您还可以使用 REST API 来控制多台运行“Total Control”应用程序的 PC。
JavaScript API
语言
Total Control 为 JavaScript 提供以下框架(ECMAScript 5 与选定的 ES6 功能)
如果 Rhino + RingoJS 没有提供足够的功能,Rhino 的好处是能够直接调用 Java API(Total Control 8 使用 OpenJDK 15)。
Total Control 提供了许多类,几个例子:
Device 和 DeviceArray 类
当设备连接时(自动连接或按下“连接”按钮),从“设备”类创建一个设备对象,使用静态方法“searchObject”定位设备对象。对于多个设备对象,它提供“DeviceArray”类(Array 的子类)。DeviceArray 对象中的设备将同时执行相同的任务。
设备类提供了近 100 种方法和属性来操作设备。DeviceArray 类提供了大约 20 多种常用方法。有关所有方法,请参阅 {{JavaScript API 文档}}。您可以轻松扩展 Device 和 DeviceArray (请参阅扩展 Device 和 DeviceArray)。
使用“Device.searchObject()”定位创建的设备对象,设备对象格式为“device@<10 digits>”。
//Returnonedeviceobject
varmySamsung=Device.searchObject('Samsung-S9');
//Returnalldeviceobjects, DeviceArray
varallDevices=Device.searchObject(tcConst.DevAll);
//Returndeviceobjectsbelongtosamegroup, DeviceArray
varallGroupX=Device.searchObject(tcConst.DevGroup,'firstrow');
varallGroupY=Device.searchObject(tcConst.DevGroup,'secondrow');
//Combine2groupstoformalargergroup, DeviceArray
vargroupXY=allGroupX.concat(allGroupY);
由于 DeviceArray() 是 Array() 的子类,它继承了 Array 类的大部分方法,因为所有 TC 特定的方法都绑定到 DeviceArray,如果你有一个数组,请使用 "var ary = new DeviceArray().concat (ary)" 转换为 DeviceArray。
//Thiswillfailsincethereisno"click"methodinArray
vardevice=Device.getMain();
varary=[device];
ary.click(100,100);
//Thiswillwork
vardevice=Device.getMain();
varary=newDeviceArray(device);
ary.click(100,100); // or ary.clickSync("OK");
几个例子:
//clicklocation100,200ondevice name "Samsung-S10".
vardevice=Device.searchObject('Samsung-S10');
if(device){
device.click(100,200); // or device.clickSync("Start");
}
//clickinthemiddleofthescreenonallconnecteddevices.
vardevices=Device.searchObject(tcConst.DevAll);
if(devices){
devices.click(0.5,0.5); // or device.clickSync("John");
}
目录和 Userlib.js
默认情况下,脚本目录位于\Users\<用户名>\Documents\Scripts 目录,您可以通过单击主窗口中的“脚本”来更改它,选择“脚本列表”,最上面一行可以更改 目录。
在其中,您可以创建一个名为“Userlib.js”的文件,该文件将始终在脚本执行之前加载,您可以包含常用函数、向现有类添加原型或引入第 3 方软件。
要调试“Userlib.js”,请使用“终端”,要重新加载“Userlib.js”,请单击终端窗口右下方的重新加载图标。
绝对与相对坐标
对于x、y坐标,TC提供“绝对”和“相对”坐标(系统设置显示坐标会同时显示两个坐标),绝对坐标从(0, 0)到(width – 1, height – 1),相对坐标为通常是从 (0, 0) 到 (0.9999, 0.9999) 的 4 个小数点。相对坐标乘以设备宽度和高度将得到绝对坐标。坐标并不理想,Professional 支持使用 UI 元素中的文本在运行时检索坐标的AAI 。
扩展设备和设备阵列
JavaScript 的额外好处是可扩展性,您可以通过向类添加原型来扩展方法。假设您要为单个和多个设备创建一个方法“longPress”,一种编写方法是:
Device.prototype.longPress=function(x,y){
varretval=this.click(x,y,tcConst.STATE_DOWN);
if(retval!=0){
returnretval;
}
delay(500);
returnthis.click(x,y,tcConst.STATE_UP);
}
DeviceArray.prototype.longPress=function(x,y){
for(leti=0;i<this.length;++i){
retval=this[i].longPress(x,y);
if(retval!=0){
returnretval;
}
}
return 0;
}
此实现对于 DeviceArray 中的大量设备效率不高,如果阵列中有 100 个设备,该方法可能需要长达 50 秒。
最好的方法是先向所有设备发送 STATE_DOWN,然后等到剩余的 500 毫秒用完。幸运的是,Device 和 DeviceArray(多个设备)引入了“sendAll”,它会执行指定的方法并等到指定的时间到期。这样,执行时间几乎是 500 毫秒。由于“sendAll”在 Device 和 DeviceArray 中可用,实现 Device 和 DeviceArray 的代码完全相同。
Device.prototype.longPress=function(x,y){
varretval=this.sendAll(Device.prototype.click,
[x,y,tcConst.STATE_DOWN],500);
if(retval!=0){
returnretval;
}
returnthis.sendAll(Device.prototype.click,[x,y,tcConst.STATE_UP]);
}
DeviceArray.prototype.longPress=Device.prototype.longPress;
sendAll 仅适用于成功返回 0 的方法。
脚本工具
Total Control 中有很多工具可用,点击“脚本”会打开一个新窗口:
终端:打开 Rhino + RingoJS 命令提示符,您可以将其用于测试或开发目的。单击右下角的按钮以重新加载解释器和 Userlib.js。可以使用 load("filename.js") 来运行脚本。
脚本列表⇒路径:脚本默认路径,点击图标可更改路径到其他目录。
脚本列表⇒ JS 源文件:用于快速执行一个JavaScript 文件,它提供了一个简单的编辑器来快速更改或单击箭头键来执行脚本。
脚本列表⇒记录脚本:将设备上的动作记录到Excel文件中,Excel文件也可用于生成JS脚本或JSON文件(可能对REST API有用)。没有 JavaScript 经验的用户可以使用此工具生成简单的脚本。
脚本列表⇒图像助手。从主设备屏幕内容生成 BMP 文件,seekImage() 需要图像,它扩展 BMP 文件以提供附加信息,例如应用程序名称、活动、宽度和高度信息。seekImage() 将利用这些信息而不提供大量参数。
脚本列表⇒颜色助手。从主设备(如图像助手)加载屏幕内容,这是一个放大的颜色选择器,用于选择颜色的 RGB 值并生成具有复杂参数的 seekColor()。可以支持单色和多色。
脚本列表⇒用户界面资源管理器。 打开 UI Explorer 窗口,单击捕获以捕获现有的主手机窗口并允许用户体验查询语言以选择 UI 元素。 Helper 窗口将以查询语言为每个键提供帮助说明。
执行(将重命名为任务)。 创建一个任务以在各种条件下运行脚本,例如日期和时间、迭代次数、定期间隔或设备。 任务执行的输出和结果将被保存以供审查。 也可以通过 JS API 创建或更改任务。 Runner 执行将在此处显示为任务。
检查:检查与脚本和引擎相关的各种内部变量。
AAI 将屏幕上的 UI 元素识别为对象,传统的 (x, y) 坐标方式将屏幕视为一个巨大的对象,因此需要寻找图像/颜色和 OCR 来识别屏幕上的对象。
可访问性是一种将屏幕上的 UI 元素表示为底层节点的功能,一个节点包括许多属性,例如文本/描述、维度、布尔属性(例如可点击、可编辑或可滚动)、底层类名等。可以访问文本/描述 很容易(不需要 OCR),尺寸(和可点击)确保即使节点移动到另一个位置也可以在特定位置点击按钮。
一个节点可以表示一个 UI 元素(例如按钮)或一组 UI 元素或某些元素的布局。节点(元素、组或布局)可以通过节点 ID(以十六进制字符串表示)来标识。我们集成了 Accessibility、TC 脚本框架和 UI Automator 库来实现以下目标:
AAI最简单的情况:
devices.inputTextSync([position], "text") // Enter text, the position is used for multiple inputs
devices.runAppSync(<package name>, [query])
devices.restartAppSync(<package name>, [query])
整个屏幕是由很多节点组成的,一个节点可以是一个最小的UI元素,也可以是很多节点的容器,有些节点是不可见的。整个屏幕是一个从单个根节点开始的树形结构。根据 App 的复杂程度,一个屏幕可以包含 50-300 个节点。
由于用户只对一小部分节点感兴趣,因此挑战在于找到用户想要的正确节点并提取信息或对其执行操作。
挑战是如何找到节点? 我们发明了一种查找节点的查询语言,在每台设备上都安装了FindNode程序,会执行查询语言来获取符合条件的节点,目的是将大量的节点减少到一个或几个想要的节点 ,用户可以获取信息或对节点应用操作。
例如:Java 中的 UI Automator 在 UiDevice.findObject() 或 findObjects() 中提供了“UiSelector”和“BySelector”来定位节点,对于多种情况可能很复杂:
new UiSelector().className("android.widget.TextView").text("OK")
我们创建了一种简单的查询语言,它更短且可移植,因为查询将发送到许多设备,上面的代码可以用我们的查询语言重写为:
"C: android.widget.TextView&&T:OK"
AAI项目包括以下内容:
询问
每个查询都有一个或多个“<key>:<value>”对,多个键可以用“&&”作为分隔符。
每个节点都由节点 ID 标识。 一次查询可以分为 3 个阶段:
执行查询后,找到的一个/多个节点列在“ML”(匹配列表)中,可以对ML应用操作列表,操作可以是检索信息或对ML执行操作。
模板:
为 BQ 或 EQ 生成初始节点。
TP:all 所有节点
TP:more 除以“Layout”结尾的节点外的所有节点
TP:basic 所有叶节点(子节点数为零)
TP:reduced 优化“TP:more”返回对屏幕重要的节点
TP:anyText[,<min>[,<max>]] “文本”中包含一定长度内容的节点。
TP:anyDescription[,<min>[,<max>]] “描述”中包含一定长度内容的节点。
TP:textInput 从左上角到右下角排序的所有“可编辑”字段。
TP:findText,<text> 参数中包含文本的节点,可以包含“*”和“/…/”。
TP:line,top|bottom,<number> 返回可滚动节点之外的顶部/底部节点。
TP:scrollable,<position> 可滚动容器内的节点,多个可滚动节点的位置。
基本查询(BQ):
查询获得的节点级信息,TP中的每个节点都会在BQ中匹配(如果提供)进行处理。
扩展查询(EQ):
这里的查询通常跨多个节点。
扩展查询的顺序很重要,所有扩展查询都是从左到右执行的。 允许使用相同键的命令。
对于BQ,查询语法可以包含“!” 对于不是,“>”,“<” 对于大于或小于,“*”表示通配符匹配,“/<regexp>/”表示正则表达式。 它可以匹配包名、类名、资源ID、文本、描述、子数量和输入类型。
FindNode 安装在每个设备中(Total Control App 的一部分),它是唯一识别查询语法的程序,它解析查询、定位节点并对找到的节点执行操作。FindNode 卸载了 JavaScript 的复杂性和 Total Control 的 CPU 使用率,所有搜索都在设备中进行。
device.sendAAi() 和 devices.sendAai() 是通过一个或一系列设备与 FindNode 进行通信的直接方式。JS 对象在发送到设备前会被翻译成 JSON,返回值为 JS 对象格式。如果遇到错误,则返回 null,lastError() 包含错误消息。
一个简单的查询示例,获取模型名称的文本,使用 1 的 X 偏移量(右侧):
>> device.sendAai({query:"T:Model name&&OX:1", postAction:"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}
点击“联系人”图标:
>> 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}
// Find multiple "Contacts" are found on the screen, IX:-1 is to select the
// last node found
>> device.sendAai({query:"T:Contacts&&IX:-1&&OY:-1", action:"click"})
{retval: true}
请阅读FindNode 用户指南以获得完整信息。
后台查询(在 8.0 更新 20 中提供)
后台查询允许用户将设备中的查询映射到 JavaScript 回调函数,当查询条件满足时,会触发带有节点 ID 的回调函数。
例如,如果您想在计算器显示 999 时关闭计算器应用程序。
函数closeMe(名称,设备,包名,节点ID){ device.closeApp(packageName); 返回真; } addQueryListener("closeCalculator", device, "T:/^999$/", closeMe);
如果设备没有运行计算器应用程序,需要包含计算器包名:例如“P:com.sigma_rt.calc||T:/^999$/”。
“return true”允许Total Control重新激活后台查询,没有“return true”,回调只运行一次。
在计算器应用程序中输入“999”以终止计算器。
同步 API
在版本 8 之前,所有的动作或移动命令都是异步的,当一个命令(例如 device.click())返回时,表示该命令已经传递给我们的移动代理执行,当命令返回时,很可能是该命令未执行,因此需要睡眠以给时间完成操作并刷新屏幕:
device.click(100, 100); 睡眠(300);
这可能发生在所有使编码繁琐的动作和移动命令上。UI Automator 包括同步命令和等待窗口更新,当点击时,一个窗口已经更新,很可能命令完成。所有同步命令都以“Sync”为后缀。所有“同步”命令都使用 AAI 标签而不是坐标:
runAppSync()
restartAppSync()
clickSync()
inputTextSync()
UiElement 和 UiElementArray 对象方法
同步功能的一个缺点是命令完成所需的时间比异步命令长得多,这可能会减慢大量设备的脚本执行速度,我们已经解决了这个问题。
后续版本将为所有动作和运动功能提供同步功能。
AAI 脚本 (AAIS)
我们还开发了一个利用 AAI 功能进行自动化的迷你脚本,目前在 WDM 和 MDCC 中可用(文件扩展名为“.tst”),捕获和重放(带有对象选项)将在此脚本中生成。 它还提供与 JavaScript 的无缝集成(包含在“{}”中)。
例如
exec "teslaLib.js"
open "Tesla"
find "T:VIN:"
get "T:VIN:&&OX:1", "text"
{ saveVar("vin", getOutput().retval) }
get "T:/[0-9,]+ miles/", "text"
{ saveMileage(new Date(), loadVar("vin"), getOutput().retval) }
print "Done"
这个脚本可以同时在100台设备上运行,可以在任何屏幕尺寸上运行,“查找”会滚动直到找到查询。 任何线路上的任何故障都将停止脚本。 它为 AAIS 和 JavaScript API 提供命令。
另一个例子:
{
var apps = ["Skype", "Whatsapp", "Telegram"];
var arguments = getArg();
var app = apps[0];
if (arguments.length > 0) {
var appNum;
if(isNaN(appNum = parseInt(arguments[0]))) {
throw "Need a number;"
}
if (appNum < 1 || appNum > apps.length) {
throw "Option out of range";
}
app = apps[appNum-1];
}
}
open "${app}"
print "${app} opened"
有关 AAIS 信息,请阅读AAIS 用户指南。