Write automated tests with UI Automator

Stay organized with collections Save and categorize content based on your preferences.

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:

  1. Change the device rotation.
  2. Press a hardware key, such as "volume up".
  3. Press the Back, Home, or Menu buttons.
  4. Open the notification shade.
  5. 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:

  • UiCollection: Enumerates a container's UI elements for the purpose of counting, or targeting sub-elements by their visible text or content-description property.
  • UiObject: Represents a UI element that is visible on the device.
  • UiScrollable: Provides support for searching for items in a scrollable UI container.
  • UiSelector: Represents a query for one or more target UI elements on a device.
  • Configurator: Allows you to set key parameters for running UI Automator tests.

For example, the following code shows how you can write a test script that displays the default app launcher in the device:

Kotlin


device = UiDevice.getInstance(getInstrumentation())
device.pressHome()

// Bring up the default launcher by searching for a UI component
// that matches the content description for the launcher button.
val allAppsButton: UIObject = device.findObject(
UiSelector().description("Apps"))

// Perform a click on the button to load the launcher.
allAppsButton.clickAndWaitForNewWindow()

Java


device = UiDevice.getInstance(getInstrumentation());
device.pressHome();

// Bring up the default launcher by searching for a UI component
// that matches the content description for the launcher button.
UiObject allAppsButton = device
.findObject(new UiSelector().description("Apps"));

// Perform a click on the button to load the launcher.
allAppsButton.clickAndWaitForNewWindow();

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:$uiAutomatorVersion')
}

Groovy

dependencies {
  ...
  androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}

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:

  1. Launch the target app on a physical device.
  2. Connect the device to your development machine.
  3. Open a terminal window and navigate to the <android-sdk>/tools/ directory.
  4. Run the tool with this command:
 $ uiautomatorviewer

To view the UI properties for your application:

  1. In the uiautomatorviewer interface, click the Device Screenshot button.
  2. 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.
  3. 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:

  1. Create a concrete class that extends ExploreByTouchHelper.
  2. 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:

  1. 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.
  2. Get a UiObject 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.
  3. Simulate a specific user interaction to perform on that UI component, by calling a UiObject method; for example, call performMultiPointerGesture() to simulate a multi-touch gesture, 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.
  4. 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 UiObject which represents a view that matches a given selector criteria. You can reuse the UiObject 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 UiObject instance to click on a UI element or query a property.

The following snippet shows how your test might construct UiObject instances that represent a Cancel button and a OK button in an app.

Kotlin


val cancelButton: UiObject = device.findObject(
  UiSelector().text("Cancel").className("android.widget.Button")
)
val okButton: UiObject = device.findObject(
  UiSelector().text("OK").className("android.widget.Button")
)

// Simulate a user-click on the OK button, if found.
if (okButton.exists() && okButton.isEnabled) {
  okButton.click()
}

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);
  }
}

Specify a selector

If you want to access a specific UI component in an app, use the UiSelector class. This class represents a query for specific elements in the currently displayed UI.

If more than one matching element is found, the first matching element in the layout hierarchy is returned as the target UiObject. When constructing a UiSelector, you can chain together multiple properties to refine your search. If no matching UI element is found, a UiAutomatorObjectNotFoundException is thrown.

You can use the childSelector() method to nest multiple UiSelector instances. For example, the following code example shows how your test might specify a search to find the first ListView in the currently displayed UI, then search within that ListView to find a UI element with the text property Apps.

Kotlin


val appItem: UiObject = device.findObject(
  UiSelector().className("android.widget.ListView")
  .instance(0)
  .childSelector(
    UiSelector().text("Apps")
  )
)

Java


UiObject cancelButton = device.findObject(new UiSelector()
.text("Cancel")
.className("android.widget.Button"));
UiObject okButton = device.findObject(new UiSelector()
.text("OK")
.className("android.widget.Button"));

// Simulate a user-click on the OK button, if found.
if(okButton.exists() && okButton.isEnabled()) {
  okButton.click();
}

