选择器进阶
selector()
返回值类型
UiSelector
创建一个新的选择器。但一般情况不需要使用该函数,因为可以直接用相应条件的语句创建选择器。
由于历史遗留原因,本不应该这样设计(不应该让id(), text()等作为全局函数,而是应该用By.id(), By.text()),但为了后向兼容性只能保留这个设计。
这样的API设计会污染全局变量,后续可能会支持"去掉这些全局函数而使用By.*"的选项。
UiSelector.text(str)
参数 类型 描述
str string 控件文本
返回值类型 备注
UiSelector 返回选择器自身以便链式调用
为当前选择器附加控件"text等于字符串str"的筛选条件。
控件的text(文本)属性是文本控件上的显示的文字,例如微信左上角的"微信"文本。
// 约宝宝附件的人
object = text("在线").findOnce();
if (object != null) {
log(object.parent().parent().click()) //点击元素
} else {
//错误处理
};
UiSelector.textContains(str)
参数 类型 描述
str string 要包含的字符串
为当前选择器附加控件"text需要包含字符串str"的筛选条件。
这是一个比较有用的条件,例如QQ动态页和微博发现页上方的"大家都在搜…"的控件可以用textContains(“大家都在搜”).findOne()来获取。
UiSelector.textStartsWith(prefix)
参数 类型 描述
prefix string 前缀
为当前选择器附加控件"text需要以prefix开头"的筛选条件。
这也是一个比较有用的条件,例如要找出Auto.js脚本列表中名称以"QQ"开头的脚本的代码为textStartsWith(“QQ”).find()。
UiSelector.textEndsWith(suffix)
参数 类型 描述
suffix string 后缀
为当前选择器附加控件"text需要以suffix结束"的筛选条件。
UiSelector.textMatches(reg)
参数 类型 描述
reg string {Regex} 要满足的正则表达式
为当前选择器附加控件"text需要满足正则表达式reg"的条件。
有关正则表达式,可以查看正则表达式 - 菜鸟教程。
需要注意的是,如果正则表达式是字符串,则需要使用\来表达(也即Java正则表达式的形式),例如
textMatches("\d+") 匹配多位数字;
但如果使用JavaScript语法的正则表达式则不需要,例如textMatches(/\d+/)。
但如果使用字符串的正则表达式则该字符串不能以"/“同时以”/“结束,也即不能写诸如textMatches(”/\d+/")的表达式,否则会被开头的"/“和结尾的”/"会被忽略。
UiSelector.desc(str)
参数 类型 描述
str string 控件文本
返回值类型 备注
UiSelector 返回选择器自身以便链式调用
为当前选择器附加控件"desc等于字符串str"的筛选条件。
控件的desc(描述,全称为Content-Description)属性是对一个控件的描述,例如网易云音乐右上角的放大镜图标的描述为搜索。要查看一个控件的描述,同样地可以借助悬浮窗查看。
desc属性同样是定位控件的利器。
UiSelector.descContains(str)
参数 类型 描述
str string 要包含的字符串
为当前选择器附加控件"desc需要包含字符串str"的筛选条件。
UiSelector.descStartsWith(prefix)
参数 类型 描述
prefix string 前缀
为当前选择器附加控件"desc需要以prefix开头"的筛选条件。
UiSelector.descEndsWith(suffix)
参数 类型 描述
suffix string 后缀
为当前选择器附加控件"desc需要以suffix结束"的筛选条件。
UiSelector.descMatches(reg)
参数 类型 描述
reg string {Regex} 要满足的正则表达式
为当前选择器附加控件"desc需要满足正则表达式reg"的条件。
UiSelector.id(resId)
参数 类型 描述
resId string 控件的id
控件的id,以"包名:id/“开头,例如"com.tencent.mm:id/send_btn”。也可以不指定包名,这时会以当前正在运行的应用的包名来补全id
例如id(“send_btn”),在QQ界面想当于id(“com.tencent.mobileqq:id/send_btn”)。
为当前选择器附加"id等于resId"的筛选条件。
微信运动点赞例子:
// 执行点赞
object = id("bjb").findOnce();
if (object != null) {
object.click(); //点击元素
} else {
//错误处理
};
约宝宝APP寻找性别例子:
// 约宝宝附件的人
let object = id("v_sex_age").findOnce();
if (object != null) {
log(object.parent().click()) //点击元素
} else {
//错误处理
log("没找到")
};
控件的id属性通常是可以用来确定控件的唯一标识,如果一个控件有id,那么使用id来找到他是最好的方法。要查看屏幕上的控件的id,可以开启悬浮窗并使用界面工具,点击相应控件即可查看。若查看到的控件id为null, 表示该控件没有id。另外,在列表中会出现多个控件的id相同的情况。例如微信的联系人列表,每个头像的id都是一样的。此时不能用id来唯一确定控件。
在QQ界面经常会出现多个id为"name"的控件,在微信上则每个版本的id都会变化。对于这些软件而言比较难用id定位控件。
UiSelector.idContains(str)
参数 类型 描述
str string id要包含的字符串
为当前选择器附加控件"id包含字符串str"的筛选条件。比较少用。
UiSelector.idStartsWith(prefix)
参数 类型 描述
prefix string 前缀
为当前选择器附加"id需要以prefix开头"的筛选条件。比较少用。
UiSelector.idEndsWith(suffix)
参数 类型 描述
suffix string 后缀
为当前选择器附加"id需要以suffix结束"的筛选条件。比较少用。
UiSelector.idMatches(reg)
参数 类型 描述
reg string {Regex} id要满足的正则表达式
附加id需要满足正则表达式。
idMatches("[a-zA-Z]+")
UiSelector.className(str)
参数 类型 描述
str string 控件文本
返回值类型 备注
UiSelector 返回选择器自身以便链式调用
为当前选择器附加控件"className等于字符串str"的筛选条件。
控件的className(类名)表示一个控件的类别,例如文本控件的类名为android.widget.TextView。
如果一个控件的类名以"android.widget."开头,则可以省略这部分,例如文本控件可以直接用className(“TextView”)的选择器。
常见控件的类名 备注
android.widget.TextView 文本控件
android.widget.ImageView 图片控件
android.widget.Button 按钮控件
android.widget.EditText 输入框控件
android.widget.AbsListView 列表控件
android.widget.LinearLayout 线性布局
android.widget.FrameLayout 帧布局
android.widget.RelativeLayout 相对布局
android.widget.RelativeLayout 相对布局
android.support.v7.widget.RecyclerView 通常也是列表控件
UiSelector.classNameContains(str)
参数 类型 描述
str string 要包含的字符串
为当前选择器附加控件"className需要包含字符串str"的筛选条件。
UiSelector.classNameStartsWith(prefix)
参数 类型 描述
prefix string 前缀
为当前选择器附加控件"className需要以prefix开头"的筛选条件。
UiSelector.classNameEndsWith(suffix)
参数 类型 描述
suffix string 后缀
为当前选择器附加控件"className需要以suffix结束"的筛选条件。
UiSelector.classNameMatches(reg)
参数 类型 描述
reg string {Regex} 要满足的正则表达式
为当前选择器附加控件"className需要满足正则表达式reg"的条件。
UiSelector.packageName(str)
参数 类型 描述
str string 控件文本
返回值类型 备注
UiSelector 返回选择器自身以便链式调用
为当前选择器附加控件"packageName等于字符串str"的筛选条件。
控件的packageName表示控件所属界面的应用包名。例如微信的包名为"com.tencent.mm", 那么微信界面的控件的packageName为"com.tencent.mm"。
要查看一个应用的包名,可以用函数app.getPackageName()获取,例如
toast(app.getPackageName("微信"))。
UiSelector.packageNameContains(str)
参数 类型 描述
str string 要包含的字符串
为当前选择器附加控件"packageName需要包含字符串str"的筛选条件。
UiSelector.packageNameStartsWith(prefix)
参数 类型 描述
prefix string 前缀
为当前选择器附加控件"packageName需要以prefix开头"的筛选条件。
UiSelector.packageNameEndsWith(suffix)
参数 类型 描述
suffix string 后缀
为当前选择器附加控件"packageName需要以suffix结束"的筛选条件。
UiSelector.packageNameMatches(reg)
参数 类型 描述
reg string {Regex} 要满足的正则表达式
为当前选择器附加控件"packageName需要满足正则表达式reg"的条件。
UiSelector.bounds(left, top, right, buttom)
参数 类型 描述
left {number} 控件左边缘与屏幕左边的距离
top {number} 控件上边缘与屏幕上边的距离
right {number} 控件右边缘与屏幕左边的距离
bottom {number} 控件下边缘与屏幕上边的距离
一个控件的bounds属性为这个控件在屏幕上显示的范围。我们可以用这个范围来定位这个控件。尽管用这个方法定位控件对于静态页面十分准确,却无法兼容不同分辨率的设备;同时对于列表页面等动态页面无法达到效果,因此使用不推荐该选择器。
注意参数的这四个数字不能随意填写,必须精确的填写控件的四个边界才能找到该控件。例如,要点击QQ主界面的右上角加号,我们用布局分析查看该控件的属性,如下图:
可以看到bounds属性为(951, 67, 1080, 196),此时使用代码bounds(951, 67, 1080, 196).clickable().click()即可点击该控件。
UiSelector.boundsInside(left, top, right, buttom)
参数 类型 描述
left {number} 范围左边缘与屏幕左边的距离
top {number} 范围上边缘与屏幕上边的距离
right {number} 范围右边缘与屏幕左边的距离
bottom {number} 范围下边缘与屏幕上边的距离
为当前选择器附加控件"bounds需要在left, top, right, buttom构成的范围里面"的条件。
这个条件用于限制选择器在某一个区域选择控件。
例如要在屏幕上半部分寻找文本控件TextView,代码为:
var w = className("TextView").boundsInside(0, 0, device.width, device.height / 2).findOne();
log(w.text());
其中我们使用了device.width来获取屏幕宽度,device.height来获取屏幕高度。
UiSelector.boundsContains(left, top, right, buttom)
参数 类型 描述
left {number} 范围左边缘与屏幕左边的距离
top {number} 范围上边缘与屏幕上边的距离
right {number} 范围右边缘与屏幕左边的距离
bottom {number} 范围下边缘与屏幕上边的距离
为当前选择器附加控件"bounds需要包含left, top, right, buttom构成的范围"的条件。
这个条件用于限制控件的范围必须包含所给定的范围。
例如给定一个点(500, 300), 寻找在这个点上的可点击控件的代码为:
var w = boundsContains(500, 300, device.width - 500, device.height - 300).clickable().findOne();
w.click();
UiSelector.drawingOrder(order)
order {number} 控件在父视图中的绘制顺序
为当前选择器附加控件"drawingOrder等于order"的条件。
drawingOrder为一个控件在父控件中的绘制顺序,通常可以用于区分同一层次的控件。
但该属性在Android 7.0以上才能使用。
UiSelector.clickable([b = true])
参数 类型 描述
b Boolean 表示控件是否可点击
为当前选择器附加控件是否可点击的条件。但并非所有clickable为false的控件都真的不能点击,这取决于控件的实现。对于自定义控件(例如显示类名为android.view.View的控件)很多的clickable属性都为false都却能点击。
需要注意的是,可以省略参数b而表示选择那些可以点击的控件,例如:
className(“ImageView”).clickable()表示可以点击的图片控件的条件,className(“ImageView”).clickable(false)表示不可点击的图片控件的条件。
UiSelector.longClickable([b = true])
参数 类型 描述
b Boolean 表示控件是否可长按
为当前选择器附加控件是否可长按的条件。
UiSelector.checkable([b = true])
参数 类型 描述
b Boolean 表示控件是否可勾选
为当前选择器附加控件是否可勾选的条件。勾选通常是对于勾选框而言的,例如图片多选时左上角通常有一个勾选框。
UiSelector.selected([b = true])
参数 类型 描述
b Boolean 表示控件是否被选
为当前选择器附加控件是否已选中的条件。被选中指的是,例如QQ聊天界面点击下方的"表情按钮"时,会出现自己收藏的表情,这时"表情按钮"便处于选中状态,其selected属性为true。
UiSelector.enabled([b = true])
参数 类型 描述
b Boolean 表示控件是否已启用
为当前选择器附加控件是否已启用的条件。大多数控件都是启用的状态(enabled为true),处于“禁用”状态通常是灰色并且不可点击。
UiSelector.scrollable([b = true])
参数 类型 描述
b Boolean 表示控件是否可滑动
为当前选择器附加控件是否可滑动的条件。滑动包括上下滑动和左右滑动。
可以用这个条件来寻找可滑动控件来滑动界面。
例如滑动Auto.js的脚本列表的代码为:
className("android.support.v7.widget.RecyclerView").scrollable().findOne().scrollForward();
//或者classNameEndsWith("RecyclerView").scrollable().findOne().scrollForward();
UiSelector.editable([b = true])
参数 类型 描述
b Boolean 表示控件是否可编辑
为当前选择器附加控件是否可编辑的条件。
一般来说可编辑的控件为输入框(EditText),但不是所有的输入框(EditText)都可编辑。
UiSelector.multiLine([b = true])
参数 类型 描述
b Boolean 表示文本或输入框控件是否是多行显示的
为当前选择器附加控件是否文本或输入框控件是否是多行显示的条件。
UiSelector.findOne()
返回值类型 备注
UiSelector 返回选择器自身以便链式调用
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件。如果找不到控件,当屏幕内容发生变化时会重新寻找,直至找到。
需要注意的是,如果屏幕上一直没有出现所描述的控件,则该函数会阻塞,直至所描述的控件出现为止。因此此函数不会返回null。
该函数本来应该命名为untilFindOne(),但由于历史遗留原因已经无法修改。如果想要只在屏幕上搜索一次而不是一直搜索,请使用findOnce()。
另外,如果屏幕上有多个满足条件的控件,findOne()采用深度优先搜索(DFS),会返回该搜索算法找到的第一个控件。注意控件找到的顺序有时会起到作用。
UiSelector.findOne(timeout)
参数 类型 描述
timeout number 搜索的超时时间,单位毫秒
返回值类型 备注
UiObject 返回选择器自身以便链式调用
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件;如果在timeout毫秒的时间内没有找到符合条件的控件,则终止搜索并返回null。
该函数类似于不加参数的findOne(),只不过加上了时间限制。
官方示例:
//启动Auto.js
launchApp("Auto.js");
//在6秒内找出日志图标的控件
var object = id("action_log").findOne(6000);
//如果找到控件则点击
if(object != null){
object.click();
}else{
//否则提示没有找到
toast("没有找到日志图标");
}
约宝宝APP附件的人关注+私信:
//此代码由飞云脚本圈原创(www.feiyunjs.com)
// 约宝宝附件的人
let object = id("v_sex_age").findOnce();
if (object != null) {
//点击元素
if (object.parent().click()) {
object = id("btn_attention").findOne(3000); //等待关注按钮出现并点击
if (object != null) {
object.click(); //关注
};
object = id("btn_chat").findOne(3000);
if (object != null) {
object.click(); //聊天
};
object = id("editTextMessage").findOne(3000);
if (object != null) {
object.click(); //输入框置焦点
};
};
} else {
//错误处理
log("没找到")
};
UiSelector.findOnce()
返回值类型 备注
UiObject 返回选择器自身以便链式调用
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,如果找到符合条件的控件则返回该控件;否则返回null。
以下非官方例子
let object = text("抖音号").findOnce()
log(object)
if (object != null) {
log("已找到")
} else {
log("Error:未找到")
}
微信朋友圈点赞:
//此代码由飞云脚本圈原创(www.feiyunjs.com)
function 微信朋友圈点赞() {
// 检查是否在朋友圈页面
object = text("朋友圈").findOnce();
if (object != null) {
object = desc("返回").findOnce();
if (object != null) {
var Number = parseInt(ui.total.getText()); //需点赞的数量
var m = 0;
do {
var isCarry = false; //是否已执行(使用这个变量防止下面到了节点尾部没点赞,每次都延时)
// 展开气泡
object = desc("评论").findOnce();
if (object != null) {
object.click();
sleep(500);
};
// 执行点赞
object = text("赞").findOnce();
if (object != null) {
object.parent().click(); //点击父元素
m++;
toastLog('已执行' + m + '个');
isCarry = true;
sleep(1000);
};
// 向上翻页
swipe(device.width / 2 + random(1, 10), device.height - 100, device.width / 2 + random(1, 10), device.height - 400, random(500, 1000)); //向上滑动翻页
//每执行多少个暂停多少秒
if (m % parseInt(ui.every.getText()) == 0 && m > 0 && isCarry) {
toastLog('任务暂停');
sleep(parseInt(ui.suspend.getText()) * 1000);
} else {
sleep(1000);
};
}
while (m <= Number);
};
};
};
UiSelector.findOnce(i)
参数 类型 描述
i {number} 索引
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,并返回第 i + 1 个符合条件的控件;如果没有找到符合条件的控件,或者符合条件的控件个数 < i, 则返回null。
注意这里的控件次序,是搜索算法深度优先搜索(DSF)决定的。
以下非官方例子
下面这个APP页面,存在两个“意向客户”的文本
image.png
使用节点分析工具,查看页面的XML节点
image.png
当页面上出现多个text或id或className相同的元素时,就需要按照索引来查找控件。因为每个控件的索引是唯一的。
注意索引的值是从0开始的。这里有2个text相同的控件,我们要寻找并操作第2个,所以findOnce()的索引值,应该填1。
代码如下:
let object = text("意向客户").findOnce(1)
log(object)
if (object != null) {
log("已找到")
} else {
log("Error:未找到")
}
UiSelector.find()
返回值类型 备注
UiCollection 控件集合
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,找到所有满足条件的控件集合并返回。这个搜索只进行一次,并不保证一定会找到,因而会出现返回的控件集合为空的情况。
不同于findOne()或者findOnce()只找到一个控件并返回一个控件,find()函数会找出所有满足条件的控件并返回一个控件集合。之后可以对控件集合进行操作。
可以通过empty()函数判断找到的是否为空。例如:
let object = id("Button").find();
if (!object.empty()) {
log("找到啦");
object.forEach(function(currentValue, index) {
log(currentValue.text(),index)
});
} else {
log("没找到╭(╯^╰)╮");
};
也可以使用传统的数组遍历方式.
//不推荐使用for方法来遍历find()找到的组件。因为在循环中执行其他操作可能会中断循环
let object = className("TextView").find();
if (!object.empty()) {
log("找到啦");
for (var i = 0; i < object.length; i++) {
let tv = object[i];
log(tv.className(),tv.id(),tv.text(),tv.depth(),tv.bounds(),i);
};
} else {
log("没找到╭(╯^╰)╮");
};
以下非官方文档例子
下面是抖音v6.6.0的某个用户的关注列表和粉丝列表。
页面上存在一个tab卡盘,有两个子页面。也就存在两个列表。一个是关注列表,一个是粉丝列表。
image.png
下面是操作关注列表的例子。如果要操作粉丝列表,将下面的depth值改为13即可。
// 此代码由飞云脚本圈原创(www.feiyunjs.com)
// 抖音v6.6.0其他用户的关注列表,枚举用户昵称并进入个人资料页例子:
waitForActivity("com.ss.android.ugc.aweme.following.ui.FollowRelationTabActivity")
// 寻找用户昵称集合
var object = id("deb").depth(13).find();
if (!object.empty()) {
log("找到啦");
// 遍历用户昵称
object.forEach(function (currentValue, index) {
//获取项目索引
log(index)
// 获取昵称文本
log(currentValue.text())
// 获取列表项对象
let item = currentValue.parent().parent().parent()
if (item != null) {
// 点击列表项
log(item.click())
sleep(1000)
// 等待个人资料页
waitForActivity("com.ss.android.ugc.aweme.profile.ui.UserProfileActivity")
sleep(1000)
// 点击左上角返回按钮
desc('返回').click()
sleep(1000)
} else {
sleep(1000)
}
})
} else {
log("没找到╭(╯^╰)╮");
}
下面是约宝宝APP附近的人列表项目枚举实例。该脚本在执行的时候,需要跳过昵称后面有图标的用户。
这里需要做多个判断。
//此代码由飞云脚本圈原创(www.feiyunjs.com)
var object = id("tv_nick").find(); //寻找页面上所有昵称
if (!object.empty()) {
// 遍历组件
object.forEach(function (currentValue, index) {
let item = currentValue.parent(); //取昵称父组件对象
//判断id,如果不存在,说明项目没在前端显示
if (item.id() != null) {
if (item.childCount() == 1) {
//取子组件数量。如果值为1,说明只有昵称。如果值为2,说明该用户正在聊天室。不执行关注和私信
log(currentValue.text()); //取用户昵称
};
};
});
};
image.png
UiSelector.untilFind()
返回值类型 备注
UiCollection 控件集合
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到找到至少一个满足条件的控件为止,并返回所有满足条件的控件集合。
该函数与find()函数的区别在于,该函数永远不会返回空集合;但是,如果屏幕上一直没有出现满足条件的控件,则该函数会保持阻塞。
UiSelector.exists()
返回值类型 备注
Boolean
判断屏幕上是否存在控件符合选择器所确定的条件。例如要判断某个文本出现就执行某个动作,可以用:
if(text("某个文本").exists()){
//要支持的动作
}
以下非官方例子:
抖音v6.6.0其他用户的关注列表,判断是否到达底部的例子。如果要操作粉丝列表,将depth的值改为11即可。
text("没有更多了~").depth(12).exists() && log("已经到底了")
头条多多APP登录例子,等待控件消失:
log(waitDisappear(text("登录"),5000))
// 等待控件消失
//此代码由飞云脚本圈原创(www.feiyunjs.com)
function waitDisappear(object,timeOut) {
if (!timeOut) {
timeOut = 5000 //默认5秒
};
let date1 = new Date(); //开始时间
let date2 = date1.getTime()
let result = false //执行结果
while (true) {
if (!object.exists()) {
result = true
break;
}
date1 = new Date(); //结束时间
let date3 = date1.getTime()
if ((date3 - date2) > timeOut) {
log("已超时")
break; //跳出循环
};
sleep(500)
}
return result;
};
UiSelector.waitFor()
等待屏幕上出现符合条件的控件;在满足该条件的控件出现之前,该函数会一直保持阻塞。
例如要等待包含"哈哈哈"的文本控件出现的代码为:
textContains("哈哈哈").waitFor();
UiSelector.filter(f)
参数 类型 描述
f {Function} 过滤函数,参数为UiObject,返回值为boolean
为当前选择器附加自定义的过滤条件。
例如,要找出屏幕上所有文本长度为10的文本控件的代码为:
var uc = className("TextView").filter(function(currentValue){
return currentValue.text().length == 10;
});
示例
约宝宝APP附件的人关注+私信(完整)
//此代码由飞云脚本圈原创(www.feiyunjs.com)
//监听弹窗事件(异常处理)
threads.start(function () {
while (true) {
sleep(1200);
if (text('提醒').exists()) {
text('取消').click();
};
};
});
// 约宝宝附件的人
let object = id("v_sex_age").findOnce();
if (object != null) {
//点击元素
if (object.parent().click()) {
object = id("btn_attention").findOne(3000); //等待关注按钮出现并点击
if (object != null) {
object.click(); //关注
sleep(random(100, 500));
};
object = id("btn_chat").findOne(3000);
if (object != null) {
object.click(); //聊天
sleep(random(100, 500));
object = id("editTextMessage").findOne(3000);
if (object != null) {
object.click(); //输入框置焦点
sleep(random(100, 500));
input("你好!");
sleep(random(100,500));
id("buttonSendMessage").findOne().click(); //发送
sleep(random(100,500));
};
object = id("iv_back_new").findOne(3000);
if (object != null) {
object.click(); //返回
sleep(random(100, 500));
};
};
object = id("iv_back_new").findOne(3000);
if (object != null) {
object.click(); //返回
sleep(random(100, 500));
};
};
} else {
//错误处理
log("没找到")
};