Scrivere test automatici con Automator di UI

Il framework di test UI Automator fornisce un insieme di API per creare test dell'interfaccia utente che interagiscono con le app utente e di sistema.

Introduzione ai test UI Automator moderni

UI Automator 2.4 introduce un linguaggio specifico del dominio (DSL) semplificato e compatibile con Kotlin che semplifica la scrittura dei test dell'interfaccia utente per Android. Questa nuova superficie API si concentra sulla ricerca di elementi basata su predicati e sul controllo esplicito degli stati delle app. Utilizzala per creare test automatici più gestibili e affidabili.

UI Automator ti consente di testare un'app dall'esterno del processo dell'app. In questo modo puoi testare le versioni di release con la minificazione applicata. UI Automator è utile anche per scrivere test di macrobenchmark.

Le funzionalità principali dell'approccio moderno includono:

  • Un ambito di test uiAutomator dedicato per un codice di test più pulito ed espressivo.
  • Metodi come onElement, onElements e onElementOrNull per trovare elementi dell'interfaccia utente con predicati chiari.
  • Meccanismo di attesa integrato per gli elementi condizionali onElement*(timeoutMs: Long = 10000)
  • Gestione esplicita dello stato dell'app, ad esempio waitForStable e waitForAppToBeVisible.
  • Interazione diretta con i nodi della finestra di accessibilità per scenari di test multi-finestra
  • Funzionalità di screenshot integrate e un ResultsReporter per test visivi e debug.

Configura il progetto

Per iniziare a utilizzare le API UI Automator moderne, aggiorna il file build.gradle.kts del progetto in modo da includere la dipendenza più recente:

Kotlin

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

Groovy

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

Concetti di base dell'API

Le sezioni seguenti descrivono i concetti di base dell'API UI Automator moderna.

L'ambito di test uiAutomator

Accedi a tutte le nuove API UI Automator all'interno del uiAutomator { ... } blocco. Questa funzione crea un UiAutomatorTestScope che fornisce un ambiente conciso e con sicurezza dei tipi per le operazioni di test.

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

Trova elementi dell'interfaccia utente

Utilizza le API UI Automator con i predicati per individuare gli elementi dell'interfaccia utente. Questi predicati ti consentono di definire le condizioni per le proprietà come testo, stato selezionato o con stato attivo e descrizione dei contenuti.

  • onElement { predicate }: restituisce il primo elemento dell'interfaccia utente che corrisponde al predicato entro un timeout predefinito. La funzione genera un'eccezione se non individua un elemento corrispondente.

    // 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 }: simile a onElement, ma restituisce null se la funzione non trova un elemento corrispondente entro il timeout. Non genera un'eccezione. Utilizza questo metodo per gli elementi facoltativi.

    val optionalButton = onElementOrNull { textAsString() == "Skip" }
    optionalButton?.click() // Click only if the button exists
    
  • onElements { predicate }: attende che almeno un elemento dell'interfaccia utente corrisponda a l predicato specificato, quindi restituisce un elenco di tutti gli elementi dell'interfaccia utente corrispondenti.

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

Ecco alcuni suggerimenti per l'utilizzo delle chiamate onElement:

  • Concatenamento delle chiamate onElement per gli elementi nidificati: puoi concatenare le chiamate onElement per trovare elementi all'interno di altri elementi, seguendo una gerarchia padre-figlio.

    // 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()
    
  • Specifica un timeout per le funzioni onElement* passando un valore che rappresenta i millisecondi.

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

Interagisci con gli elementi dell'interfaccia utente

Interagisci con gli elementi dell'interfaccia utente simulando i clic o impostando il testo nei campi modificabili.

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

Gestisci gli stati e i watcher delle app

Gestisci il ciclo di vita dell'app e gestisci gli elementi dell'interfaccia utente imprevisti che potrebbero apparire durante i test.

Gestione del ciclo di vita delle app

Le API forniscono modi per controllare lo stato dell'app in fase di test:

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

Gestisci l'interfaccia utente imprevista

