选择器 UiSelector
UiSelector 按节点属性筛选当前界面,再交给 UiObject 点击、输入或滑动。Android 界面由节点组成:文字是 TextView,图片是 ImageView,位置由布局决定。节点常见属性有 text、desc、className、id、clickable。
有节点时用选择器。没有稳定文本或节点树拿不到时,再用坐标,见 手势 Gesture。写脚本前可先用 节点查看器 看属性。
不带参数或 UiSelector(true) 是简单模式。UiSelector(false) 是复杂模式,能看到更多节点(含状态栏等系统节点)。
let sendButton = UiSelector().text('发送').findOne();
if (sendButton) {
sendButton.click();
}条件可以链式叠加,后接 findOne / find 才真正去查。
条件
| 方法 | 作用 |
|---|---|
id(name) | id 等于 name(对应 viewIdResourceName) |
className(name) | 类名等于 name |
bounds(left, top, right, bottom) | 节点矩形范围 |
text(content) | 文本等于 content |
desc(content) | 描述等于 content(对应 contentDescription) |
clickable(canClick) | 是否可点击 |
selected(isSelected) | 是否已选择 |
checked(isChecked) | 是否勾选 |
enabled(isEnabled) | 是否可用 |
focusable(canFocus) | 是否可获焦 |
focused(isFocused) | 是否已获焦 |
editable(canEdit) | 是否可编辑 |
scrollable(canScroll) | 是否可滚动 |
isVisibleToUser(visible) | 是否对用户可见 |
textContains(content) | 文本包含 content |
textMatches(content) | 文本正则匹配 |
descContains(content) | 描述包含 content |
descMatches(content) | 描述正则匹配 |
filter(callback) | 按回调再过滤 |
查找
| 方法 | 作用 |
|---|---|
exists() | 当前条件能否匹配到节点 |
waitFindOne() | 一直等到节点出现 |
find() | 所有匹配节点 |
findBy(uiSelector) | 在已匹配节点的子树里再查 |
findBy(timeout) | 最多等 timeout 毫秒再 find |
findOne() | 第一个匹配节点 |
findOnce() | 同 findOne |
findOne(uiSelector) | 在已匹配节点的子树里找第一个 |
findOneBy(uiSelector) | 子树里找到一个立刻返回 |
findOneBy(timeout) | 最多等 timeout 毫秒找一个 |
id(name)
参数:
name {string}节点 id
返回: {UiSelector} 选择器自身,可继续链式调用
对应节点选择器里的 viewIdResourceName。
className(name)
参数:
name {string}类名,如"TextView"、"EditText"
返回: {UiSelector} 选择器自身
bounds(left, top, right, bottom)
参数:
left {number}左边缘到屏幕左边的距离top {number}上边距right {number}右边距bottom {number}下边距
返回: {UiSelector} 选择器自身
按节点在屏幕上的矩形筛选。
text(content)
参数:
content {string}完整文本
返回: {UiSelector} 选择器自身
文本必须整段相等。要模糊匹配用 textContains 或 textMatches。
desc(content)
参数:
content {string}完整描述
返回: {UiSelector} 选择器自身
对应 contentDescription。图标按钮常常只有描述、没有文本。
clickable(canClick)
参数:
canClick {boolean}是否可点击
返回: {UiSelector} 选择器自身
selected(isSelected)
参数:
isSelected {boolean}是否已选择
返回: {UiSelector} 选择器自身
checked(isChecked)
参数:
isChecked {boolean}是否勾选
返回: {UiSelector} 选择器自身
enabled(isEnabled)
参数:
isEnabled {boolean}false表示禁用
返回: {UiSelector} 选择器自身
focusable(canFocus)
参数:
canFocus {boolean}是否可以获取焦点
返回: {UiSelector} 选择器自身
focused(isFocused)
参数:
isFocused {boolean}是否已经获取焦点
返回: {UiSelector} 选择器自身
editable(canEdit)
参数:
canEdit {boolean}是否可编辑
返回: {UiSelector} 选择器自身
scrollable(canScroll)
参数:
canScroll {boolean}是否可滚动
返回: {UiSelector} 选择器自身
isVisibleToUser(isVisibleToUser)
参数:
isVisibleToUser {boolean}是否对用户可见
返回: {UiSelector} 选择器自身
let visibleButton = UiSelector().isVisibleToUser(true).text('发送').findOne();textContains(content)
参数:
content {string}子串
返回: {UiSelector} 选择器自身
可以连续调用,要求同时包含多段文字。
let tag = UiSelector().textContains('点赞').textContains('按钮').findOne();textMatches(content)
参数:
content {string|RegExp}正则或普通字符串
返回: {UiSelector} 选择器自身
传入普通字符串时,效果和 textContains 一样。
let tag = UiSelector().textMatches(/点赞/).findOne();
let tag2 = UiSelector().textMatches(/\d+/).findOne();
let tag3 = UiSelector().textMatches('点赞').findOne();descContains(content)
参数:
content {string}子串
返回: {UiSelector} 选择器自身
let tag = UiSelector().descContains('点赞').descContains('按钮').findOne();descMatches(content)
参数:
content {string|RegExp}正则或普通字符串
返回: {UiSelector} 选择器自身
传入普通字符串时,效果和 descContains 一样。
let tag = UiSelector().descMatches(/点赞/).findOne();
let tag2 = UiSelector().descMatches(/\d+/).findOne();
let tag3 = UiSelector().descMatches('点赞').findOne();filter(callback)
参数:
callback {function}入参是UiObject;返回true保留,返回false丢掉该节点
返回: {UiSelector} 选择器自身,可继续链式调用(再接 findOne / find / exists 等)
在其它条件匹配到的节点上,再按自定义逻辑筛一遍。点击前一般先用 filter 丢掉屏幕外节点(很少需要操作屏外内容)。常见用途:
| 场景 | 为什么要用 filter |
|---|---|
| 过滤屏幕外的节点(默认) | 同 id / 同文本常有多份;findOne() 可能先拿到屏幕外的(bounds.top < 0 等),一点击就会点偏 |
| 限制在某一侧操作栏 | 只要右侧点赞/评论按钮:bounds().left > Device.width() * 0.7 |
| 排除过小 / 过大的热区 | 避免点到整块大容器误进个人主页 |
| 组合属性判断 | 例如只要可点击且中心点在屏幕可见区域内 |
isVisibleToUser(true) 不能代替屏内几何校验:部分机型 / 场景下,屏幕外节点仍可能被标成可见。生成点击代码时,默认用 filter 再确认坐标。
过滤屏幕内节点(推荐):
let likeBtn = UiSelector()
.id('com.ss.android.ugc.aweme:id/gpf')
.filter(function (node) {
if (!node) {
return false;
}
let b = node.bounds();
if (!b || b.width() <= 0 || b.height() <= 0) {
return false;
}
// 丢掉屏幕外或越界节点
if (b.left < 0 || b.top < 0) {
return false;
}
if (b.right > Device.width() || b.bottom > Device.height()) {
return false;
}
return true;
})
.findOne();
if (likeBtn) {
likeBtn.click();
}按区域过滤(例如只要右侧操作栏):
let commentBtn = UiSelector()
.id('com.ss.android.ugc.aweme:id/e0d')
.filter(function (node) {
let b = node.bounds();
return b && b.left >= 0 && b.top >= 0 && b.left > Device.width() * 0.7;
})
.findOne();简单坐标条件:
let ui = UiSelector().filter(function (v) {
return v && v.bounds() && v.bounds().left > 100 && v.bounds().top > 300;
}).findOne();也可用箭头:UiSelector().filter((v) => v && v.bounds() && v.bounds().left > 100).findOne()。
exists()
返回: {boolean} 能匹配到至少一个节点时为 true
只判断在不在,不返回节点。
if (UiSelector().text('发送').exists()) {
console.log('发送按钮存在');
}waitFindOne()
返回: {UiObject} 出现后的节点
一直阻塞,直到屏幕上出现匹配节点。界面还在加载时用这个,不要空转 findOne。
find()
返回: {UiObject[]} 全部匹配节点
当前条件立刻查一遍,不等待。
findBy(uiSelector)
参数:
uiSelector {UiSelector}在子树里继续用的条件
返回: {UiObject[]}
先按当前选择器得到集合,再在每个节点的子孙里找符合 uiSelector 的节点。
findBy(timeout)
参数:
timeout {number}最长等待毫秒
返回: {UiObject[]}
和 find() 相同,但节点还没出现时最多等 timeout 毫秒。
findOne()
返回: {UiObject} 第一个匹配节点;没有则为空
立刻查一次,不会一直等。
同 id / 同文本在界面上可能有多份。点击前一般先用上面的 filter 去掉屏幕外节点,再 findOne。
findOnce()
返回: {UiObject} 第一个匹配节点
与 findOne() 相同。
findOne(uiSelector)
参数:
uiSelector {UiSelector}子树条件
返回: {UiObject}
在当前匹配节点的子孙里找第一个符合条件的节点。
findOneBy(uiSelector)
参数:
uiSelector {UiSelector}子树条件
返回: {UiObject}
类似 findBy,找到一个就返回。
findOneBy(timeout)
参数:
timeout {number}最长等待毫秒
返回: {UiObject}
类似带超时的 findBy,找到一个就返回。
