使用 UI Automator 编写自动化测试

UI Automator 测试框架提供了一组 API,用于构建与用户应用和系统应用交互的界面测试。

现代 UI Automator 测试简介

UI Automator 2.4 引入了一种简化的 Kotlin 友好型领域专用 语言 (DSL),可简化 Android 界面测试的编写。这个新的 API 表面侧重于基于谓词的元素查找和对应用 状态的显式控制。使用它可以创建更易于维护且更可靠的自动化测试。

借助 UI Automator,您可以从应用进程外部测试应用。这样,您就可以测试应用了缩减功能的发布版本。 在编写宏基准测试时,UI Automator 也 很有用。

现代方法的主要功能包括:

  • 专用的 uiAutomator 测试范围,可让测试代码更简洁、更具表现力。
  • 用于查找具有明确谓词的界面元素的方法,例如 onElementonElementsonElementOrNull
  • 用于条件元素的内置等待机制 onElement*(timeoutMs: Long = 10000)
  • 显式应用状态管理,例如 waitForStablewaitForAppToBeVisible
  • 与无障碍功能窗口节点直接交互,适用于多窗口测试 场景。
  • 内置屏幕截图功能和 ResultsReporter,适用于视觉测试 和调试。

设置项目

如需开始使用现代 UI Automator API,请更新项目的 build.gradle.kts 文件以包含最新依赖项

Kotlin

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

Groovy

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

核心 API 概念

以下部分介绍了现代 UI Automator API 的核心概念。

uiAutomator 测试范围

uiAutomator { ... } 块中访问所有新的 UI Automator API。此函数会创建一个 UiAutomatorTestScope,为您的测试操作提供简洁 且类型安全的环境。

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

查找界面元素

使用带有谓词的 UI Automator API 来查找界面元素。借助这些谓词 ,您可以为属性(例如文本、选中或焦点 状态以及内容说明)定义条件。

  • onElement { predicate }:返回在默认超时时间内与 谓词匹配的第一个界面元素。如果该函数找不到匹配的元素,则会抛出异常。

    // 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 }:与 onElement 类似,但如果该函数在超时时间内找不到匹配的元素,则会返回 null。它 不会抛出异常。将此方法用于可选元素。

    val optionalButton = onElementOrNull { textAsString() == "Skip" }
    optionalButton?.click() // Click only if the button exists
    
  • onElements { predicate }:等待至少一个界面元素与给定的谓词匹配 ,然后返回所有匹配的界面元素的列表。

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

以下是使用 onElement 调用的几点提示:

  • 链接 onElement 调用以查找嵌套元素:您可以链接 onElement 调用,以按照父子 层次结构在其他元素中查找元素。

    // 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()
    
  • 通过传递表示 毫秒的值,为 onElement* 函数指定超时时间。

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

与界面元素交互

通过模拟点击或在可修改的 字段中设置文本,与界面元素交互。

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

处理应用状态和监听器

管理应用的生命周期,并处理测试期间可能 出现的意外界面元素。

应用生命周期管理

这些 API 提供了控制受测应用状态的方法:

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

处理意外界面

借助 watchFor API,您可以为测试流程中可能出现的意外界面元素 (例如权限对话框)定义处理程序。这 会使用内部监听器机制,但提供了更高的灵活性。

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

PermissionDialogScopedWatcher<T> 的一个示例,其中 T 是作为范围传递给 watchFor 中块的 对象。您可以根据此模式创建自定义 监听器。

等待应用可见性和稳定性

有时,测试需要等待元素变为可见或稳定。 UI Automator 提供了多个 API 来帮助实现此目的。

waitForAppToBeVisible("com.example.targetapp") 会等待具有给定软件包名称的界面元素 在可自定义的超时时间内出现在屏幕上。

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

使用 waitForStable() API 验证应用的界面在与用户交互之前是否被视为稳定 。

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

将 UI Automator 用于宏基准测试和基准配置文件

将 UI Automator 用于使用 Jetpack Macrobenchmark 进行的性能测试,以及用于生成 基准配置文件,因为它提供了一种可靠的方式来 与您的应用交互,并从最终用户的角度衡量性能。

宏基准测试使用 UI Automator API 来驱动界面并衡量交互。 例如,在启动基准测试中,您可以使用 onElement 来检测界面 内容何时完全加载,从而衡量 完全显示所用时间 (TTFD)。在卡顿基准测试中,UI Automator API 用于滚动列表或 运行动画以衡量帧时序。startActivity()startIntent() 等函数对于让应用进入正确状态非常有用,然后 开始衡量。

生成基准配置文件时,您可以自动执行应用的关键用户 历程 (CUJ),以记录哪些类和方法需要预编译。UI Automator 是编写这些自动化脚本的理想工具。与其他方法相比,现代 DSL 基于谓词的元素查找和内置等待机制 (onElement) 可实现更可靠、更具确定性的测试执行。 这种稳定性可减少不稳定性,并确保生成的基准配置文件 准确反映在最重要的用户 流程期间执行的代码路径。

高级功能

以下功能对于更复杂的测试场景非常有用。

与多个窗口交互

借助 UI Automator API,您可以直接与界面 元素交互并检查界面元素。这对于涉及多个窗口的场景 (例如画中画 (PiP) 模式或分屏布局)尤其有用。

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

屏幕截图和视觉断言

直接在测试中捕获整个屏幕、特定窗口或 单个界面元素的屏幕截图。这有助于进行视觉 回归测试和调试。

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

Bitmap 的 saveToFile 扩展函数可简化将捕获的 图片保存到指定路径的过程。

使用 ResultsReporter 进行调试

借助 ResultsReporter,您可以将测试工件(例如屏幕截图) 直接与 Android Studio 中的测试结果相关联,以便更轻松地进行检查和 调试。

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

从旧版 UI Automator 迁移

如果您有使用旧版 API 表面编写的现有 UI Automator 测试,请使用 下表作为参考来迁移到现代方法:

操作类型 旧版 UI Automator 方法 新版 UI Automator 方法
入口点 UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) 将测试逻辑封装在 uiAutomator { ... } 范围内。
查找界面元素 device.findObject(By.res("com.example.app:id/my_button")) onElement { viewIdResourceName == "my\_button" }
查找界面元素 device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
等待空闲界面 device.waitForIdle() 首选 onElement 的内置超时机制;否则,请使用 activeWindow().waitForStable()
查找子元素 手动嵌套的 findObject 调用 onElement().onElement() 链接
处理权限对话框 UiAutomator.registerWatcher() watchFor(PermissionDialog)