UI Test Development for Android Application (UI Automator)
UI Automator is not Espresso replacement. It's tool for different class of tasks: system permission dialogs, transitions between apps, notifications in shade, keyboard interaction at IME level. Espresso simply can't reach — it's limited to single process. UI Automator works via UiDevice and InstrumentationRegistry, controlling device at Accessibility Service level.
When Espresso Stops Working
Typical scenario: app requests camera permission via ActivityResultContracts.RequestPermission(). Permission dialog is system UI from different process (com.android.permissioncontroller). Espresso crashes with NoMatchingViewException, can't see "Allow" button outside its hierarchy.
Another case — testing app behavior on push notification. Need to open shade, tap notification and check correct screen opened. Without UiDevice.openNotification() and UiScrollable can't write this.
Third scenario often underestimated: deep link testing from browser. User clicks link in Chrome, Android shows app chooser bottomsheet. UI Automator lets select needed option programmatically via UiSelector().text("Open in MyApp").
Writing Tests with UI Automator
Basic architecture built on three objects:
-
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())— entry point, gives access to whole device -
UiObject2— modern API (API 18+), xpath-like search viaBy.res(),By.text(),By.desc() -
UiSelector+UiObject— old API, but still needed forUiScrollable
Example handling permission dialog:
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
val allowButton = device.wait(
Until.findObject(By.text("Allow").pkg("com.android.permissioncontroller")),
3000
)
allowButton?.click() ?: fail("Permission dialog did not appear within 3s")
device.wait() with timeout — mandatory. System dialogs appear asynchronously. Without wait test unstable on different CI machine loads.
Working with Notifications
device.openNotification()
device.wait(Until.hasObject(By.text("New Message")), 5000)
val notification = device.findObject(By.text("New Message"))
notification.click()
// Check MessagesActivity opened
val activityLabel = device.wait(Until.findObject(By.res("com.example.app:id/toolbar_title")), 3000)
assertThat(activityLabel?.text).isEqualTo("Messages")
Espresso Integration
In practice tests mix both frameworks: UI Automator for system interactions, Espresso for UI checks inside app. Absolutely normal — they don't conflict, both work via Instrumentation.
// UI Automator: handle OS dialog
device.findObject(By.text("Allow")).click()
// Espresso: check app UI state
onView(withId(R.id.cameraPreview)).check(matches(isDisplayed()))
Test Instability — Main Problem
Flaky tests on UI Automator more common than on Espresso. Several reasons:
System animations. On devices without disabled developer options (ANIMATOR_DURATION_SCALE, TRANSITION_ANIMATION_SCALE, WINDOW_ANIMATION_SCALE = 0), transitions slow UI and Until.findObject() with short timeout fails. In AndroidJUnitRunner subclass or via adb shell settings put global disable explicitly.
Different firmware. Samsung One UI changes system button text: "Allow" → "Allow only while using the app". By.text("Allow") finds both, but if need specific — use By.textStartsWith() or By.textContains().
Notification order. Shade may have multiple notifications. Use By.res() with package qualifier, not By.text() by notification text which may match another.
Project Structure
UI Automator tests live in androidTest/ next to Espresso. In build.gradle.kts:
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
androidTestImplementation("androidx.test:runner:1.5.2")
androidTestImplementation("androidx.test:rules:1.5.0")
Version uiautomator:2.3.0 — current, with BySelector and UiObject2 support. Old com.android.support.test.uiautomator:uiautomator-v18 obsolete.
Run via Fastlane or Gradle:
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.SystemPermissionsTest
Scope of Work
- Writing tests for system dialogs (permissions, app selection, Intent Chooser)
- Notification tests: appearance, swipe, tap, deep link
- Inter-app scenarios (share, open in, clipboard)
- Integration with existing Espresso tests
- CI setup (GitHub Actions / GitLab CI / Bitbucket Pipelines)
- Disabling system animations for stable execution
Timelines
3–5 days depending on system scenario quantity. Simple set (2–3 permission dialogs + notifications) — 3 days. Complex inter-app scenarios with multiple flows — 5 days. Cost calculated individually after requirements analysis.







