如何用UI Automator高效完成Android界面自动化测试?

来源:Nginx教程作者:椎名光头衔:网络博主
导读:本期聚焦于椎名光创作的《如何用UI Automator高效完成Android界面自动化测试?》,敬请观看详情。UI Automator 是 Android 官方提供的界面自动化框架,定位在设备级黑盒测试。它不依赖应用源码,通过 Accessibility 节点树识别控件,所以能跨应用操作系统界面、通知栏和第三方页面,这一点是很多单应用测试框架做不到的。实际项目中,登录流程之后往往还要处理系统权限弹窗、安装确认框或者跳转设置页,如果只用 Espresso 会频繁遇到无法定位的情况。UI Automator 围绕 UiDevice、UiSelector 和 UiObject 三个核心类设计,分别负责设备操作、控件定位和控件交互。本文从环境配置开始,逐步介绍控件选择器用法、动态列表滚动、等待条件封装以及跨应用测试的注意事项。重点会演示如何避免固定 sleep,用 Wait 和 UiWatcher 提升脚本稳定性。读完可以搭建一套能跑通完整业务链路的 Android UI 自动化测试用例,适合测试工程师和需要做冒烟回归的团队使用。

UI Automator 是 Android SDK 自带的界面自动化测试框架,定位在设备级黑盒测试。它与 Espresso 这类需要和应用组件打交道的框架不同,不依赖被测应用的源码,而是通过系统无障碍服务生成的节点树来查找和操作控件。这意味着测试脚本可以跨越应用边界,直接点击系统设置、通知栏、权限弹窗甚至第三方应用界面。理解这一点很关键,因为很多团队在单应用测试里用 Espresso 非常顺手,一旦遇到系统弹窗就开始手动补测,其实这类场景正好可以交给 UI Automator。

如何用UI Automator高效完成Android界面自动化测试?

使用 UI Automator 时,核心思路是先把设备当作一个整体,通过 UiDevice 获取设备实例,再用 UiSelector 描述目标控件的特征,最后通过 UiObject 完成点击、输入、滑动等动作。整个过程不需要关心 Activity 生命周期,也不需要注入测试代码到目标应用,因此很适合回归测试和兼容性测试。下面从核心组件、环境配置、定位技巧和稳定性策略几个方面展开。

一、UI Automator 的核心组件与执行模型

UI Automator 的核心类并不多,最常用的是 UiDevice、UiSelector、UiObject、UiCollection 和 UiScrollable。UiDevice 代表当前连接的设备或模拟器,提供 pressBack、pressHome、click、swipe、wakeUp 等设备级操作。UiSelector 用来描述控件的查找条件,可以按 resourceId、text、description、className、packageName 等属性组合筛选。UiObject 则是查找到的一个具体控件,拥有 click、setText、longClick、swipe 等交互方法。

执行模型基于 Instrumentation 测试环境,测试代码运行在单独的测试进程中,通过系统服务的 Accessibility 接口获取窗口节点。由于节点信息是异步刷新的,UI Automator 在查找控件时会有一个默认等待时间,但默认值往往不够用,实际脚本里通常需要显式等待。与 Espresso 自动同步主线程不同,UI Automator 不会自动感知界面是否稳定,所以如果页面还在动画或加载中,直接 findObject 很可能得到 null,后续操作就会抛异常。这一点是不少用例偶发失败的根本原因。

另一个容易混淆的概念是 UiObject 与 UiObject2。UiObject 属于传统 API,使用 UiSelector 构建,功能直观但存在一些限制;UiObject2 来自 AndroidX 测试库的新版 API,基于 BySelector 和 SearchCondition,支持更灵活的等待和条件组合。目前新项目建议优先使用 UiObject2,但大量既有资料和旧代码仍然使用 UiObject,因此两种写法都需要了解。

二、在 Gradle 工程中集成并编写第一个测试

要使用 UI Automator,首先在 Android 项目的 app 模块下添加测试依赖。AndroidX 提供的 uiautomator 库独立于 Android SDK 版本,适合在真机和模拟器上运行。在 build.gradle 文件中加入以下配置即可。

androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'

