4、UI Automator 框架入门:使用 UI Automator 进行基本元素定位

UI Automator 是 Android 官方提供的跨应用 UI 自动化测试框架。说白了,它能让你的代码像真实用户一样操作手机——启动 App、点击按钮、滑动列表。特别在做 TalkBack 无障碍测试时,UI Automator 几乎是绕不开的工具。

我个人习惯先把框架的定位原理弄明白,再动手写代码。嗯,先看下面这张图,把整体脉络理清楚:

UI Automator 元素定位策略 UiSelector resourceId(ID) text(文本) contentDesc className XPath + 父节点 优/缺点 ✔ 稳定,无歧义 ✘ 需开发者提供 ID 测试前需等待布局 优/缺点 ✔ 直观易读 ✘ 受 i18n 影响 TalkBack 下可能失败 优/缺点 ✔ 无障碍重要属性 ✘ 可能为空 需开发者填充 优/缺点 ✔ 适合同类批量操作 ✘ 定位不唯一 需配合索引 优/缺点 ✔ 灵活 ✘ 慢,易崩 不推荐

4.1 核心对象:UiSelector

UI Automator 的一切定位都围绕着 UiSelector。你可以把它想象成一个「筛子」——指定条件,框架就从当前界面里把符合条件的控件筛出来。最常用的方法有这些:

定位方式UiSelector 方法适用场景稳定度
资源 ID.resourceId(String)有唯一 ID 的控件★★★★★
文本内容.text(String)按钮、标签文本固定★★★☆☆
内容描述.contentDescription(String)TalkBack 无障碍节点★★★★☆
类名.className(String)一类控件批量操作★★☆☆☆
XPath(通过 UiDevice.findObject() + By.clazz 等).fromParent() / .childSelector()复杂层次定位★★★☆☆

关键技巧:对于 TalkBack 自动化测试,contentDescription 是最可靠的定位属性。因为 TalkBack 朗读用的就是这个字段,你定位的元素就是用户听到的元素。我曾经在测试一个新闻 App 时,用 text 死活定位不到「分享」按钮,后来发现开发者设置了 contentDescription="分享文章",改用 contentDescription 后一次通过。

4.2 基本定位与操作

先看一个最常用的例子:在系统设置里点击「无线和网络」。注意这段代码演示了如何等待元素出现、定位并点击。

// 获取 UiDevice 实例
UiDevice device = UiDevice.getInstance(instrumentation);

// 等待系统设置界面加载完成
device.waitForIdle(3000);

// 通过内容描述定位(TalkBack 友好)
UiObject2 networkSetting = device.findObject(
    By.desc("无线和网络")
);

if (networkSetting != null) {
    networkSetting.click();
    System.out.println("定位成功并点击了:「无线和网络」");
} else {
    System.out.println("未找到元素,请检查 contentDescription 是否设置");
}

这里用了 By.desc(),它是 contentDescription 的快捷写法,等价于 By.clazz(UiSelector().contentDescription(...))。我个人建议在无障碍测试中优先使用 By.desc(),因为 TalkBack 焦点就是根据这个属性移动的。

4.3 进阶定位:链式筛选与父节点

现实情况往往更复杂——同一个界面可能有多个相同类名的控件。比如一个列表里有十几个「TextView」,光用 className 肯定不行。这时可以用链式筛选:

// 找到父容器(比如 RecyclerView 的 contentDescription)
UiObject2 parent = device.findObject(By.desc("消息列表"));

// 在父容器内定位第一个文本为"你好"的控件
UiObject2 target = parent.findObject(By.text("你好"));

if (target != null) {
    target.click();
}

你想想看,这种「先找容器再找元素」的方式,是不是很像你在真实操作时先看区域再点按钮?UI Automator 的 childSelector 也是同样的思路。我曾经踩过一个坑:直接用 device.findObject(By.text("确定")) 的时候,界面上有两个「确定」按钮——一个在底部弹窗里,一个在顶部导航栏。结果每次点击的都是顶部的那个,导致弹窗永远关不掉。后来改用父容器限定才解决。

注意:不要滥用 XPath 或通过 By.xpath() 定位。UI Automator 的 XPath 支持有限且性能很差。在 TalkBack 测试中尤其要避免,因为无障碍节点树可能比布局树更复杂,XPath 解析容易超时。我建议用 By.clazz() + By.depth() 或者多级 findObject 替代。

4.4 定位失败时的排查方法

刚开始学 UI Automator 时,最崩溃的就是「明明元素在屏幕上,却定位不到」。别急,我总结了几个排查步骤:

  1. 确认当前界面:用 device.getCurrentPackageName() 检查是不是在你想要的 App 里。
  2. 打印布局树:调用 device.dumpWindowHierarchy(sdcard/tmp.xml) 把当前页面 dump 出来,然后在文件中搜索你的目标文本或 contentDescription。
  3. 检查属性是否为空:很多自定义 View 没有设置 contentDescription,导致 By.desc() 永远找不到。
  4. 等待时间不够:网络加载、动画过渡都会让元素延迟出现。用 device.wait(Until.findObject(By.desc("xxx")), 5000) 代替直接 find。

小技巧:在 Android Studio 的 Device Explorer 里,可以直接导出 dump.xml 用浏览器打开查看树结构,比在控制台看纯文本方便多了。

4.5 实战:定位 TalkBack 首焦元素

TalkBack 开启后,屏幕上会有一个醒目的「绿色框」——那就是当前无障碍焦点。在自动化测试中,我们有时需要拿到这个焦点对应的元素。可以这样做:

// 获取当前具有无障碍焦点的元素
UiObject2 focused = device.findObject(By.focused(true));

if (focused != null) {
    String desc = focused.getContentDescription();
    String text = focused.getText();
    System.out.println("当前焦点元素 contentDescription: " + desc + ", text: " + text);
}

这里用 By.focused(true) 快速找到 TalkBack 正在朗读的那个控件。如果你需要在测试中模拟 TalkBack 的下一步操作(比如下滑切换焦点),可以用 device.pressDPadDown() 或 device.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)。不过这些属于更高级的技巧,后续章节会详细展开。

记住:UI Automator 的定位能力非常强大,但用得巧不巧,决定了你测试脚本的稳定性。多用 contentDescription,善用层级过滤,别偷懒用 XPath,你的测试就能跑得更稳。