L'API watchFor ti consente di definire i gestori per gli elementi dell'interfaccia utente imprevisti, ad esempio le finestre di dialogo di autorizzazione, che potrebbero apparire durante il flusso di test. Utilizza il meccanismo di watcher interno, ma offre maggiore flessibilità.

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 è un esempio di ScopedWatcher<T>, dove T è l'oggetto passato come ambito al blocco in watchFor. Puoi creare watcher personalizzati in base a questo pattern.

Attendi la visibilità e la stabilità dell'app

A volte i test devono attendere che gli elementi diventino visibili o stabili. UI Automator offre diverse API per aiutarti in questo.

Il waitForAppToBeVisible("com.example.targetapp") attende che un elemento dell'interfaccia utente con il nome del pacchetto specificato venga visualizzato sullo schermo entro un timeout personalizzabile.

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

Utilizza l'API waitForStable() per verificare che l'interfaccia utente dell'app sia considerata stabile prima di interagire con essa.

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

Utilizza UI Automator per macrobenchmark e profili di base

Utilizza UI Automator per i test delle prestazioni con Jetpack Macrobenchmark e per la generazione di profili di base, in quanto fornisce un modo affidabile per interagire con l'app e misurare le prestazioni dal punto di vista dell'utente finale.

Macrobenchmark utilizza le API UI Automator per gestire l'interfaccia utente e misurare le interazioni. Ad esempio, nei benchmark di avvio, puoi utilizzare onElement per rilevare quando i contenuti dell'interfaccia utente sono completamente caricati, consentendoti di misurare il tempo di visualizzazione completa (TTFD). Nei benchmark di jank, le API UI Automator vengono utilizzate per scorrere gli elenchi o eseguire animazioni per misurare i tempi dei frame. Funzioni come startActivity() o startIntent() sono utili per portare l'app nello stato corretto prima dell' inizio della misurazione.

Quando generi i profili di base, automatizzi i percorsi utente critici della tua app (CUJ) per registrare le classi e i metodi che richiedono la precompilazione. UI Automator è uno strumento ideale per scrivere questi script di automazione. La ricerca di elementi basata su predicati e i meccanismi di attesa integrati (onElement) del DSL moderno portano a un'esecuzione dei test più robusta e deterministica rispetto ad altri metodi. Questa stabilità riduce la variabilità e garantisce che il profilo di base generato rifletta accuratamente i percorsi del codice eseguiti durante i flussi utente più importanti.

Funzionalità avanzate

Le seguenti funzionalità sono utili per scenari di test più complessi.

Interagisci con più finestre

Le API UI Automator ti consentono di interagire direttamente con gli elementi dell'interfaccia utente e di esaminarli. Questo è particolarmente utile per gli scenari che coinvolgono più finestre, come la modalità Picture in picture (PiP) o i layout a schermo diviso.

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

Screenshot e asserzioni visive

Acquisisci screenshot dell'intero schermo, di finestre specifiche o di singoli elementi dell'interfaccia utente direttamente all'interno dei test. Questo è utile per i test di regressione visiva e il debug.

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

La funzione di estensione saveToFile per Bitmap semplifica il salvataggio dell'immagine acquisita in un percorso specificato.

Utilizza ResultsReporter per il debug

ResultsReporter ti aiuta ad associare gli artefatti di test, come gli screenshot, direttamente ai risultati dei test in Android Studio per semplificare l'ispezione e il debug.

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

Esegui la migrazione dalle versioni precedenti di UI Automator

Se hai test UI Automator esistenti scritti con superfici API precedenti, utilizza la seguente tabella come riferimento per eseguire la migrazione all'approccio moderno:

Tipo di azione Metodo UI Automator precedente Nuovo metodo UI Automator
Punto di ingresso UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) Esegui il wrapping della logica di test nell'ambito uiAutomator { ... }.
Trova elementi dell'interfaccia utente device.findObject(By.res("com.example.app:id/my_button")) onElement { viewIdResourceName == "my\_button" }
Trova elementi dell'interfaccia utente device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
Attendi l'interfaccia utente inattiva device.waitForIdle() Preferisci il meccanismo di timeout integrato di onElement; in caso contrario, activeWindow().waitForStable()
Trova elementi secondari Chiamate findObject nidificate manualmente Concatenamento onElement().onElement()
Gestisci le finestre di dialogo di autorizzazione UiAutomator.registerWatcher() watchFor(PermissionDialog)