Viết kiểm thử tự động bằng UI Automator

Khung kiểm thử UI Automator cung cấp một bộ API để tạo các quy trình kiểm thử giao diện người dùng tương tác với ứng dụng người dùng và ứng dụng hệ thống.

Giới thiệu về hoạt động kiểm thử bằng UI Automator hiện đại

UI Automator 2.4 giới thiệu Ngôn ngữ dành riêng cho miền (DSL) thân thiện với Kotlin và được tinh giản, giúp đơn giản hoá việc viết các kiểm thử giao diện người dùng cho Android. Nền tảng API mới này tập trung vào việc tìm phần tử dựa trên vị từ và kiểm soát rõ ràng các trạng thái ứng dụng. Sử dụng tính năng này để tạo các kiểm thử tự động đáng tin cậy và dễ duy trì hơn.

UI Automator cho phép bạn kiểm thử một ứng dụng từ bên ngoài quy trình của ứng dụng. Điều này cho phép bạn kiểm thử các phiên bản phát hành có áp dụng tính năng rút gọn. UI Automator cũng giúp ích khi viết các kiểm thử macrobenchmark.

Các tính năng chính của phương pháp hiện đại bao gồm:

  • Một phạm vi kiểm thử uiAutomator chuyên biệt để có mã kiểm thử rõ ràng và biểu cảm hơn.
  • Các phương thức như onElement, onElementsonElementOrNull để tìm các phần tử trên giao diện người dùng bằng các vị từ rõ ràng.
  • Cơ chế chờ tích hợp cho các phần tử có điều kiện onElement*(timeoutMs: Long = 10000)
  • Quản lý trạng thái ứng dụng một cách tường minh, chẳng hạn như waitForStablewaitForAppToBeVisible.
  • Tương tác trực tiếp với các nút cửa sổ hỗ trợ tiếp cận cho các trường hợp kiểm thử nhiều cửa sổ.
  • Khả năng chụp ảnh màn hình tích hợp và ResultsReporter để kiểm thử và gỡ lỗi trực quan.

Thiết lập dự án

Để bắt đầu sử dụng các API UI Automator hiện đại, hãy cập nhật tệp build.gradle.kts của dự án để thêm phần phụ thuộc mới nhất:

Kotlin

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

Groovy

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

Các khái niệm cốt lõi về API

Các phần sau đây mô tả những khái niệm cốt lõi của API UI Automator hiện đại.

Phạm vi kiểm thử uiAutomator

Truy cập vào tất cả các API UI Automator mới trong khối uiAutomator { ... }. Hàm này tạo một UiAutomatorTestScope cung cấp một môi trường ngắn gọn và an toàn về kiểu cho các hoạt động kiểm thử của bạn.

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

Tìm các phần tử trên giao diện người dùng

Sử dụng các API UI Automator với các vị từ để xác định vị trí của các phần tử trên giao diện người dùng. Các vị từ này cho phép bạn xác định các điều kiện cho các thuộc tính như văn bản, trạng thái đã chọn hoặc trạng thái được lấy tiêu điểm và nội dung mô tả.

  • onElement { predicate }: Trả về phần tử giao diện người dùng đầu tiên khớp với vị từ trong thời gian chờ mặc định. Hàm này sẽ gửi một ngoại lệ nếu không tìm thấy phần tử phù hợp.

    // 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 }: Tương tự như onElement, nhưng trả về null nếu hàm không tìm thấy phần tử nào khớp trong thời gian chờ. Hệ thống sẽ không gửi một trường hợp ngoại lệ. Hãy dùng phương thức này cho các phần tử không bắt buộc.

    val optionalButton = onElementOrNull { textAsString() == "Skip" }
    optionalButton?.click() // Click only if the button exists
    
  • onElements { predicate }: Chờ cho đến khi có ít nhất một thành phần giao diện người dùng khớp với vị từ đã cho, sau đó trả về danh sách tất cả các thành phần giao diện người dùng khớp.

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

Dưới đây là một số mẹo sử dụng cuộc gọi onElement:

  • Chuỗi các lệnh gọi onElement cho các phần tử lồng nhau: Bạn có thể tạo chuỗi các lệnh gọi onElement để tìm các phần tử trong các phần tử khác, theo hệ thống phân cấp cha-con.

    // 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()
    
  • Chỉ định thời gian chờ cho các hàm onElement* bằng cách truyền một giá trị biểu thị mili giây.

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

