UI Automator 是一个适用于跨应用功能界面的界面测试框架
对系统和已安装的应用进行测试。借助 UI Automator API
在设备上包含可见元素,无论 Activity
位于哪个
因此您可以执行打开“设置”菜单等操作
或测试设备中的应用启动器。您的测试可以通过以下方式查找界面组件
使用方便的描述词,例如该组件中显示的文字,或
内容说明。
UI Automator 测试框架是基于插桩的 API,可以
(使用 AndroidJUnitRunner
测试运行程序)。它很适合写作
不透明的方框式自动化测试,其中测试代码不依赖于内部
目标应用的实现详情。
UI Automator 测试框架的主要功能包括:
- 用于检索状态信息并对目标执行操作的 API 设备。如需了解详情,请参阅访问设备状态。
- 支持跨应用界面测试的 API。有关详情,请参阅用户界面 Automator API。
访问设备状态
UI Automator 测试框架提供了一个 UiDevice
类来访问
在运行目标应用的设备上执行操作。您可以
调用其方法来访问设备属性,如当前屏幕方向或
显示大小。借助 UiDevice
类,您还可以执行以下操作
操作:
- 改变设备的旋转。
- 按硬件键,例如“音量调高键”。
- 按返回、主屏幕或菜单按钮。
- 打开通知栏。
- 截取当前窗口的屏幕截图。
例如,如需模拟按下主屏幕按钮,请调用 UiDevice.pressHome()
方法。
UI Automator API
借助 UI Automator API,您无需了解 有关您目标应用的实现详情。您可以使用 这些 API 可用于在多个应用中捕获和操纵界面组件:
UiObject2
:表示设备上可见的界面元素。BySelector
:指定用于匹配界面元素的条件。By
:以简洁的方式构造BySelector
。Configurator
:可让您设置运行 UI Automator 测试的关键参数。
例如,以下代码展示了如何编写一个测试脚本, 在设备中打开 Gmail 应用:
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.pressHome()
val gmail: UiObject2 = device.findObject(By.text("Gmail"))
// Perform a click and wait until the app is opened.
val opened: Boolean = gmail.clickAndWait(Until.newWindow(), 3000)
assertThat(opened).isTrue()
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.pressHome();
UiObject2 gmail = device.findObject(By.text("Gmail"));
// Perform a click and wait until the app is opened.
Boolean opened = gmail.clickAndWait(Until.newWindow(), 3000);
assertTrue(opened);
设置 UI Automator
在使用 UI Automator 构建界面测试之前,请务必配置您的测试 源代码位置和项目依赖项,如设置项目 (AndroidX Test)。
在 Android 应用模块的 build.gradle
文件中,您必须设置依赖项
对 UI Automator 库的引用:
dependencies {
...
androidTestImplementation('androidx.test.uiautomator:uiautomator:2.3.0-alpha03')
}
dependencies {
...
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.3.0-alpha03'
}
要优化 UI Automator 测试,您应首先检查目标应用的 界面组件并确保它们可供访问。这些优化提示 会介绍相关信息
检查设备上的界面
在设计测试之前,请检查在
设备。为了确保您的 UI Automator 测试可以访问这些组件,
请检查这些组件是否具有可见的文本标签,
android:contentDescription
值,或同时设置两者。
uiautomatorviewer
工具提供了一个方便的可视化界面,供您进行检查
布局层次结构,并查看可见界面组件的属性
在设备前台运行借助这些信息,你可以
使用 UI Automator 进行精细测试例如,你可以创建一个界面选择器
与特定可见属性匹配的广告素材
要启动 uiautomatorviewer
工具,请执行以下操作:
- 在实体设备上启动目标应用。
- 将设备连接到开发机器。
- 打开终端窗口并导航至
<android-sdk>/tools/
目录。 - 使用以下命令运行该工具:
$ uiautomatorviewer
如需查看应用的界面属性,请执行以下操作:
- 在
uiautomatorviewer
界面中,点击 Device Screenshot 按钮。 - 将光标悬停在左侧面板中的快照上,以查看界面组件
由
uiautomatorviewer
工具识别。属性列于 较低 右侧面板中的布局层次结构,以及右上角面板中的布局层次结构。 - (可选)点击 Toggle NAF Nodes 按钮,以查看界面组件 UI Automator 无法访问的数据。可能只有有限的信息 可用于这些组件
要了解 Android 提供的常见类型的界面组件,请参阅用户 接口。
确保 Activity 可访问
UI Automator 测试框架在实现了
Android 无障碍功能。当您使用 View
类型的界面元素时,或
是 SDK 中的 View
的子类,则无需实现无障碍功能
因为这些类已经为您完成了这项工作。
不过,有些应用会使用自定义界面元素来提供更丰富的用户体验。
此类元素不会提供自动无障碍功能支持。如果您的应用
包含并非来自 SDK 的 View
子类的实例,请确保
请务必完成
操作步骤:
- 创建一个扩展 ExploreByTouchHelper 的具体类。
- 通过以下方式将新类的实例与特定自定义界面元素相关联: 调用 setAccessibilityDelegate()。
获得有关向自定义视图添加无障碍功能的其他指南 元素,请参阅构建无障碍自定义视图。要详细了解 有关在 Android 上使用无障碍功能的一般最佳实践,请参阅让应用更加实用 无障碍。
创建 UI Automator 测试类
UI Automator 测试类的编写方式应与 JUnit 4 测试相同 类。详细了解如何创建 JUnit 4 测试类以及如何使用 JUnit 4 断言和注解,请参阅创建插桩单元测试类。
在测试的开头添加 @RunWith(AndroidJUnit4.class) 注解 类定义。您还需要指定 AndroidJUnitRunner 类, 作为您的默认测试运行程序。这一步将 在设备或模拟器上运行 UI Automator 测试。
在 UI Automator 测试类中实现以下编程模型:
- 通过调用以下方法获取
UiDevice
对象,以访问要测试的设备 getInstance() 方法,并将 Instrumentation 对象作为 参数。 - 获取
UiObject2
对象,以访问显示在 设备(例如,前台的当前视图),方法是调用 findObject() 方法中找到它。 - 通过以下方法模拟要在该界面组件上执行的特定用户互动:
调用
UiObject2
方法;例如,调用 使用 scrollUntil() 来滚动,以及使用 setText() 修改文本字段。 您可以在第 2 步和第 3 步调用 API 以测试涉及 多个界面组件或一系列用户操作。 - 在用户登录后,检查界面是否反映了预期的状态或行为 会发生哪些互动
下面几部分更详细地介绍了这些步骤。
访问界面组件
UiDevice
对象是您访问和操控
状态在测试中,您可以调用 UiDevice
方法来检查
各种属性的状态,例如当前屏幕方向或显示屏尺寸。
您的测试可以使用 UiDevice
对象执行设备级操作,
例如强制设备以特定方式旋转、按方向键硬件
按钮,然后按主屏幕和菜单按钮。
最好从设备的主屏幕开始测试。出发地: 主屏幕(或您在设备中选择的其他起始位置) 您可以调用 UI Automator API 提供的方法来选择和交互 特定界面元素
以下代码段展示了您的测试如何获取
UiDevice
并模拟按下主屏幕按钮的操作:
import org.junit.Before
import androidx.test.runner.AndroidJUnit4
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
...
private const val BASIC_SAMPLE_PACKAGE = "com.example.android.testing.uiautomator.BasicSample"
private const val LAUNCH_TIMEOUT = 5000L
private const val STRING_TO_BE_TYPED = "UiAutomator"
@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = 18)
class ChangeTextBehaviorTest2 {
private lateinit var device: UiDevice
@Before
fun startMainActivityFromHomeScreen() {
// Initialize UiDevice instance
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
// Start from the home screen
device.pressHome()
// Wait for launcher
val launcherPackage: String = device.launcherPackageName
assertThat(launcherPackage, notNullValue())
device.wait(
Until.hasObject(By.pkg(launcherPackage).depth(0)),
LAUNCH_TIMEOUT
)
// Launch the app
val context = ApplicationProvider.getApplicationContext<Context>()
val intent = context.packageManager.getLaunchIntentForPackage(
BASIC_SAMPLE_PACKAGE).apply {
// Clear out any previous instances
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
context.startActivity(intent)
// Wait for the app to appear
device.wait(
Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
LAUNCH_TIMEOUT
)
}
}
import org.junit.Before;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
...
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class ChangeTextBehaviorTest {
private static final String BASIC_SAMPLE_PACKAGE
= "com.example.android.testing.uiautomator.BasicSample";
private static final int LAUNCH_TIMEOUT = 5000;
private static final String STRING_TO_BE_TYPED = "UiAutomator";
private UiDevice device;
@Before
public void startMainActivityFromHomeScreen() {
// Initialize UiDevice instance
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Start from the home screen
device.pressHome();
// Wait for launcher
final String launcherPackage = device.getLauncherPackageName();
assertThat(launcherPackage, notNullValue());
device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),
LAUNCH_TIMEOUT);
// Launch the app
Context context = ApplicationProvider.getApplicationContext();
final Intent intent = context.getPackageManager()
.getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
// Clear out any previous instances
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intent);
// Wait for the app to appear
device.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
LAUNCH_TIMEOUT);
}
}
在本例中,@SdkSuppress(minSdkVersion = 18) 语句有助于确保 测试将只能在搭载 Android 4.3(API 级别 18)或更高版本的设备上运行; 按照 UI Automator 框架的要求进行。
使用 findObject()
方法检索表示以下内容的 UiObject2
:
与给定选择器条件的视图匹配。您可以重复使用UiObject2
根据需要在应用测试的其他部分创建的实例。
请注意,UI Automator 测试框架会在当前显示屏中搜索
每当测试使用 UiObject2
实例点击界面时匹配
元素或查询属性。
以下代码段展示了您的测试如何构建 UiObject2
代表“取消”按钮和“确定”按钮的实例。
val okButton: UiObject2 = device.findObject(
By.text("OK").clazz("android.widget.Button")
)
// Simulate a user-click on the OK button, if found.
if (okButton != null) {
okButton.click()
}
UiObject2 okButton = device.findObject(
By.text("OK").clazz("android.widget.Button")
);
// Simulate a user-click on the OK button, if found.
if (okButton != null) {
okButton.click();
}
指定选择器
如果您要访问应用中的特定界面组件,请使用
By
类来构建 BySelector
实例。BySelector
表示对显示的界面中特定元素的查询。
如果找到了多个匹配元素,
布局层次结构会作为目标 UiObject2
返回。在构建
BySelector
,您可以将多个属性链接在一起以优化
搜索。如果未找到匹配的界面元素,则返回 null
。
您可以使用 hasChild()
或 hasDescendant()
方法进行嵌套
多个 BySelector
实例。例如,以下代码示例显示了
您的测试可能会如何指定搜索来查找第一个 ListView
,
具有带有 text 属性的子界面元素。
val listView: UiObject2 = device.findObject(
By.clazz("android.widget.ListView")
.hasChild(
By.text("Apps")
)
)
UiObject2 listView = device.findObject(
By.clazz("android.widget.ListView")
.hasChild(
By.text("Apps")
)
);
在选择器条件中指定对象状态可能很有用。对于
假设您想选择一个包含所有已选中元素的列表
您可以调用 checked()
方法,并将参数设为 true。
执行操作
在测试获取 UiObject2
对象后,您可以在
UiObject2
类,用于在界面组件上执行用户互动
由该对象表示。您可以指定如下操作:
click()
:点击界面元素的可见边界的中心。drag()
:将此对象拖动到任意坐标。setText()
:清除 字段的内容。反之,clear()
方法会清除现有文本 输入“?”swipe()
:向指定方向执行滑动操作。scrollUntil()
:向指定方向执行滚动操作 直到Condition
或EventCondition
满足要求。
通过 UI Automator 测试框架,您可以发送 Intent 或启动
无需使用 shell 命令的 Activity,通过获取 Context
getContext()
对象。
以下代码段展示了您的测试如何使用 Intent 来启动 被测应用仅当您只想进行测试时,此方法非常有用 计算器应用,而不关心启动器。
fun setUp() {
...
// Launch a simple calculator app
val context = getInstrumentation().context
val intent = context.packageManager.getLaunchIntentForPackage(CALC_PACKAGE).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
// Clear out any previous instances
context.startActivity(intent)
device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT)
}
public void setUp() {
...
// Launch a simple calculator app
Context context = getInstrumentation().getContext();
Intent intent = context.getPackageManager()
.getLaunchIntentForPackage(CALC_PACKAGE);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
// Clear out any previous instances
context.startActivity(intent);
device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT);
}
验证结果
InstrumentationTestCase 扩展了 TestCase,因此您可以使用 标准的 JUnit Assert 方法,用于测试应用中的界面组件是否返回 预期结果。
以下代码段展示了您的测试如何找到 计算器应用,按顺序点击它们,然后验证 。
private const val CALC_PACKAGE = "com.myexample.calc"
fun testTwoPlusThreeEqualsFive() {
// Enter an equation: 2 + 3 = ?
device.findObject(By.res(CALC_PACKAGE, "two")).click()
device.findObject(By.res(CALC_PACKAGE, "plus")).click()
device.findObject(By.res(CALC_PACKAGE, "three")).click()
device.findObject(By.res(CALC_PACKAGE, "equals")).click()
// Verify the result = 5
val result: UiObject2 = device.findObject(By.res(CALC_PACKAGE, "result"))
assertEquals("5", result.text)
}
private static final String CALC_PACKAGE = "com.myexample.calc";
public void testTwoPlusThreeEqualsFive() {
// Enter an equation: 2 + 3 = ?
device.findObject(By.res(CALC_PACKAGE, "two")).click();
device.findObject(By.res(CALC_PACKAGE, "plus")).click();
device.findObject(By.res(CALC_PACKAGE, "three")).click();
device.findObject(By.res(CALC_PACKAGE, "equals")).click();
// Verify the result = 5
UiObject2 result = device.findObject(By.res(CALC_PACKAGE, "result"));
assertEquals("5", result.getText());
}
在设备或模拟器上运行 UI Automator 测试
您可以通过 Android Studio 或
命令行。请务必将 AndroidJUnitRunner
指定为默认值
插桩测试运行程序。
更多示例
与系统界面交互
UI Automator 可以与屏幕上的所有内容(包括系统)互动 应用外部的元素,如以下代码段所示:
// Opens the System Settings.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.executeShellCommand("am start -a android.settings.SETTINGS")
// Opens the System Settings.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.executeShellCommand("am start -a android.settings.SETTINGS");
// Opens the notification shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.openNotification()
// Opens the notification shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.openNotification();
// Opens the Quick Settings shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.openQuickSettings()
// Opens the Quick Settings shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.openQuickSettings();
// Get the system clock.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock"))
print(clock.getText())
// Get the system clock.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock"));
print(clock.getText());
等待转换

