Cómo escribir pruebas automatizadas con UI Automator

El framework de prueba de UI Automator proporciona un conjunto de APIs para compilar pruebas de IU que interactúan con las apps del usuario y las apps del sistema.

Introducción a las pruebas modernas de UI Automator

UI Automator 2.4 presenta un lenguaje específico de dominio (DSL) optimizado y compatible con Kotlin que simplifica la escritura de pruebas de IU para Android. Esta nueva plataforma de API se enfoca en la búsqueda de elementos basada en predicados y el control explícito sobre los estados de la app. Úsala para crear pruebas automatizadas más confiables y fáciles de mantener.

UI Automator te permite probar una app desde fuera de su proceso. Esto te permite probar versiones de lanzamiento con la minificación aplicada. UI Automator también ayuda a escribir pruebas de macrocomparativas.

Las funciones clave del enfoque moderno incluyen las siguientes:

  • Un alcance de prueba uiAutomator dedicado para un código de prueba más limpio y expresivo
  • Métodos como onElement, onElements y onElementOrNull para encontrar elementos de la IU con predicados claros.
  • Mecanismo de espera integrado para elementos condicionales onElement*(timeoutMs: Long = 10000)
  • Administración explícita del estado de la app, como waitForStable y waitForAppToBeVisible
  • Interacción directa con los nodos de la ventana de accesibilidad para situaciones de prueba de varias ventanas.
  • Funciones integradas de captura de pantalla y un ResultsReporter para pruebas visuales y depuración.

Configura tu proyecto

Para comenzar a usar las APIs modernas de UI Automator, actualiza el archivo build.gradle.kts de tu proyecto para incluir la dependencia más reciente:

Kotlin

dependencies {
  ...
  androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05")
}

Groovy

dependencies {
  ...
  androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05"
}

Conceptos básicos de la API

En las siguientes secciones, se describen los conceptos básicos de la API de UI Automator moderna.

El alcance de la prueba de uiAutomator

Accede a todas las nuevas APIs de UI Automator dentro del bloque uiAutomator { ... }. Esta función crea un UiAutomatorTestScope que proporciona un entorno conciso y con seguridad de tipos para tus operaciones de prueba.

uiAutomator {
  // All your UI Automator actions go here
  startApp("com.example.targetapp")
  onElement { textAsString() == "Hello, World!" }.click()
}

Cómo encontrar elementos de la IU

Usa las APIs de UI Automator con predicados para ubicar elementos de la IU. Estos predicados te permiten definir condiciones para propiedades como texto, estado seleccionado o enfocado, y descripción del contenido.

  • onElement { predicate }: Muestra el primer elemento de la IU que coincide con el predicado dentro de un tiempo de espera predeterminado. La función arroja una excepción si no encuentra un elemento coincidente.

    // 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 }: Similar a onElement, pero devuelve null si la función no encuentra ningún elemento coincidente dentro del tiempo de espera. No arroja una excepción. Usa este método para los elementos opcionales.

    val optionalButton = onElementOrNull { textAsString() == "Skip" }
    optionalButton?.click() // Click only if the button exists
    
  • onElements { predicate }: Espera hasta que al menos un elemento de la IU coincida con el predicado determinado y, luego, devuelve una lista de todos los elementos de la IU que coinciden.

    // Get all items in a list Ui element
    val listItems = onElements { className == "android.widget.TextView" && isClickable }
    listItems.forEach { it.click() }
    

A continuación, se incluyen algunas sugerencias para usar las llamadas a onElement:

  • Encadena llamadas a onElement para elementos anidados: Puedes encadenar llamadas a onElement para encontrar elementos dentro de otros elementos, siguiendo una jerarquía de elementos principales y secundarios.

    // 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()
    
  • Especifica un tiempo de espera para las funciones onElement* pasando un valor que represente milisegundos.

    // 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()
    

Interactúa con elementos de la IU

Interactúa con elementos de la IU simulando clics o configurando texto en campos editables.

// 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()

Cómo controlar los estados y los observadores de la app

Administra el ciclo de vida de tu app y controla los elementos de la IU inesperados que puedan aparecer durante las pruebas.

Administración del ciclo de vida de la app

Las APIs proporcionan formas de controlar el estado de la app que se está probando:

// 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")

Cómo controlar la IU inesperada

La API de watchFor te permite definir controladores para elementos de IU inesperados, como diálogos de permisos, que pueden aparecer durante el flujo de prueba. Este usa el mecanismo de supervisión interno, pero ofrece más flexibilidad.

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 es un ejemplo de ScopedWatcher<T>, en el que T es el objeto que se pasa como un alcance al bloque en watchFor. Puedes crear observadores personalizados basados en este patrón.

Espera a que la app esté visible y estable

A veces, las pruebas deben esperar a que los elementos se vuelvan visibles o estables. UI Automator ofrece varias APIs para ayudarte con esto.

El waitForAppToBeVisible("com.example.targetapp") espera a que aparezca en la pantalla un elemento de IU con el nombre de paquete determinado dentro de un tiempo de espera personalizable.

// Wait for the app to be visible after launching it
startApp("com.example.targetapp")
waitForAppToBeVisible("com.example.targetapp")

Usa la API de waitForStable() para verificar que la IU de la app se considere estable antes de interactuar con ella.

// 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()

Funciones avanzadas

Las siguientes funciones son útiles para situaciones de prueba más complejas.

Interactúa con varias ventanas

Las APIs de UI Automator te permiten interactuar directamente con los elementos de la IU y examinarlos. Esto es especialmente útil para situaciones que involucran varias ventanas, como el modo de pantalla en pantalla (PIP) o los diseños de pantalla dividida.

// 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()

Capturas de pantalla y aserciones visuales

Captura capturas de pantalla de toda la pantalla, ventanas específicas o elementos de IU individuales directamente en tus pruebas. Esto es útil para las pruebas de regresión visual y la depuración.

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"))
}

La función de extensión saveToFile para Bitmap simplifica el guardado de la imagen capturada en una ruta de acceso especificada.

Usa ResultsReporter para la depuración

ResultsReporter te ayuda a asociar artefactos de prueba, como capturas de pantalla, directamente con los resultados de las pruebas en Android Studio para facilitar la inspección y la depuración.

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()
}

Migra desde versiones anteriores de UI Automator

Si tienes pruebas de UI Automator existentes escritas con superficies de API anteriores, usa la siguiente tabla como referencia para migrar al enfoque moderno:

Tipo de acción Método anterior de UI Automator Nuevo método de UI Automator
Punto de entrada UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) Encapsula la lógica de prueba en el alcance de uiAutomator { ... }.
Cómo encontrar elementos de la IU device.findObject(By.res("com.example.app:id/my_button")) onElement { id == "my\_button" }
Cómo encontrar elementos de la IU device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
Espera a que la IU esté inactiva device.waitForIdle() Prefiere el mecanismo de tiempo de espera integrado de onElement; de lo contrario, activeWindow().waitForStable()
Cómo encontrar elementos secundarios Llamadas findObject anidadas manualmente Encadenamiento de onElement().onElement()
Cómo controlar los diálogos de permisos UiAutomator.registerWatcher() watchFor(PermissionDialog)