Tương tác với các phần tử giao diện người dùng

Tương tác với các phần tử trên giao diện người dùng bằng cách mô phỏng các lượt nhấp hoặc đặt văn bản trong các trường có thể chỉnh sửa.

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

Xử lý các trạng thái và trình theo dõi ứng dụng

Quản lý vòng đời của ứng dụng và xử lý các phần tử không mong muốn trên giao diện người dùng có thể xuất hiện trong quá trình kiểm thử.

Quản lý vòng đời của ứng dụng

Các API này cung cấp những cách để kiểm soát trạng thái của ứng dụng đang kiểm thử:

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

Xử lý giao diện người dùng không mong muốn

API watchFor cho phép bạn xác định các trình xử lý cho những phần tử không mong muốn trên giao diện người dùng (chẳng hạn như hộp thoại cấp quyền) có thể xuất hiện trong quy trình kiểm thử. Phương thức này sử dụng cơ chế trình theo dõi nội bộ nhưng mang lại tính linh hoạt cao hơn.

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 là một ví dụ về ScopedWatcher<T>, trong đó T là đối tượng được truyền dưới dạng một phạm vi đến khối trong watchFor. Bạn có thể tạo trình theo dõi tuỳ chỉnh dựa trên mẫu này.

Chờ khả năng hiển thị và độ ổn định của ứng dụng

Đôi khi, các kiểm thử cần đợi cho đến khi các phần tử xuất hiện hoặc ổn định. UI Automator cung cấp một số API để hỗ trợ việc này.

waitForAppToBeVisible("com.example.targetapp") đợi một thành phần trên giao diện người dùng có tên gói đã cho xuất hiện trên màn hình trong một khoảng thời gian chờ có thể tuỳ chỉnh.

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

Sử dụng API waitForStable() để xác minh rằng giao diện người dùng của ứng dụng được coi là ổn định trước khi tương tác với giao diện đó.

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

Các tính năng nâng cao

Các tính năng sau đây rất hữu ích cho những tình huống kiểm thử phức tạp hơn.

Tương tác với nhiều cửa sổ

API UI Automator cho phép bạn tương tác trực tiếp và kiểm tra các phần tử trên giao diện người dùng. Điều này đặc biệt hữu ích trong các trường hợp liên quan đến nhiều cửa sổ, chẳng hạn như chế độ Hình trong hình (PiP) hoặc bố cục chia đôi màn hình.

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

Ảnh chụp màn hình và câu khẳng định trực quan

Chụp ảnh màn hình toàn bộ màn hình, các cửa sổ cụ thể hoặc các phần tử giao diện người dùng riêng lẻ ngay trong các thử nghiệm của bạn. Điều này rất hữu ích cho việc kiểm thử hồi quy trực quan và gỡ lỗi.

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

Hàm tiện ích saveToFile cho Bitmap giúp đơn giản hoá việc lưu hình ảnh đã chụp vào một đường dẫn cụ thể.

Sử dụng ResultsReporter để gỡ lỗi

ResultsReporter giúp bạn liên kết các cấu phần phần mềm kiểm thử (chẳng hạn như ảnh chụp màn hình) trực tiếp với kết quả kiểm thử trong Android Studio để dễ dàng kiểm tra và gỡ lỗi.

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

Di chuyển từ các phiên bản UI Automator cũ

Nếu bạn có các kiểm thử UI Automator hiện có được viết bằng các giao diện API cũ, hãy sử dụng bảng sau làm tài liệu tham khảo để di chuyển sang phương pháp hiện đại:

Loại thao tác Phương thức UI Automator cũ Phương thức UI Automator mới
Điểm truy cập UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) Gói logic kiểm thử trong phạm vi uiAutomator { ... }.
Tìm các phần tử trên giao diện người dùng device.findObject(By.res("com.example.app:id/my_button")) onElement { id == "my\_button" }
Tìm các phần tử trên giao diện người dùng device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
Đợi giao diện người dùng ở trạng thái rảnh device.waitForIdle() Ưu tiên cơ chế thời gian chờ tích hợp của onElement; nếu không, hãy dùng activeWindow().waitForStable()
Tìm phần tử con Cuộc gọi findObject được lồng theo cách thủ công onElement().onElement() xâu chuỗi
Xử lý hộp thoại quyền UiAutomator.registerWatcher() watchFor(PermissionDialog)