依赖添加完成后,在 androidTest 目录下创建测试类。下面这个例子演示了启动应用、输入账号密码并点击登录按钮的完整流程。注意资源 ID 要在被测应用的包名中找,不能直接使用测试项目的 R 文件,因为 UI Automator 是黑盒测试。

@RunWith(AndroidJUnit4.class)
public class LoginUiTest {

    @Test
    public void loginWithValidCredentials() {
        UiDevice device = UiDevice.getInstance(
                InstrumentationRegistry.getInstrumentation());
        Context context = InstrumentationRegistry.getInstrumentation()
                .getTargetContext();
        Intent intent = context.getPackageManager()
                .getLaunchIntentForPackage("com.example.app");
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        context.startActivity(intent);

        UiObject username = device.findObject(
                new UiSelector().resourceId("com.example.app:id/username"));
        username.waitForExists(5000);
        username.setText("test_user");

        UiObject password = device.findObject(
                new UiSelector().resourceId("com.example.app:id/password"));
        password.waitForExists(5000);
        password.setText("123456");

        UiObject loginButton = device.findObject(
                new UiSelector().text("登录"));
        loginButton.waitForExists(3000);
        loginButton.click();

        UiObject homeTitle = device.findObject(
                new UiSelector().text("首页"));
        assertTrue("登录后未进入首页", homeTitle.waitForExists(5000));
    }
}

这个用例先通过包名启动 Activity,然后按 resourceId 找到用户名和密码输入框,再按文本找到登录按钮。最后用 waitForExists 判断登录是否成功。waitForExists 会阻塞当前线程直到控件出现或超时,比直接 findObject 再判断 null 要可靠。但需要注意的是,setText 方法要求目标控件已经获得焦点,如果输入框不在可输入状态,操作可能无效,这时可以先调用 click 再输入。

运行用例时,使用 Gradle 任务 connectedDebugAndroidTest,连接真机或启动模拟器即可。测试报告会输出在 build/reports/androidTests/connected 目录下。若在 Windows 环境中开发,注意 SDK 路径通常包含反斜杠,比如 C:\Android\sdk\platform-tools,配置环境变量时不要误写成斜杠。

三、控件定位技巧与动态列表处理

resourceId 和 text 是最常用的定位条件,但在很多实际界面中,控件可能没有稳定的 resourceId,或者文本内容会随业务状态变化。这时可以使用 UiSelector 的多个方法组合筛选,例如 className 加 instance 索引,或者 textContains 做模糊匹配。UiSelector 的方法支持链式调用,每个方法都会缩小候选范围。

UiObject item = device.findObject(
        new UiSelector().className("android.widget.TextView")
                .textContains("订单")
                .instance(1));
item.click();

上面代码先限制控件类型为 TextView,再匹配包含订单二字的文本,最后取第二个匹配项。instance 从 0 开始计数,适合处理多个相同特征的控件。不过 instance 索引比较脆弱,界面改版后可能导致定位到错误控件。更推荐使用 UiCollection 或 UiScrollable 结合父子关系定位,或者请开发在关键控件上添加固定的 resourceId。

动态列表是自动化测试中的一大难点。对于需要滚动才能看到的列表项,直接 findObject 往往找不到,这时应该使用 UiScrollable。它可以指定滚动容器,并通过 scrollIntoView 或 scrollTextIntoView 自动滚动到目标控件。

UiScrollable list = new UiScrollable(
        new UiSelector().resourceId("com.example.app:id/order_list"));
UiObject target = list.getChildByText(
        new UiSelector().className("android.widget.TextView"),
        "已发货");
target.click();

如果列表控件没有明确的 resourceId,可以用 scrollable(true) 或 className(RecyclerView) 来找到滚动容器。需要注意的是,RecyclerView 的节点信息不像 ListView 那样完整,部分 item 在屏幕外时不会出现在节点树中,因此 UiScrollable 的滚动步长和超时时间需要结合实际页面调整。新版 API 的 UiObject2 配合 By.scrollable 和 Until.scrollUntil 能更精细地控制滚动方向。

四、系统弹窗、权限对话框与跨应用测试

