Написание автоматизированных тестов с помощью UI Automator

Фреймворк тестирования UI Automator предоставляет набор API для создания тестов пользовательского интерфейса, взаимодействующих с пользовательскими и системными приложениями.

Введение в современное тестирование UI Automator

UI Automator 2.4 представляет собой оптимизированный, совместимый с Kotlin предметно-ориентированный язык (DSL), который упрощает написание UI-тестов для Android. Этот новый API-интерфейс ориентирован на поиск элементов на основе предикатов и явный контроль над состояниями приложения. Используйте его для создания более простых в обслуживании и надежных автоматизированных тестов.

UI Automator позволяет тестировать приложение вне процесса его разработки. Это позволяет тестировать версии релиза с применением минификации. UI Automator также помогает при написании макробенчмарк-тестов.

Ключевые особенности современного подхода включают в себя:

  • Специальная тестовая область uiAutomator для более чистого и выразительного тестового кода.
  • Такие методы, как onElement , onElements и onElementOrNull для поиска элементов пользовательского интерфейса с четкими предикатами.
  • Встроенный механизм ожидания для условных элементов onElement*(timeoutMs: Long = 10000)
  • Явное управление состоянием приложения, такое как waitForStable и waitForAppToBeVisible .
  • Прямое взаимодействие с узлами окна доступности для многооконного тестирования.
  • Встроенные возможности создания снимков экрана и ResultsReporter для визуального тестирования и отладки.

Настройте свой проект

Чтобы начать использовать современные API UI Automator, обновите файл build.gradle.kts вашего проекта, включив в него последнюю зависимость :

Котлин

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 { id == "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 exists 
  • onElements { 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 { id == "first" }   .onElement { id == "second" }   .onElement { id == "third" }   .click() 
  • Укажите время ожидания для функций onElement* , передав значение, представляющее миллисекунды.

    // Find a Ui element with a zero timeout (instant check) onElement(0) { id == "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 { id == "my_loading_indicator" }.waitForStable() 

Расширенные функции

Следующие функции полезны для более сложных сценариев тестирования.

Взаимодействие с несколькими окнами

API UI Automator позволяют напрямую взаимодействовать с элементами пользовательского интерфейса и просматривать их. Это особенно полезно в сценариях с несколькими окнами, например, в режиме «картинка в картинке» (PiP) или в режимах с разделённым экраном.

// 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 { id == "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 { id == "my\_button" }
Найти элементы пользовательского интерфейса device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
Ожидание бездействия пользовательского интерфейса device.waitForIdle() Предпочитать встроенный механизм тайм-аута onElement ; в противном случае activeWindow().waitForStable()
Найти дочерние элементы Вложенные вручную вызовы findObject цепочка onElement().onElement()
Обработка диалоговых окон с разрешениями UiAutomator.registerWatcher() watchFor(PermissionDialog)