As a best practice, when specifying a selector, you should use a Resource ID (if one is assigned to a UI element) instead of a text element or content-descriptor. Not all elements have a text element (for example, icons in a toolbar). Text selectors are brittle and can lead to test failures if there are minor changes to the UI. They may also not scale across different languages; your text selectors may not match translated strings.

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 UiObject object, you can call the methods in the UiObject 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.
  • dragTo() : Drags this object to arbitrary coordinates.
  • setText() : Sets the text in an editable field, after clearing the field's content. Conversely, the clearTextField() method clears the existing text in an editable field.
  • swipeUp() : Performs the swipe up action on the UiObject. Similarly, the swipeDown(), swipeLeft(), and swipeRight() methods perform corresponding actions.

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);
}

Perform actions on collections

Use the UiCollection class if you want to simulate user interactions on a collection of items (for example, songs in a music album or a list of emails in an Inbox). To create a UiCollection object, specify a UiSelector that searches for a UI container or a wrapper of other child UI elements, such as a layout view that contains child UI elements.

The following code snippet shows how your test might construct a UiCollection to represent a video album that is displayed within a FrameLayout:

Kotlin


val videos = UiCollection(UiSelector().className("android.widget.FrameLayout"))

// Retrieve the number of videos in this collection:
val count = videos.getChildCount(
  UiSelector().className("android.widget.LinearLayout")
)

// Find a specific video and simulate a user-click on it
val video: UiObject = videos.getChildByText(
  UiSelector().className("android.widget.LinearLayout"),
  "Cute Baby Laughing"
)
video.click()

// Simulate selecting a checkbox that is associated with the video
val checkBox: UiObject = video.getChild(
  UiSelector().className("android.widget.Checkbox")
)
if (!checkBox.isSelected) checkBox.click()

Java


UiCollection videos = new UiCollection(new UiSelector()
.className("android.widget.FrameLayout"));

// Retrieve the number of videos in this collection:
int count = videos.getChildCount(new UiSelector()
.className("android.widget.LinearLayout"));

// Find a specific video and simulate a user-click on it
UiObject video = videos.getChildByText(new UiSelector()
.className("android.widget.LinearLayout"), "Cute Baby Laughing");
video.click();

// Simulate selecting a checkbox that is associated with the video
UiObject checkBox = video.getChild(new UiSelector()
.className("android.widget.Checkbox"));
if(!checkBox.isSelected()) checkbox.click();

Perform actions on scrollable views

Use the UiScrollable class to simulate vertical or horizontal scrolling across a display. This technique is helpful when a UI element is positioned off-screen and you need to scroll to bring it into view.

The following code snippet shows how to simulate scrolling down the Settings menu and clicking on an About tablet option:

Kotlin


val settingsItem = UiScrollable(UiSelector().className("android.widget.ListView"))
val about: UiObject = settingsItem.getChildByText(
  UiSelector().className("android.widget.LinearLayout"),
  "About tablet"
)
about.click()

Java


UiScrollable settingsItem = new UiScrollable(new UiSelector()
.className("android.widget.ListView"));
UiObject about = settingsItem.getChildByText(new UiSelector()
.className("android.widget.LinearLayout"), "About tablet");
about.click();

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(UiSelector().packageName(CALC_PACKAGE).resourceId("two")).click()
  device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("plus")).click()
  device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("three")).click()
  device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("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(new UiSelector()
  .packageName(CALC_PACKAGE).resourceId("two")).click();
  device.findObject(new UiSelector()
  .packageName(CALC_PACKAGE).resourceId("plus")).click();
  device.findObject(new UiSelector()
  .packageName(CALC_PACKAGE).resourceId("three")).click();
  device.findObject(new UiSelector()
  .packageName(CALC_PACKAGE).resourceId("equals")).click();

  // Verify the result = 5
  UiObject 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.

Additional resources

For more information about using UI Automator in Android tests, consult the following resources.

Reference documentation:

Samples