UI Automator is a UI testing framework suitable for cross-app functional UI
testing across system and installed apps. The UI Automator APIs let you interact
with visible elements on a device, regardless of which Activity
is in
focus, so it allows you to perform operations such as opening the Settings menu
or the app launcher in a test device. Your test can look up a UI component by
using convenient descriptors such as the text displayed in that component or its
content description.
The UI Automator testing framework is an instrumentation-based API and works
with the AndroidJUnitRunner
test runner. It's well-suited for writing
opaque box-style automated tests, where the test code does not rely on internal
implementation details of the target app.
The key features of the UI Automator testing framework include the following:
- An API to retrieve state information and perform operations on the target device. For more information, see Accessing device state.
- APIs that support cross-app UI testing. For more information, see UI Automator APIs.
Accessing device state
The UI Automator testing framework provides a UiDevice
class to access
and perform operations on the device on which the target app is running. You can
call its methods to access device properties such as current orientation or
display size. The UiDevice
class also let you perform the following
actions:
- Change the device rotation.
- Press hardware keys, such as "volume up".
- Press the Back, Home, or Menu buttons.
- Open the notification shade.
- Take a screenshot of the current window.
For example, to simulate a Home button press, call the UiDevice.pressHome()
method.
UI Automator APIs
The UI Automator APIs allow you to write robust tests without needing to know about the implementation details of the app that you are targeting. You can use these APIs to capture and manipulate UI components across multiple apps:
UiObject2
: Represents a UI element that is visible on the device.BySelector
: Specifies criteria for matching UI elements.By
: ConstructsBySelector
in a concise manner.Configurator
: Lets you set key parameters for running UI Automator tests.
For example, the following code shows how you can write a test script that opens a Gmail app in the device:
Kotlin
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()
Java
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);
Set up UI Automator
Before building your UI test with UI Automator, make sure to configure your test source code location and project dependencies, as described in Set up project for AndroidX Test.
In the build.gradle
file of your Android app module, you must set a dependency
reference to the UI Automator library:
Kotlin
dependencies {
...
androidTestImplementation('androidx.test.uiautomator:uiautomator:2.3.0-alpha03')
}
Groovy
dependencies {
...
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.3.0-alpha03'
}
To optimize your UI Automator testing, you should first inspect the target app’s UI components and ensure that they are accessible. These optimization tips are described in the next two sections.
Inspect the UI on a device
Before designing your test, inspect the UI components that are visible on the
device. To ensure that your UI Automator tests can access these components,
check that these components have visible text labels,
android:contentDescription
values, or both.
The uiautomatorviewer
tool provides a convenient visual interface to inspect
the layout hierarchy and view the properties of UI components that are visible
on the foreground of the device. This information lets you create more
fine-grained tests using UI Automator. For example, you can create a UI selector
that matches a specific visible property.
To launch the uiautomatorviewer
tool:
- Launch the target app on a physical device.
- Connect the device to your development machine.
- Open a terminal window and navigate to the
<android-sdk>/tools/
directory. - Run the tool with this command:
$ uiautomatorviewer
To view the UI properties for your application:
- In the
uiautomatorviewer
interface, click the Device Screenshot button. - Hover over the snapshot in the left-hand panel to see the UI components
identified by the
uiautomatorviewer
tool. The properties are listed in the lower right-hand panel and the layout hierarchy in the upper right-hand panel. - Optionally, click on the Toggle NAF Nodes button to see UI components that are non-accessible to UI Automator. Only limited information might be available for these components.
To learn about the common types of UI components provided by Android, see User Interface.
Ensure your activity is accessible
The UI Automator test framework performs better on apps that have implemented
Android accessibility features. When you use UI elements of type View
, or
a subclass of View
from the SDK, you don't need to implement accessibility
support, as these classes have already done that for you.
Some apps, however, use custom UI elements to provide a richer user experience.
Such elements won't provide automatic accessibility support. If your app
contains instances of a subclass of View
that isn't from the SDK, make
sure that you add accessibility features to these elements by completing the
following steps:
- Create a concrete class that extends ExploreByTouchHelper.
- Associate an instance of your new class with a specific custom UI element by calling setAccessibilityDelegate().
For additional guidance on adding accessibility features to custom view elements, see Building Accessible Custom Views. To learn more about general best practices for accessibility on Android, see Making Apps More Accessible.
Create a UI Automator test class
Your UI Automator test class should be written the same way as a JUnit 4 test class. To learn more about creating JUnit 4 test classes and using JUnit 4 assertions and annotations, see Create an Instrumented Unit Test Class.
Add the @RunWith(AndroidJUnit4.class) annotation at the beginning of your test class definition. You also need to specify the AndroidJUnitRunner class, provided in AndroidX Test, as your default test runner. This step is described in more detail in Run UI Automator tests on a device or emulator.
Implement the following programming model in your UI Automator test class:
- Get a
UiDevice
object to access the device you want to test, by calling the getInstance() method and passing it an Instrumentation object as the argument. - Get a
UiObject2
object to access a UI component that is displayed on the device (for example, the current view in the foreground), by calling the findObject() method. - Simulate a specific user interaction to perform on that UI component, by
calling a
UiObject2
method; for example, call scrollUntil() to scroll, and setText() to edit a text field. You can call on the APIs in steps 2 and 3 repeatedly as necessary to test more complex user interactions that involve multiple UI components or sequences of user actions. - Check that the UI reflects the expected state or behavior, after these user interactions are performed.
These steps are covered in more detail in the sections below.
Access UI components
The UiDevice
object is the primary way you access and manipulate the
state of the device. In your tests, you can call UiDevice
methods to check for
the state of various properties, such as current orientation or display size.
Your test can use the UiDevice
object to perform device-level actions,
such as forcing the device into a specific rotation, pressing D-pad hardware
buttons, and pressing the Home and Menu buttons.
It’s good practice to start your test from the Home screen of the device. From the Home screen (or some other starting location you’ve chosen in the device), you can call the methods provided by the UI Automator API to select and interact with specific UI elements.
The following code snippet shows how your test might get an instance of
UiDevice
and simulate a Home button press:
Kotlin
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 ) } }
Java
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); } }
In the example, the @SdkSuppress(minSdkVersion = 18) statement helps to ensure that tests will only run on devices with Android 4.3 (API level 18) or higher, as required by the UI Automator framework.
Use the findObject()
method to retrieve a UiObject2
which represents
a view that matches a given selector criteria. You can reuse the UiObject2
instances that you have created in other parts of your app testing, as needed.
Note that the UI Automator test framework searches the current display for a
match every time your test uses a UiObject2
instance to click on a UI
element or query a property.
The following snippet shows how your test might construct UiObject2
instances that represent a Cancel button and a OK button in an app.
Kotlin
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() }
Java
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(); }
Specify a selector
If you want to access a specific UI component in an app, use the
By
class to construct a BySelector
instance. BySelector
represents a query for specific elements in the displayed UI.
If more than one matching element is found, the first matching element in the
layout hierarchy is returned as the target UiObject2
. When constructing a
BySelector
, you can chain together multiple properties to refine your
search. If no matching UI element is found, a null
is returned.
You can use the hasChild()
or hasDescendant()
method to nest
multiple BySelector
instances. For example, the following code example shows
how your test might specify a search to find the first ListView
that
has a child UI element with the text property.
Kotlin
val listView: UiObject2 = device.findObject( By.clazz("android.widget.ListView") .hasChild( By.text("Apps") ) )
Java
UiObject2 listView = device.findObject( By.clazz("android.widget.ListView") .hasChild( By.text("Apps") ) );
It can be useful to specify the object state in your selector criteria. For
example, if you want to select a list of all checked elements so that you can
uncheck them, call the checked()
method with the argument set to true.
Perform actions
Once your test has obtained a UiObject2
object, you can call the methods in
the UiObject2
class to perform user interactions on the UI component
represented by that object. You can specify such actions as:
click()
: Clicks the center of the visible bounds of the UI element.drag()
: Drags this object to arbitrary coordinates.setText()
: Sets the text in an editable field, after clearing the field's content. Conversely, theclear()
method clears the existing text in an editable field.swipe()
: Performs the swipe action toward specified direction.scrollUntil()
: Performs the scroll action toward specified direction untilCondition
orEventCondition
is satisfied.
The UI Automator testing framework allows you to send an Intent or launch
an Activity without using shell commands, by getting a Context
object through getContext()
.
The following snippet shows how your test can use an Intent to launch the app under test. This approach is useful when you are only interested in testing the calculator app, and don't care about the launcher.
Kotlin
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) }
Java
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); }
Verify results
The InstrumentationTestCase extends TestCase, so you can use standard JUnit Assert methods to test that UI components in the app return the expected results.
The following snippet shows how your test can locate several buttons in a calculator app, click on them in order, then verify that the correct result is displayed.
Kotlin
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) }
Java
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()); }
Run UI Automator tests on a device or emulator
You can run UI Automator tests from Android Studio or from the
command-line. Make sure to specify AndroidJUnitRunner
as the default
instrumentation runner in your project.
More examples
Interact with the System UI
UI Automator can interact with everything on the screen, including system elements outside of your app, as shown in the following code snippets:
Kotlin
// Opens the System Settings. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) device.executeShellCommand("am start -a android.settings.SETTINGS")
Java
// Opens the System Settings. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); device.executeShellCommand("am start -a android.settings.SETTINGS");
Kotlin
// Opens the notification shade. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) device.openNotification()
Java
// Opens the notification shade. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); device.openNotification();
Kotlin
// Opens the Quick Settings shade. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) device.openQuickSettings()
Java
// Opens the Quick Settings shade. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); device.openQuickSettings();
Kotlin
// Get the system clock. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock")) print(clock.getText())
Java
// Get the system clock. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock")); print(clock.getText());
Wait for transitions
Screen transitions can take time and predicting their duration is unreliable, so you should have UI Automator wait after performing operations. UI Automator provides multiple methods for this:
UiDevice.performActionAndWait(Runnable action, EventCondition<U> condition, long timeout)
: For example, to click a button and wait until a new window appears, calldevice.performActionAndWait(() -> button.click(), Until.newWindow(), timeout)
UiDevice.wait(Condition<Object, U> condition, long timeout)
: For example, to wait until there is a certainUiObject2
on device, calldevice.wait(device.hasObject(By.text("my_text")), timeout);
UiObject2.wait(@NonNull Condition<Object, U> condition, long timeout)
: For example, to wait until a checkbox is checked, callcheckbox.wait(Until.checked(true), timeout);
UiObject2.clickAndWait(@NonNull EventCondition<U> condition, long timeout)
: For example to click a button and wait until a new window appears, callbutton.clickAndWait(Until.newWindow(), timeout);
UiObject2.scrollUntil(@NonNull Direction direction, @NonNull Condition<Object, U> condition)
: For example to scroll down until a new object appears, callobject.scrollUntil(Direction.DOWN, Until.hasObject(By.text('new_obj')));
UiObject2.scrollUntil(@NonNull Direction direction, @NonNull EventCondition<U> condition)
: For example to scroll down to the bottom, callobject.scrollUntil(Direction.DOWN, Until.scrollFinished(Direction.DOWN));
The following code snippet shows how to use UI Automator to turn off Do Not
Disturb mode in System settings using the performActionAndWait()
method that
waits for transitions:
Kotlin
@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)) }
Java
@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)); }
Additional resources
For more information about using UI Automator in Android tests, consult the following resources.
Reference documentation:
Samples
- BasicSample: Basic UI Automator sample.