Разработка UI-тестов для Android-приложения (UI Automator)
UI Automator — это не замена Espresso. Это инструмент для другого класса задач: системные диалоги разрешений, переход между приложениями, уведомления в шторке, взаимодействие с клавиатурой на уровне IME. Espresso здесь просто не достанет — он ограничен одним процессом. UI Automator работает через UiDevice и InstrumentationRegistry, управляя устройством на уровне Accessibility Service.
Когда Espresso перестаёт работать
Типичный сценарий: приложение запрашивает разрешение на камеру через ActivityResultContracts.RequestPermission(). Диалог разрешения — это системный UI из другого процесса (com.android.permissioncontroller). Espresso падает с NoMatchingViewException, потому что не видит кнопку «Разрешить» за пределами своей иерархии View.
Другой случай — проверка поведения приложения при получении push-уведомления. Нужно опустить шторку, нажать на уведомление и проверить, что открылся правильный экран. Без UiDevice.openNotification() и UiScrollable это не написать.
Третий сценарий, который часто недооценивают: тестирование deep link из браузера. Пользователь нажимает ссылку в Chrome, Android показывает bottomsheet выбора приложения. UI Automator позволяет выбрать нужный вариант программно через UiSelector().text("Открыть в MyApp").
Как пишем тесты с UI Automator
Базовая архитектура теста строится на трёх объектах:
-
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())— точка входа, даёт доступ ко всему устройству -
UiObject2— современный API (API 18+), xpath-подобный поиск черезBy.res(),By.text(),By.desc() -
UiSelector+UiObject— старый API, но всё ещё нужен дляUiScrollable
Пример обработки диалога разрешения:
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
val allowButton = device.wait(
Until.findObject(By.text("Разрешить").pkg("com.android.permissioncontroller")),
3000
)
allowButton?.click() ?: fail("Permission dialog did not appear within 3s")
device.wait() с таймаутом — обязательно. Системные диалоги появляются асинхронно. Без ожидания тест нестабилен при разной загрузке CI-машины.
Работа с уведомлениями
device.openNotification()
device.wait(Until.hasObject(By.text("Новое сообщение")), 5000)
val notification = device.findObject(By.text("Новое сообщение"))
notification.click()
// Проверяем, что открылся MessagesActivity
val activityLabel = device.wait(Until.findObject(By.res("com.example.app:id/toolbar_title")), 3000)
assertThat(activityLabel?.text).isEqualTo("Сообщения")
Интеграция с Espresso
На практике тесты смешивают оба фреймворка: UI Automator для системных взаимодействий, Espresso для проверки UI внутри приложения. Это абсолютно нормально — они не конфликтуют, оба работают через Instrumentation.
// UI Automator: обрабатываем диалог ОС
device.findObject(By.text("Разрешить")).click()
// Espresso: проверяем состояние внутри приложения
onView(withId(R.id.cameraPreview)).check(matches(isDisplayed()))
Нестабильность тестов — главная проблема
Flaky tests на UI Automator встречаются чаще, чем на Espresso. Причин несколько:
Системные анимации. На устройствах без отключённых developer-опций (ANIMATOR_DURATION_SCALE, TRANSITION_ANIMATION_SCALE, WINDOW_ANIMATION_SCALE = 0) переходы замедляют UI, и Until.findObject() с коротким таймаутом проваливается. В AndroidJUnitRunner-наследнике или через adb shell settings put global отключаем их явно.
Разные прошивки. Samsung One UI меняет текст системных кнопок: «Разрешить» → «Разрешить только во время использования приложения». By.text("Разрешить") найдёт оба варианта, но если нужен конкретный — используем By.textStartsWith() или By.textContains().
Очерёдность уведомлений. В шторке может быть несколько уведомлений. Используем By.res() с package-квалификатором, а не By.text() по тексту уведомления, который может совпасть с другим.
Структура проекта
UI Automator тесты живут в androidTest/ рядом с Espresso. В 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")
Версия uiautomator:2.3.0 — актуальная, с поддержкой BySelector и UiObject2. Старая com.android.support.test.uiautomator:uiautomator-v18 устарела.
Запуск через Fastlane или Gradle:
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.SystemPermissionsTest
Что входит в работу
- Написание тестов для системных диалогов (разрешения, выбор приложения, Intent Chooser)
- Тесты уведомлений: появление, свайп, тап, deep link
- Тесты межприложенческих сценариев (share, open in, clipboard)
- Интеграция с существующими Espresso-тестами
- Настройка запуска в CI (GitHub Actions / GitLab CI / Bitbucket Pipelines)
- Отключение системных анимаций для стабильного прогона
Сроки
3–5 дней в зависимости от количества системных сценариев. Простой набор (2–3 диалога разрешений + уведомления) — 3 дня. Сложные межприложенческие сценарии с несколькими флоу — 5 дней. Стоимость рассчитывается индивидуально после анализа требований.







