Розробка 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 днів. Стоимость рассчитывается індивідуально після аналізу вимог.







