O framework de testes do UI Automator oferece um conjunto de APIs para criar testes de interface que interagem com apps de usuário e do sistema.
Introdução aos testes modernos do UI Automator
O UI Automator 2.4 apresenta uma linguagem específica do domínio (DSL, na sigla em inglês) simplificada e compatível com Kotlin que simplifica a criação de testes de interface para Android. Essa nova superfície de API se concentra na localização de elementos baseada em predicados e no controle explícito dos estados do app. Use-o para criar testes automatizados mais confiáveis e fáceis de manter.
O UI Automator permite testar um app de fora do processo dele. Isso permite testar versões de lançamento com minificação aplicada. O UI Automator também ajuda ao escrever testes de macrobenchmark.
Os principais recursos da abordagem moderna incluem:
- Um escopo de teste
uiAutomatordedicado para um código de teste mais limpo e expressivo code. - Métodos como
onElement,onElements, eonElementOrNullpara encontrar elementos de interface com predicados claros. - Mecanismo de espera integrado para elementos condicionais
onElement*(timeoutMs: Long = 10000) - Gerenciamento explícito do estado do app, como
waitForStableewaitForAppToBeVisible. - Interação direta com nós de janela de acessibilidade para cenários de teste de várias janelas.
- Recursos de captura de tela integrados e um
ResultsReporterpara testes visuais e depuração.
Configurar seu projeto
Para começar a usar as APIs modernas do UI Automator, atualize o arquivo do projeto
build.gradle.kts para incluir a dependência mais recente:
Kotlin
dependencies {
...
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05")
}
Groovy
dependencies {
...
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05"
}
Conceitos básicos da API
As seções a seguir descrevem os conceitos básicos da API moderna do UI Automator.
O escopo de teste do uiAutomator
Acesse todas as novas APIs do UI Automator no uiAutomator { ... }
bloco. Essa função cria um UiAutomatorTestScope que fornece um ambiente conciso
e com segurança de tipo para suas operações de teste.
uiAutomator {
// All your UI Automator actions go here
startApp("com.example.targetapp")
onElement { textAsString() == "Hello, World!" }.click()
}
Encontrar elementos de interface
Use as APIs do UI Automator com predicados para localizar elementos de interface. Esses predicados permitem definir condições para propriedades como texto, estado selecionado ou em foco e descrição do conteúdo.
onElement { predicate }: retorna o primeiro elemento de interface que corresponde ao predicado em um tempo limite padrão. A função gera uma exceção se não localizar um elemento correspondente.// 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 }: semelhante aonElement, mas retornanullse a função não encontrar um elemento correspondente dentro do tempo limite. Isso não gera uma exceção. Use esse método para elementos opcionais.val optionalButton = onElementOrNull { textAsString() == "Skip" } optionalButton?.click() // Click only if the button existsonElements { predicate }: aguarda até que pelo menos um elemento de interface corresponda a o predicado fornecido e, em seguida, retorna uma lista de todos os elementos de interface correspondentes.// Get all items in a list Ui element val listItems = onElements { className == "android.widget.TextView" && isClickable } listItems.forEach { it.click() }
Confira algumas dicas para usar chamadas onElement:
Encadeie chamadas
onElementpara elementos aninhados: é possível encadear chamadasonElementpara encontrar elementos em outros elementos, seguindo uma hierarquia pai-filho.// 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()Especifique um tempo limite para funções
onElement*transmitindo um valor que representa milissegundos.// 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()
Interagir com elementos de interface
Interaja com elementos de interface simulando cliques ou definindo texto em campos editáveis.
// 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()
Processar estados e observadores de apps
Gerencie o ciclo de vida do app e processe elementos de interface inesperados que podem aparecer durante os testes.
Gerenciamento do ciclo de vida do app
As APIs oferecem maneiras de controlar o estado do app em teste:
// 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")
Processar interface inesperada
A API watchFor permite definir processadores para elementos de interface inesperados,
como caixas de diálogo de permissão, que podem aparecer durante o fluxo de teste. Isso
usa o mecanismo de observador interno, mas oferece mais flexibilidade.
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 é um exemplo de ScopedWatcher<T>, em que T é o
objeto transmitido como um escopo para o bloco em watchFor. É possível criar observadores personalizados
com base nesse padrão.
Aguardar a visibilidade e a estabilidade do app
Às vezes, os testes precisam aguardar que os elementos se tornem visíveis ou estáveis. O UI Automator oferece várias APIs para ajudar com isso.
O waitForAppToBeVisible("com.example.targetapp") aguarda que um elemento de interface com
o nome do pacote fornecido apareça na tela dentro de um tempo limite personalizável.
// Wait for the app to be visible after launching it
startApp("com.example.targetapp")
waitForAppToBeVisible("com.example.targetapp")
Use a API waitForStable() para verificar se a interface do app é considerada estável
antes de interagir com ela.
// 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()
Usar o UI Automator para macrobenchmarks e perfis de referência
Use o UI Automator para testes de desempenho com Jetpack Macrobenchmark e para gerar perfis de referência, já que ele oferece uma maneira confiável de interagir com o app e medir o desempenho da perspectiva do usuário final.
O Macrobenchmark usa APIs do UI Automator para direcionar a interface e medir interações.
Por exemplo, em benchmarks de inicialização, é possível usar onElement para detectar quando o conteúdo da interface
está totalmente carregado, permitindo medir o tempo para exibição total
(TTFD, na sigla em inglês). Em benchmarks de instabilidade, as APIs do UI Automator são usadas para rolar listas ou
executar animações para medir os tempos de frame. Funções como startActivity() ou
startIntent() são úteis para colocar o app no estado correto antes do
início da medição.
Ao gerar perfis de referência, você automatiza as jornadas ideais do usuário (CUJs) do app para registrar quais classes e métodos exigem pré-compilação. O UI
Automator é uma ferramenta ideal para escrever esses scripts de automação. A localização de elementos baseada em predicados e os mecanismos de espera integrados (onElement)
da DSL moderna levam a uma execução de teste mais robusta e determinística em comparação com outros métodos.
Essa estabilidade reduz a instabilidade e garante que o perfil de referência gerado
reflita com precisão os caminhos de código executados durante os fluxos de usuários mais importantes.
Recursos avançados
Os recursos a seguir são úteis para cenários de teste mais complexos.
Interagir com várias janelas
As APIs do UI Automator permitem interagir diretamente com elementos de interface e inspecioná-los. Isso é particularmente útil para cenários que envolvem várias janelas, como o modo picture-in-picture (PiP) ou layouts de tela dividida.
// 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()
Capturas de tela e declarações visuais
Capture capturas de tela da tela inteira, janelas específicas ou elementos de interface individuais diretamente nos testes. Isso é útil para testes de regressão visual e depuração.
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"))
}
A função de extensão saveToFile para bitmap simplifica o salvamento da imagem capturada
em um caminho especificado.
Usar o ResultsReporter para depuração
O ResultsReporter ajuda a associar artefatos de teste, como capturas de tela,
diretamente aos resultados do teste no Android Studio para facilitar a inspeção e a
depuração.
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()
}
Migrar de versões mais antigas do UI Automator
Se você tiver testes do UI Automator escritos com versões mais antigas da API, use a tabela a seguir como referência para migrar para a abordagem moderna:
| Tipo de ação | Método antigo do UI Automator | Novo método do UI Automator |
|---|---|---|
| Ponto de entrada | UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) |
Encapsule a lógica de teste no escopo uiAutomator { ... }. |
| Encontrar elementos de interface | device.findObject(By.res("com.example.app:id/my_button")) |
onElement { viewIdResourceName == "my\_button" } |
| Encontrar elementos de interface | device.findObject(By.text("Click Me")) |
onElement { textAsString() == "Click Me" } |
| Aguardar a interface inativa | device.waitForIdle() |
Prefira o mecanismo de tempo limite integrado do onElement; caso contrário, activeWindow().waitForStable() |
| Encontrar elementos filhos | Chamadas findObject aninhadas manualmente |
onElement().onElement() encadeamento |
| Processar caixas de diálogo de permissão | UiAutomator.registerWatcher() |
watchFor(PermissionDialog) |