UI Automator is a framework from Google for automated UI testing of Android applications that operates at the system level and can interact with interface elements beyond a single app. Unlike Espresso, UI Automator is not tied to a specific app’s process: it can open system dialogs, the notification shade, and switch between apps. According to Google Android Developers, UI Automator uses the standard Accessibility Service to access the device’s UI tree.
Key Takeaways
UI Automator is a framework for functional UI testing of Android that operates at the operating system level. It provides an API for accessing any element on the device screen, regardless of which app it belongs to — including the system status bar, permission dialogs, the home screen, and third-party apps. This makes it indispensable for testing scenarios that extend beyond a single app.
Architecturally, UI Automator uses the Accessibility Service — the same service used by TalkBack, Switch Access, and other accessibility tools. Through this service, the framework obtains the complete UI component tree of the current screen and allows performing actions on them: tapping, swiping, text input, and long pressing.
UI Automator first appeared in Android 4.3 (API 18) and has since been part of the Android Testing Support Library as Google’s official tool for cross-application testing. In AndroidX Test, it’s available as a separate artifact androidx.test.uiautomator:uiautomator version 2.3.0 (2024), which supports all Android versions from API 18.
How it works: UI Automator is based on scanning the Accessibility tree of the current screen. When the findObject(selector) method is called, the framework traverses the View hierarchy, finds the first element matching the UiSelector conditions, and returns a UiObject — a proxy for interacting with the actual View.
A typical UI Automator test starts by obtaining a UiDevice instance, which represents the physical device. UiDevice provides methods for finding elements, managing button presses (Home, Back, Recent), rotating the screen, and taking screenshots. After finding an element via UiSelector, actions are performed on the UiObject.
In the example below, the test opens the Settings app, finds the “Battery” item by text, and taps it. UI Automator does not require launching an Activity — it works with any screen on the device, including third-party apps.
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
// Open settings screen
device.pressHome()
device.wait(Until.hasObject(UiSelector().text("Settings")), 2000)
// Find the "Battery" item and tap
val batteryItem = device.findObject(
UiSelector().text("Battery")
)
batteryItem.clickAndWait(Until.newWindow(), 3000)
UiDevice is the main class for interacting with the device. It provides methods for finding elements, simulating hardware button presses (Home, Back, Menu, Volume), managing power, taking screenshots, and waiting for specific screen states. UiDevice is created once per test and reused for all operations.
UiSelector is a fluent API for finding UI elements. Unlike Espresso ViewMatchers, UiSelector does not require compilation — search conditions are formed through a method chain: text(), className(), description(), resourceId(), index(). Multiple conditions are combined automatically via logical AND.
| UiSelector Method | Purpose |
|---|---|
| text(String) | Search by exact element text |
| textContains(String) | Search by partial text match |
| resourceId(String) | Search by resource ID (e.g., com.example:id/button) |
| className(String) | Search by View class name |
| description(String) | Search by content-description |
| childSelector(selector) | Search for a child element within a container |
When multiple elements on the screen share the same text, UiSelector allows combining criteria: find a container by ID, then within it — an element by text and class. This guarantees unique identification of the desired component. The childSelector method narrows the search scope to a specified container, speeding up navigation through the UI tree.
val scrollView = device.findObject(
UiSelector().resourceId("android:id/list")
)
// Inside the list, find the element with text "Wi-Fi"
val wifiItem = scrollView.findObject(
UiSelector().text("Wi-Fi")
wifiItem.click()
Cross-application (inter-application) testing is the main feature for which UI Automator is chosen. The framework can switch between apps, test OAuth login via a browser, check system dialogs (permissions, app chooser), and interact with the system status bar, notification panel, and lock screen.
A typical cross-app test scenario: the app opens a browser for OAuth authorization, the user enters their login and password, and the browser redirects back to the app. UI Automator switches between processes, finds input fields in the browser, fills them in, and taps “Sign In”.
// Waiting for browser to appear
device.wait(Until.hasObject(
UiSelector().packageName("com.android.chrome")
), 5000)
// Searching for email input field in the browser
val emailField = device.findObject(
UiSelector().className("android.widget.EditText").instance(0)
)
emailField.text = "user@example.com"
UI Automator can check and dismiss system dialogs — location permissions, notifications, file access. This is critically important for testing first launch scenarios when the system sequentially requests multiple permissions. Without UI Automator, such scenarios cannot be automated because system dialogs do not belong to the app’s process.
The choice between UI Automator and Espresso depends on the testing scenario. Espresso is optimized for testing a single app with automatic synchronization and minimal boilerplate. UI Automator is suited for scenarios where you need to interact with the system, browser, or multiple apps.
| Criterion | UI Automator | Espresso |
|---|---|---|
| Scope | Entire device, multiple apps | Single app |
| Synchronization | Manual (wait, sleep) | Automatic (Idling Resource) |
| Speed | Slower (access via service) | Faster (works within process) |
| System UI | Supports (Notifications, Quick Settings) | Does not support |
| Search Precision | UiSelector by attributes | ViewMatchers by type and hierarchy |
| Stability | Lower (depends on timing) | Higher (automatic waiting) |
In practice, these frameworks are often used together: Espresso covers UI tests of the main app with high stability, while UI Automator is brought in for scenarios that extend beyond the app’s boundaries — OAuth login, system permissions, working with Share Intent. This combination provides maximum UI coverage with minimal test maintenance costs.
Integration of UI Automator is done by adding a dependency to build.gradle. The framework is part of AndroidX Test and does not require additional manifest permissions — access to the Accessibility Service is configured automatically when the instrumented test is launched.
The minimal configuration includes the uiautomator artifact and the standard AndroidJUnitRunner test runner. UI Automator tests are placed in the src/androidTest directory and run on an emulator or physical device running Android API 18+.
dependencies {
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test:runner:1.6.1")
}
To obtain a UiDevice instance, InstrumentationRegistry.getInstrumentation() is used. UiDevice should be created once in the setUp() method and reused across all tests in the class to conserve device resources. It’s important to note that UiDevice is not thread-safe — all operations must be performed within the same test method thread. Creating a new UiDevice in each test incurs overhead and slows down execution. It is recommended to create UiDevice once in the beforeClass method and reuse it for all tests in the test class.
Unlike Espresso, UI Automator does not have automatic synchronization. To wait for elements to appear, the UiDevice.wait(condition, timeout) method is used with an Until object: Until.findObject(selector), Until.hasObject(selector), Until.gone(selector). Without proper waits, tests become flaky due to race conditions — an element may not have appeared on the screen by the time of the search. It is recommended to set a timeout of at least 3–5 seconds for stability.
Frequently Asked Questions
UI Automator operates at the Accessibility Service level and can interact with any app. Espresso works within a single app’s process and uses automatic UI thread synchronization. UI Automator is better for cross-app scenarios, while Espresso is better for stable single-app tests.
Yes, UI Automator works on all devices running Android API 18+. It does not require root access — it uses the standard Accessibility Service, which is activated through Instrumentation when tests are launched.
UI Automator uses the Accessibility Service to obtain the complete UI component tree of the current screen. Then UiSelector traverses this tree and finds elements by specified criteria: text, class, ID, content-description, or a combination thereof.
Yes, the UiDevice.takeScreenshot(storePath) method allows taking a screenshot of the current screen and saving it to a file. This is useful for debugging: when a test fails, you can save the screenshot and analyze the screen state.
UI Automator does not have automatic synchronization, so tests are sensitive to timing. If an animation hasn’t finished or a View hasn’t rendered yet, findObject may not find the element. The solution is to use UiDevice.wait() with a sufficient timeout.
Summary
The UI Automator toolset covers all key cross-application testing scenarios and is the standard for Android automation at the system level.
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also