脚本

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 提供了许多类,几个例子:

  • 设备
  • 设备阵列
  • UiElement(总控8)
  • UiElementArray(总控8)
  • 通知
  • 键盘
  • Excel

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”的文件,该文件将始终在脚本执行之前加载,您可以包含常用功能,将原型添加到现有类或引入 3rd 方软件。

要调试“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()。可以支持单色和多色。

执行(将重命名为任务)。创建一个任务以在各种条件下运行脚本,例如日期和时间、迭代次数、定期间隔或设备。任务执行的输出和结果将被保存以供查看。也可以通过 JS API 创建或更改任务。

检查:检查与脚本和引擎相关的各种内部变量。

自动化和可访问性集成 (AAI)(Total Control 8+)

可访问性是 Android 中将屏幕上的 UI 元素映射到底层节点的功能,该节点可以表示一个 UI 元素(例如按钮)或一组 UI 元素或某些元素的布局。节点(元素、组或布局)可以通过节点 ID(以十六进制字符串表示)来标识。我们集成了 Accessibility、TC 脚本框架和 UI Automator 库来实现以下目标:

  • 坐标独立使脚本在不同分辨率和多个品牌下更具可移植性。
  • 同步 API 会等到屏幕重绘,使脚本更简单,不需要猜测休眠时间。同步 API 不会减慢多设备的执行速度。
  • 可以轻松地从应用程序中检索字符串,而不是使用容易出错的 OCR。

AAI最简单的情况:

  • 如果找到,请在屏幕上单击“确定”,对于特定分辨率,远优于 click(100, 100):devices.clickSync("OK")
  • 在文本输入中输入文本,指定位置,在多个文本输入行中输入第 n 个位置。
    devices.inputTextSync([position], "text")    // Enter text, the position is used for multiple inputs
  • 运行或重启应用,不查询,屏幕刷新返回;使用查询,刷新屏幕后将匹配查询。
    devices.runAppSync(<package name>, [query]) 
    devices.restartAppSync(<package name>, [query])

8.0(更新 20)在 REST API 上具有上述功能。

对于更复杂的脚本,您需要通过查询可访问性节点来定位节点 ID 或节点 ID 列表。Java 中的 UI Automator 在 UiDevice.findObject() 或 findObjects() 中提供了“UiSelector”和“BySelector”来定位节点,对于多个条件,需要多个查询语句,对于 JavaScript 来说,它可能过于复杂且不利于用户移植.

new UiSelector().className("android.widget.TextView").text("OK")
"C: android.widget.TextView||T:OK"

查询语法可以包含“!” 表示不,“>”,“<”表示大于或小于,“*”表示通配符匹配,“/<regexp>/”表示正则表达式。它可以匹配包名、类名、资源ID、文本、描述、子计数和输入类型。

如果找到,则使用 UiElement.findObject() 返回节点的对象,因为该对象已存储设备对象,因此执行您不需要像其他动作/移动命令那样指定设备。

var obj = UiElement.findObject(device, "T:OK");
if (obj) {
   obj.clickSync();
}

如果您有多个设备,它将返回类 UiElementArray 中的对象数组,一个动作将导致所有设备做出反应,下面的代码将单击所有带有“OK”文本的设备:

var objs =  UiElement.findObject(devices, "T:OK");
if (objs) {
   objs.clickSync();
}

您还可以从应用程序中检索信息,例如带有小时、分钟和秒的时钟:

var hour = UiElement.findObject(devices, "R:" + app + ":id/timeHour").getText();
var min = UiElement.findObject(devices, "R:" + app + ":id/timeMinute").getText();
var sec = UiElement.findObject(devices, "R:" + app + ":id/timeSecond").getText();
print("The current time is " + hour + ":" + min + ":" + sec);

或者,您可以从“UI Explorer”中检索信息以查找节点的查询语法(不是最优化的查询)。例如


为了减轻 JavaScript 的复杂性和 Total Control 的 CPU 利用率,所有搜索都在设备上进行。

设备.sendAAi() 将命令直接发送到设备执行。JS 对象将在发送到设备之前转换为 JSON,它将返回输出。如果遇到错误,则返回 null,lastError() 包含错误消息。

该命令分为“preAction”和“postAction”,“preAction”是在进行搜索/计数之前,“postAction”是对搜索输出的操作。

“elements”包含一个或多个节点ID,如果指定了elements,则不进行搜索,将传递给preAction/postAction执行。

“query”包含搜索的查询语法。

“模板”包含 4 个预定义的搜索模板之一来替换复杂的查询。

例子:

var retval = device.sendAai({query:"T:/[0-9]/", text:"OK"})

它将返回结果或 null。上面计算器的输出将返回

{count: 10, ids: ['97a3','9b64','9f25','a6a7','aa68','ae29','b5ab','b96c','bd2d','c4af']}

“count”包含匹配数(count == retval.ids.length),返回的节点基于节点的位置,从上到下,从左到右。第一个节点是按钮“7”。点击“7”:

var clickRet = device.sendAai({elements:[retval.ids[0]], postAction:"click"})

或者,下面将做同样的事情:

var clickRet = device.sendAai({query:"T:7", postAction:"click"})

(“T:7”表示文本为“7”,在这种情况下,它可能会匹配“7”作为第一个节点的结果,点击结果元素什么也不做),或者,你可以给它添加按钮(来自 UI 资源管理器):

var clickRet = device.sendAai({query:"T:7||C:android.widget.Button", postAction:"click"})

这将打印 NOT 0-9(所有特殊键):

>> var retval = device.sendAai({query:"T:!/[0-9]/|| C:android.widget.Button", fields:"T", postAction:"getNodes"})
{count: 10, list: [{id: '889f', text: 'AC'},{id: '8c60', text: '÷'},{id: '9021', text: '×'},{id: '93e2', text: 'DEL'},{id: 'a2e6', text: '-'},{id: 'b1ea', text: '+'},{id: 'c0ee', text: '%'},{id: 'c870', text: '.'},{id: 'cc31', text: '±'},{id: 'cff2', text: '='}]}

下面将找到一个带有“确定”的文本或描述的节点,然后单击它:

var retval = device.sendAai({template:"findText", text:"OK", postAction:"click"})

请阅读FindNode 用户指南了解更多信息。