Menulis pengujian otomatis dengan UI Automator

Framework pengujian UI Automator menyediakan serangkaian API untuk membuat pengujian UI yang berinteraksi dengan aplikasi pengguna dan aplikasi sistem.

Pengantar pengujian UI Automator modern

UI Automator 2.4 memperkenalkan Domain Specific Language (DSL) yang disederhanakan dan kompatibel dengan Kotlin yang menyederhanakan penulisan pengujian UI untuk Android. Platform API baru ini berfokus pada penemuan elemen berbasis predikat dan kontrol eksplisit atas status aplikasi. Gunakan untuk membuat pengujian otomatis yang lebih mudah dikelola dan andal.

UI Automator memungkinkan Anda menguji aplikasi dari luar proses aplikasi. Dengan ini, Anda dapat menguji versi rilis dengan penerapan minifikasi. UI Automator juga membantu saat menulis pengujian macrobenchmark.

Fitur utama pendekatan modern meliputi:

  • Cakupan pengujian uiAutomator khusus untuk kode pengujian yang lebih bersih dan ekspresif.
  • Metode seperti onElement, onElements, dan onElementOrNull untuk menemukan elemen UI dengan predikat yang jelas.
  • Mekanisme menunggu bawaan untuk elemen bersyarat onElement*(timeoutMs: Long = 10000)
  • Pengelolaan status aplikasi eksplisit seperti waitForStable dan waitForAppToBeVisible.
  • Interaksi langsung dengan node jendela aksesibilitas untuk skenario pengujian multi-aplikasi.
  • Kemampuan screenshot bawaan dan ResultsReporter untuk pengujian visual dan proses debug.

Menyiapkan project

Untuk mulai menggunakan API UI Automator modern, perbarui file build.gradle.kts project Anda untuk menyertakan dependensi terbaru:

Kotlin

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

Groovy

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

Konsep API inti

Bagian berikut menjelaskan konsep inti API UI Automator modern.

Cakupan pengujian uiAutomator

Akses semua API UI Automator baru dalam blok uiAutomator { ... }. Fungsi ini membuat UiAutomatorTestScope yang menyediakan lingkungan ringkas dan aman untuk operasi pengujian Anda.

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

Menemukan elemen UI

Gunakan UI Automator API dengan predikat untuk menemukan elemen UI. Predikat ini memungkinkan Anda menentukan kondisi untuk properti seperti teks, status dipilih atau difokuskan, dan deskripsi konten.

  • onElement { predicate }: Menampilkan elemen UI pertama yang cocok dengan predikat dalam waktu tunggu default. Fungsi akan menampilkan pengecualian jika tidak menemukan elemen yang cocok.

    // 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 }: Mirip dengan onElement, tetapi menampilkan null jika fungsi tidak menemukan elemen yang cocok dalam waktu tunggu. Perintah ini tidak memberikan pengecualian. Gunakan metode ini untuk elemen opsional.

    val optionalButton = onElementOrNull { textAsString() == "Skip" }
    optionalButton?.click() // Click only if the button exists
    
  • onElements { predicate }: Menunggu hingga setidaknya satu elemen UI cocok dengan predikat yang diberikan, lalu menampilkan daftar semua elemen UI yang cocok.

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

Berikut beberapa tips untuk menggunakan panggilan onElement:

  • Menggabungkan panggilan onElement untuk elemen bertingkat: Anda dapat menggabungkan panggilan onElement untuk menemukan elemen dalam elemen lain, mengikuti hierarki induk-turunan.

    // 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()
    
  • Tentukan waktu tunggu untuk fungsi onElement* dengan meneruskan nilai yang merepresentasikan milidetik.

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

Berinteraksi dengan elemen UI

Berinteraksi dengan elemen UI dengan menyimulasikan klik atau menyetel teks di kolom yang dapat diedit.

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

Menangani status dan pemantau aplikasi

Kelola siklus proses aplikasi Anda dan tangani elemen UI tak terduga yang mungkin muncul selama pengujian.

Pengelolaan siklus proses aplikasi

API menyediakan cara untuk mengontrol status aplikasi yang sedang diuji:

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

Menangani UI yang tidak terduga

watchFor API memungkinkan Anda menentukan handler untuk elemen UI yang tidak terduga, seperti dialog izin, yang mungkin muncul selama alur pengujian. Hal ini menggunakan mekanisme watcher internal, tetapi menawarkan fleksibilitas yang lebih besar.

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 adalah contoh ScopedWatcher<T>, dengan T adalah objek yang diteruskan sebagai cakupan ke blok di watchFor. Anda dapat membuat pemantau kustom berdasarkan pola ini.

Menunggu visibilitas dan stabilitas aplikasi

Terkadang, pengujian perlu menunggu hingga elemen terlihat atau stabil. UI Automator menawarkan beberapa API untuk membantu hal ini.

waitForAppToBeVisible("com.example.targetapp") menunggu elemen UI dengan nama paket tertentu muncul di layar dalam waktu tunggu yang dapat disesuaikan.

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

Gunakan waitForStable() API untuk memverifikasi bahwa UI aplikasi dianggap stabil sebelum berinteraksi dengannya.

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

Fitur lanjutan

Fitur berikut berguna untuk skenario pengujian yang lebih kompleks.

Berinteraksi dengan beberapa jendela

UI Automator API memungkinkan Anda berinteraksi dan memeriksa elemen UI secara langsung. Hal ini sangat berguna untuk skenario yang melibatkan beberapa jendela, seperti mode Picture-in-Picture (PiP) atau tata letak layar terpisah.

// 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 dan pernyataan visual

Ambil screenshot seluruh layar, jendela tertentu, atau elemen UI individual langsung dalam pengujian Anda. Hal ini berguna untuk pengujian dan proses debug regresi visual.

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

Fungsi ekstensi saveToFile untuk Bitmap menyederhanakan penyimpanan gambar yang diambil ke jalur yang ditentukan.

Menggunakan ResultsReporter untuk proses debug

ResultsReporter membantu Anda mengaitkan artefak pengujian, seperti screenshot, langsung dengan hasil pengujian di Android Studio untuk memudahkan pemeriksaan dan proses 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()
}

Bermigrasi dari versi UI Automator yang lebih lama

Jika Anda memiliki pengujian UI Automator yang ditulis dengan permukaan API lama, gunakan tabel berikut sebagai referensi untuk bermigrasi ke pendekatan modern:

Jenis tindakan Metode UI Automator lama Metode UI Automator baru
Titik awal UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) Gabungkan logika pengujian dalam cakupan uiAutomator { ... }.
Menemukan elemen UI device.findObject(By.res("com.example.app:id/my_button")) onElement { id == "my\_button" }
Menemukan elemen UI device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
Menunggu UI tidak ada aktivitas device.waitForIdle() Lebih memilih mekanisme waktu tunggu bawaan onElement; jika tidak, activeWindow().waitForStable()
Menemukan elemen turunan Panggilan findObject yang disarangkan secara manual onElement().onElement() perantaian
Menangani dialog izin UiAutomator.registerWatcher() watchFor(PermissionDialog)