Написание автоматизированных тестов с помощью 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)