屏幕转换可能需要一些时间,而且预测其持续时间并不可靠,因此 您应该让 UI Automator 在执行操作后等待。UI Automator 为此,我们提供了多种方法:
UiDevice.performActionAndWait(Runnable action, EventCondition<U> condition, long timeout)
:例如,如需点击某个按钮并等待新窗口出现,请调用device.performActionAndWait(() -> button.click(), Until.newWindow(), timeout)
UiDevice.wait(Condition<Object, U> condition, long timeout)
:例如,如需等到设备上存在某个UiObject2
,请调用device.wait(device.hasObject(By.text("my_text")), timeout);
UiObject2.wait(@NonNull Condition<Object, U> condition, long timeout)
:例如,如需等到复选框被选中,请调用checkbox.wait(Until.checked(true), timeout);
UiObject2.clickAndWait(@NonNull EventCondition<U> condition, long timeout)
:例如,如需点击某个按钮并等到新窗口出现,请调用button.clickAndWait(Until.newWindow(), timeout);
UiObject2.scrollUntil(@NonNull Direction direction, @NonNull Condition<Object, U> condition)
:例如,如需向下滚动,直到出现新对象,请调用object.scrollUntil(Direction.DOWN, Until.hasObject(By.text('new_obj')));
UiObject2.scrollUntil(@NonNull Direction direction, @NonNull EventCondition<U> condition)
:例如,如需滚动到底部,请调用object.scrollUntil(Direction.DOWN, Until.scrollFinished(Direction.DOWN));
以下代码段展示了如何使用 UI Automator 关闭 Do Not
使用 performActionAndWait()
方法在系统设置中开启勿扰模式,以便
等待转换:
@Test
@SdkSuppress(minSdkVersion = 21)
@Throws(Exception::class)
fun turnOffDoNotDisturb() {
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.performActionAndWait({
try {
device.executeShellCommand("am start -a android.settings.SETTINGS")
} catch (e: IOException) {
throw RuntimeException(e)
}
}, Until.newWindow(), 1000)
// Check system settings has been opened.
Assert.assertTrue(device.hasObject(By.pkg("com.android.settings")))
// Scroll the settings to the top and find Notifications button
var scrollableObj: UiObject2 = device.findObject(By.scrollable(true))
scrollableObj.scrollUntil(Direction.UP, Until.scrollFinished(Direction.UP))
val notificationsButton = scrollableObj.findObject(By.text("Notifications"))
// Click the Notifications button and wait until a new window is opened.
device.performActionAndWait({ notificationsButton.click() }, Until.newWindow(), 1000)
scrollableObj = device.findObject(By.scrollable(true))
// Scroll down until it finds a Do Not Disturb button.
val doNotDisturb = scrollableObj.scrollUntil(
Direction.DOWN,
Until.findObject(By.textContains("Do Not Disturb"))
)
device.performActionAndWait({ doNotDisturb.click() }, Until.newWindow(), 1000)
// Turn off the Do Not Disturb.
val turnOnDoNotDisturb = device.findObject(By.text("Turn on now"))
turnOnDoNotDisturb?.click()
Assert.assertTrue(device.wait(Until.hasObject(By.text("Turn off now")), 1000))
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void turnOffDoNotDisturb() throws Exception{
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.performActionAndWait(() -> {
try {
device.executeShellCommand("am start -a android.settings.SETTINGS");
} catch (IOException e) {
throw new RuntimeException(e);
}
}, Until.newWindow(), 1000);
// Check system settings has been opened.
assertTrue(device.hasObject(By.pkg("com.android.settings")));
// Scroll the settings to the top and find Notifications button
UiObject2 scrollableObj = device.findObject(By.scrollable(true));
scrollableObj.scrollUntil(Direction.UP, Until.scrollFinished(Direction.UP));
UiObject2 notificationsButton = scrollableObj.findObject(By.text("Notifications"));
// Click the Notifications button and wait until a new window is opened.
device.performActionAndWait(() -> notificationsButton.click(), Until.newWindow(), 1000);
scrollableObj = device.findObject(By.scrollable(true));
// Scroll down until it finds a Do Not Disturb button.
UiObject2 doNotDisturb = scrollableObj.scrollUntil(Direction.DOWN,
Until.findObject(By.textContains("Do Not Disturb")));
device.performActionAndWait(()-> doNotDisturb.click(), Until.newWindow(), 1000);
// Turn off the Do Not Disturb.
UiObject2 turnOnDoNotDisturb = device.findObject(By.text("Turn on now"));
if(turnOnDoNotDisturb != null) {
turnOnDoNotDisturb.click();
}
assertTrue(device.wait(Until.hasObject(By.text("Turn off now")), 1000));
}
其他资源
如需详细了解如何在 Android 测试中使用 UI Automator,请参阅 以下资源。
参考文档:
示例
- BasicSample:基本的 UI Automator 示例。