Фреймворк UI Automator предоставляет набор API для создания тестов пользовательского интерфейса, которые взаимодействуют с пользовательскими и системными приложениями.
Введение в современное тестирование UI Automator
В UI Automator 2.4 представлен оптимизированный, удобный для Kotlin предметно-ориентированный язык (DSL), который упрощает написание тестов пользовательского интерфейса для Android. Этот новый API-интерфейс ориентирован на поиск элементов на основе предикатов и явный контроль над состояниями приложения. Используйте его, чтобы создавать более надежные и простые в обслуживании автоматизированные тесты.
UI Automator позволяет тестировать приложение вне его процесса. Это позволяет тестировать конечные версии с минификацией. UI Automator также помогает при написании тестов макротестирования.
Основные особенности современного подхода:
- Специальная область тестирования
uiAutomatorдля более понятного и выразительного кода тестов. - Методы
onElement,onElementsиonElementOrNullдля поиска элементов интерфейса с четкими предикатами. - Встроенный механизм ожидания для условных элементов
onElement*(timeoutMs: Long = 10000) - Явное управление состоянием приложения, например
waitForStableиwaitForAppToBeVisible. - Прямое взаимодействие с узлами окна специальных возможностей для тестирования в сценариях многооконного режима.
- Встроенные возможности создания скриншотов и
ResultsReporterдля визуального тестирования и отладки.
Настройте проект
Чтобы начать использовать современные API UI Automator, добавьте в файл build.gradle.kts проекта последнюю зависимость:
Kotlin
dependencies {
...
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05")
}
Яркий
dependencies {
...
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05"
}
Основные понятия API
В следующих разделах описаны основные концепции современного API UI Automator.
Область тестирования uiAutomator
Доступ ко всем новым API UI Automator можно получить в блоке uiAutomator { ... }. Эта функция создает UiAutomatorTestScope, которая обеспечивает краткую и типобезопасную среду для тестовых операций.
uiAutomator {
// All your UI Automator actions go here
startApp("com.example.targetapp")
onElement { textAsString() == "Hello, World!" }.click()
}
Как найти элементы интерфейса
Используйте API UI Automator с предикатами, чтобы находить элементы интерфейса. Эти предикаты позволяют определять условия для таких свойств, как текст, выбранное или сфокусированное состояние и описание контента.
onElement { predicate }– возвращает первый элемент интерфейса, соответствующий предикату, в течение времени ожидания по умолчанию. Если функция не находит соответствующий элемент, она выдает исключение.// Find a button with the text "Submit" and click it onElement { textAsString() == "Submit" }.click() // Find a UI element by its resource ID onElement { viewIdResourceName == "my_button_id" }.click() // Allow a permission request watchFor(PermissionDialog) { clickAllow() }onElementOrNull { predicate }: аналогичноonElement, но возвращаетnull, если функция не находит соответствующий элемент в течение времени ожидания. Исключение не создается. Этот метод используется для необязательных элементов.val optionalButton = onElementOrNull { textAsString() == "Skip" } optionalButton?.click() // Click only if the button existsonElements { predicate }– ожидает, пока хотя бы один элемент интерфейса не будет соответствовать заданному предикату, а затем возвращает список всех соответствующих элементов интерфейса.// Get all items in a list Ui element val listItems = onElements { className == "android.widget.TextView" && isClickable } listItems.forEach { it.click() }
Вот несколько советов по использованию звонков onElement:
Вызовы
onElementдля вложенных элементов. Вы можете вызыватьonElementдля поиска элементов внутри других элементов, следуя иерархии "родительский – дочерний".// Find a parent Ui element with ID "first", then its child with ID "second", // then its grandchild with ID "third", and click it. onElement { viewIdResourceName == "first" } .onElement { viewIdResourceName == "second" } .onElement { viewIdResourceName == "third" } .click()Укажите время ожидания для функций
onElement*, передав значение в миллисекундах.// Find a Ui element with a zero timeout (instant check) onElement(0) { viewIdResourceName == "something" }.click() // Find a Ui element with a custom timeout of 10 seconds onElement(10_000) { textAsString() == "Long loading text" }.click()
Взаимодействие с элементами интерфейса
Взаимодействуйте с элементами интерфейса, имитируя клики или вводя текст в редактируемые поля.
// Click a Ui element
onElement { textAsString() == "Tap Me" }.click()
// Set text in an editable field
onElement { className == "android.widget.EditText" }.setText("My input text")
// Perform a long click
onElement { contentDescription == "Context Menu" }.longClick()
Как обрабатывать состояния приложения и наблюдателей
Управляйте жизненным циклом приложения и обрабатывайте неожиданные элементы интерфейса, которые могут появиться во время тестирования.
Управление жизненным циклом приложений
API позволяют управлять состоянием тестируемого приложения:
// Start a specific app by package name. Used for benchmarking and other
// self-instrumenting tests.
startApp("com.example.targetapp")
// Start a specific activity within the target app
startActivity(SomeActivity::class.java)
// Start an intent
startIntent(myIntent)
// Clear the app's data (resets it to a fresh state)
clearAppData("com.example.targetapp")
Обработка неожиданных элементов интерфейса
API watchFor позволяет определять обработчики для неожиданных элементов интерфейса, таких как диалоговые окна разрешений, которые могут появляться во время тестирования. Этот способ использует внутренний механизм наблюдателя, но предлагает больше гибкости.
import androidx.test.uiautomator.PermissionDialog
@Test
fun myTestWithPermissionHandling() = uiAutomator {
startActivity(MainActivity::class.java)
// Register a watcher to click "Allow" if a permission dialog appears
watchFor(PermissionDialog) { clickAllow() }
// Your test steps that might trigger a permission dialog
onElement { textAsString() == "Request Permissions" }.click()
// Example: You can register a different watcher later if needed
clearAppData("com.example.targetapp")
// Now deny permissions
startApp("com.example.targetapp")
watchFor(PermissionDialog) { clickDeny() }
onElement { textAsString() == "Request Permissions" }.click()
}
PermissionDialog – это пример ScopedWatcher<T>, где T – объект, переданный в качестве области действия блоку в watchFor. Вы можете создавать собственные
наблюдатели на основе этого шаблона.
Дождитесь, пока приложение станет видимым и стабильным
Иногда тесты должны дождаться, пока элементы станут видимыми или стабильными. Для этого в UI Automator есть несколько API.
waitForAppToBeVisible("com.example.targetapp") ожидает появления на экране элемента интерфейса с заданным названием пакета в течение настраиваемого времени ожидания.
// Wait for the app to be visible after launching it
startApp("com.example.targetapp")
waitForAppToBeVisible("com.example.targetapp")
Используйте API waitForStable(), чтобы убедиться, что интерфейс приложения стабилен, прежде чем взаимодействовать с ним.
// Wait for the entire active window to become stable
activeWindow().waitForStable()
// Wait for a specific Ui element to become stable (e.g., after a loading animation)
onElement { viewIdResourceName == "my_loading_indicator" }.waitForStable()
Как использовать UI Automator для макротестов и базовых профилей
Используйте UI Automator для тестирования производительности с помощью Jetpack Macrobenchmark и для создания базовых профилей, поскольку он обеспечивает надежный способ взаимодействия с приложением и измерения производительности с точки зрения конечного пользователя.
Macrobenchmark использует API UI Automator для управления пользовательским интерфейсом и измерения взаимодействий.
Например, в тестах запуска вы можете использовать onElement, чтобы определить, когда контент пользовательского интерфейса полностью загружен, что позволяет измерить время до полного отображения (TTFD). В тестах на задержки API UI Automator используются для прокрутки списков или запуска анимации, чтобы измерить время формирования кадра. Функции startActivity() и startIntent() позволяют привести приложение в нужное состояние перед началом измерений.
При создании базовых профилей вы автоматизируете алгоритмы достижения цели (CUJ) в приложении, чтобы записать, какие классы и методы требуют предварительной компиляции. UI Automator – идеальный инструмент для написания таких скриптов. Современный поиск элементов на основе предикатов DSL и встроенные механизмы ожидания (onElement) обеспечивают более надежное и детерминированное выполнение тестов по сравнению с другими методами.
Это снижает нестабильность и гарантирует, что сгенерированный профиль базовой конфигурации точно отражает пути выполнения кода, выполняемые во время наиболее важных путей пользователя.
Расширенные функции
Для более сложных сценариев тестирования можно использовать следующие функции:
Как работать с несколькими окнами
API UI Automator позволяют напрямую взаимодействовать с элементами интерфейса и проверять их. Это особенно полезно в сценариях с несколькими окнами, например в режиме "картинка в картинке" или при разделении экрана.
// Find the first window that is in Picture-in-Picture mode
val pipWindow = windows()
.first { it.isInPictureInPictureMode == true }
// Now you can interact with elements within that specific window
pipWindow.onElement { textAsString() == "Play" }.click()
Скриншоты и визуальные утверждения
Делайте скриншоты всего экрана, определенных окон или отдельных элементов интерфейса прямо в тестах. Это полезно для визуального регрессионного тестирования и отладки.
uiautomator {
// Take a screenshot of the entire active window
val fullScreenBitmap: Bitmap = activeWindow().takeScreenshot()
fullScreenBitmap.saveToFile(File("/sdcard/Download/full_screen.png"))
// Take a screenshot of a specific UI element (e.g., a button)
val buttonBitmap: Bitmap = onElement { viewIdResourceName == "my_button" }.takeScreenshot()
buttonBitmap.saveToFile(File("/sdcard/Download/my_button_screenshot.png"))
// Example: Take a screenshot of a PiP window
val pipWindowScreenshot = windows()
.first { it.isInPictureInPictureMode == true }
.takeScreenshot()
pipWindowScreenshot.saveToFile(File("/sdcard/Download/pip_screenshot.png"))
}
Функция расширения saveToFile для Bitmap упрощает сохранение полученного изображения по указанному пути.
Используйте ResultsReporter для отладки
ResultsReporter помогает связывать тестовые артефакты, например скриншоты, непосредственно с результатами тестирования в Android Studio, чтобы упростить проверку и отладку.
uiAutomator {
startApp("com.example.targetapp")
val reporter = ResultsReporter("MyTestArtifacts") // Name for this set of results
val file = reporter.addNewFile(
filename = "my_screenshot",
title = "Accessible button image" // Title that appears in Android Studio test results
)
// Take a screenshot of an element and save it using the reporter
onElement { textAsString() == "Accessible button" }
.takeScreenshot()
.saveToFile(file)
// Report the artifacts to instrumentation, making them visible in Android Studio
reporter.reportToInstrumentation()
}
Переход с более старых версий UI Automator
Если у вас есть тесты UI Automator, написанные с использованием старых API, используйте следующую таблицу в качестве справочника для перехода на современный подход:
| Тип операции | Старый метод UI Automator | Новый метод UI Automator |
|---|---|---|
| Точка входа | UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) |
Оберните логику тестирования в область видимости uiAutomator { ... }. |
| Как найти элементы интерфейса | device.findObject(By.res("com.example.app:id/my_button")) |
onElement { viewIdResourceName == "my\_button" } |
| Как найти элементы интерфейса | device.findObject(By.text("Click Me")) |
onElement { textAsString() == "Click Me" } |
| Ожидание бездействия интерфейса | device.waitForIdle() |
Рекомендуется использовать встроенный механизм тайм-аута onElement, в противном случае – activeWindow().waitForStable(). |
| Найти дочерние элементы | findObject вызов, вложенный вручную |
Цепочки onElement().onElement() |
| Обработка диалоговых окон с запросом разрешений | UiAutomator.registerWatcher() |
watchFor(PermissionDialog) |