Rédiger des tests automatisés avec UI Automator

Le framework de test UI Automator fournit un ensemble d'API permettant de créer des tests d'interface utilisateur qui interagissent avec les applications utilisateur et système.

Présentation des tests UI Automator modernes

UI Automator 2.4 introduit un langage spécifique au domaine (DSL) simplifié et compatible avec Kotlin, qui facilite l'écriture de tests d'UI pour Android. Cette nouvelle surface d'API se concentre sur la recherche d'éléments basée sur des prédicats et sur le contrôle explicite des états de l'application. Utilisez-le pour créer des tests automatisés plus fiables et plus faciles à gérer.

UI Automator vous permet de tester une application en dehors de son processus. Cela vous permet de tester les versions à publier avec la minification appliquée. UI Automator est également utile pour écrire des tests Macrobenchmark.

Voici les principales caractéristiques de l'approche moderne :

  • Une portée de test uiAutomator dédiée pour un code de test plus clair et plus expressif.
  • Méthodes telles que onElement, onElements et onElementOrNull pour trouver des éléments d'interface utilisateur avec des prédicats clairs.
  • Mécanisme d'attente intégré pour les éléments conditionnels onElement*(timeoutMs: Long = 10000)
  • Gestion explicite de l'état de l'application, comme waitForStable et waitForAppToBeVisible.
  • Interaction directe avec les nœuds de la fenêtre d'accessibilité pour les scénarios de test multifenêtre.
  • Fonctionnalités de capture d'écran intégrées et ResultsReporter pour les tests visuels et le débogage.

Configurer votre projet

Pour commencer à utiliser les API UI Automator modernes, mettez à jour le fichier build.gradle.kts de votre projet pour inclure la dernière dépendance :

Kotlin

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

Groovy

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

Concepts fondamentaux de l'API

Les sections suivantes décrivent les concepts clés de l'API UI Automator moderne.

Portée du test uiAutomator

Accédez à toutes les nouvelles API UI Automator dans le bloc uiAutomator { ... }. Cette fonction crée un UiAutomatorTestScope qui fournit un environnement concis et sécurisé pour vos opérations de test.

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

Rechercher des éléments d'UI

Utilisez les API UI Automator avec des prédicats pour localiser les éléments d'interface utilisateur. Ces prédicats vous permettent de définir des conditions pour des propriétés telles que le texte, l'état sélectionné ou sélectionné, et la description du contenu.

  • onElement { predicate } : renvoie le premier élément d'interface utilisateur qui correspond au prédicat dans un délai d'attente par défaut. La fonction génère une exception si elle ne trouve pas d'élément correspondant.

    // 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 } : semblable à onElement, mais renvoie null si la fonction ne trouve aucun élément correspondant dans le délai d'attente. Cela ne génère pas d'exception. Utilisez cette méthode pour les éléments facultatifs.

    val optionalButton = onElementOrNull { textAsString() == "Skip" }
    optionalButton?.click() // Click only if the button exists
    
  • onElements { predicate } : attend qu'au moins un élément d'UI corresponde au prédicat donné, puis renvoie une liste de tous les éléments d'UI correspondants.

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

Voici quelques conseils pour utiliser les appels onElement :

  • Enchaîner les appels onElement pour les éléments imbriqués : vous pouvez enchaîner les appels onElement pour trouver des éléments dans d'autres éléments, en suivant une hiérarchie parent-enfant.

    // 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()
    
  • Spécifiez un délai avant expiration pour les fonctions onElement* en transmettant une valeur représentant des millisecondes.

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

Interagir avec les éléments de l'UI

Interagissez avec les éléments de l'UI en simulant des clics ou en définissant du texte dans des champs modifiables.

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

Gérer les états et les observateurs d'application

Gérez le cycle de vie de votre application et gérez les éléments d'interface utilisateur inattendus qui peuvent apparaître lors de vos tests.

Gestion du cycle de vie des applications

Les API permettent de contrôler l'état de l'application testée :

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

Gérer les UI inattendues

L'API watchFor vous permet de définir des gestionnaires pour les éléments d'interface utilisateur inattendus, tels que les boîtes de dialogue d'autorisation, qui peuvent apparaître pendant votre flux de test. Cette méthode utilise le mécanisme d'observation interne, mais offre plus de flexibilité.

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 est un exemple de ScopedWatcher<T>, où T est l'objet transmis en tant que portée au bloc dans watchFor. Vous pouvez créer des observateurs personnalisés basés sur ce modèle.

Attendre la visibilité et la stabilité de l'application

Parfois, les tests doivent attendre que les éléments deviennent visibles ou stables. UI Automator propose plusieurs API pour vous aider.

waitForAppToBeVisible("com.example.targetapp") attend qu'un élément d'UI portant le nom de package donné s'affiche à l'écran dans un délai d'attente personnalisable.

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

Utilisez l'API waitForStable() pour vérifier que l'UI de l'application est considérée comme stable avant d'interagir avec elle.

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

Fonctionnalités avancées

Les fonctionnalités suivantes sont utiles pour les scénarios de test plus complexes.

Interagir avec plusieurs fenêtres

Les API UI Automator vous permettent d'interagir directement avec les éléments de l'UI et de les inspecter. Cela est particulièrement utile pour les scénarios impliquant plusieurs fenêtres, comme le mode Picture-in-picture (PIP) ou les mises en page en écran partagé.

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

Captures d'écran et assertions visuelles

Prenez des captures d'écran de l'intégralité de l'écran, de fenêtres spécifiques ou d'éléments d'interface utilisateur individuels directement dans vos tests. Cela est utile pour les tests de régression visuelle et le débogage.

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 fonction d'extension saveToFile pour Bitmap simplifie l'enregistrement de l'image capturée dans un chemin d'accès spécifié.

Utiliser ResultsReporter pour le débogage

ResultsReporter vous aide à associer des artefacts de test, comme des captures d'écran, directement à vos résultats de test dans Android Studio pour faciliter l'inspection et le débogage.

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

Migrer depuis d'anciennes versions d'UI Automator

Si vous avez des tests UI Automator existants écrits avec des surfaces d'API plus anciennes, utilisez le tableau suivant comme référence pour migrer vers l'approche moderne :

Type d'action Ancienne méthode UI Automator Nouvelle méthode UI Automator
Point d'entrée UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) Encapsulez la logique de test dans le champ d'application uiAutomator { ... }.
Rechercher des éléments d'UI device.findObject(By.res("com.example.app:id/my_button")) onElement { id == "my\_button" }
Rechercher des éléments d'UI device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
Attendre que l'UI soit inactive device.waitForIdle() Préférer le mécanisme de délai avant expiration intégré de onElement ; sinon, activeWindow().waitForStable()
Rechercher des éléments enfants Appels findObject imbriqués manuellement onElement().onElement() chaînage
Gérer les boîtes de dialogue d'autorisation UiAutomator.registerWatcher() watchFor(PermissionDialog)