跨应用操作是 UI Automator 最典型的优势场景。当测试流程涉及系统权限弹窗、安装确认框或跳转到系统设置时,Espresso 无法定位这些系统窗口,而 UI Automator 可以。比如首次启动应用会弹出存储权限请求,弹窗上的允许按钮属于系统进程,仍然可以通过 UiSelector 找到。

UiObject allowButton = device.findObject(
        new UiSelector().text("允许"));
if (allowButton.exists()) {
    allowButton.click();
}

这里使用 exists 而不是 waitForExists,是因为权限弹窗不一定每次都出现。先在条件分支中判断,能避免用例因为找不到按钮而失败。但很多权限弹窗的按钮文本在不同 Android 版本或不同厂商系统上不一致,有的叫允许,有的叫始终允许,还有的系统使用资源 ID。针对这种差异,可以封装一个处理权限弹窗的工具方法,同时匹配多个文本并捕获异常。

另一个常见场景是从测试应用跳转到系统设置页,例如关闭通知开关或修改定位权限。UI Automator 可以直接操作设置界面。下面代码演示如何打开应用详情页并点击权限入口。

Intent detailIntent = new Intent(
        Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
        Uri.parse("package:com.example.app"));
detailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(detailIntent);

UiObject permissionEntry = device.findObject(
        new UiSelector().text("权限"));
permissionEntry.waitForExists(5000);
permissionEntry.click();

这类跨应用用例需要额外注意系统版本和 ROM 差异,原生 Android 和 MIUI、EMUI 等定制系统的设置页结构可能完全不同。如果团队需要覆盖多厂商设备,建议用 device.getCurrentPackageName() 先判断当前界面是否在设置应用,或者使用 resourceId 匹配厂商特定的控件 ID。UI Automator 本身无法消除系统差异,只能通过更灵活的定位策略来适配。

五、等待策略与 UiWatcher 提升稳定性

固定 sleep 是自动化测试中最常见的反模式。比如在点击之后写 Thread.sleep(3000),虽然简单,但会让用例变慢且不稳定。UI Automator 提供了更好的等待机制,Wait 类和 Until 条件可以轮询查找控件直到满足条件或超时。下面代码等待一个包含指定文本的按钮出现,而不是固定等待。

UiDevice device = UiDevice.getInstance(
        InstrumentationRegistry.getInstrumentation());
UiObject button = device.findObject(
        new UiSelector().text("提交"));
boolean appeared = button.waitForExists(10000);
assertTrue("提交按钮未在10秒内出现", appeared);

除了 waitForExists,新版 UiObject2 的 wait 方法支持传入 SearchCondition,比如 Until.findObject 和 Until.gone,可以等待控件消失,这在处理加载动画时非常有用。例如等待进度条消失后再执行断言,比 sleep 更符合真实用户操作节奏。

UiObject2 loader = device.findObject(
        By.res("com.example.app:id/loading"));
loader.wait(Until.gone(By.res("com.example.app:id/loading")), 10000);

UiWatcher 是处理突发弹窗的利器。它可以在测试过程中注册一个监听器,当查找控件失败时自动触发,通常用来点击偶发的广告弹窗、升级提示或崩溃恢复对话框。注册后 UiWatcher 会在后台异步检查,如果发现匹配的控件就执行指定操作。比如应用突然弹出升级提醒,UiWatcher 可以自动点击暂不升级,保证主流程继续执行。

device.registerWatcher("upgrade_dialog", () -> {
    UiObject dismiss = device.findObject(
            new UiSelector().text("暂不升级"));
    if (dismiss.exists()) {
        dismiss.click();
        return true;
    }
    return false;
});

上面的 Lambda 代码需要 Java 8 支持,如果是旧项目可以在 build.gradle 中启用 coreLibraryDesugaring。注册 UiWatcher 后要记得在用例结束前调用 removeWatcher,否则可能影响后续测试。合理的等待策略加上 UiWatcher,可以让 UI Automator 用例在真机运行时的通过率明显提升,减少因环境干扰造成的误报。

UI AutomatorAndroid自动化测试界面测试修改时间:2026-10-05 08:06:30

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1005/65912.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。