Criar testes automatizados com o UI Automator

UI Automator é um framework de testes de interface adequado para interface funcional entre apps testes em apps instalados e do sistema. As APIs UI Automator permitem interagir com elementos visíveis em um dispositivo, independentemente de qual Activity está para você realizar operações como abrir o menu "Configurações" ou o Acesso rápido aos apps em um dispositivo de teste. O teste pode procurar um componente de interface usando descritores convenientes, como o texto exibido nesse componente ou na descrição do conteúdo.

O framework de testes UI Automator é uma API baseada em instrumentação e funciona com o executor de testes AndroidJUnitRunner. Ele é adequado para escrever testes automatizados no estilo de caixa opacos, em que o código de teste não depende de verificações detalhes de implementação do app de destino.

Os principais recursos do framework de testes do UI Automator incluem:

  • Uma API para recuperar informações de estado e realizar operações no destino dispositivo. Para mais informações, consulte Como acessar o estado do dispositivo.
  • APIs compatíveis com testes de IU entre apps. Para mais informações, consulte UI Automator APIs.
.

Como acessar o estado do dispositivo

O framework de testes UI Automator fornece uma classe UiDevice para acessar e realizar operações no dispositivo em que o app de destino está sendo executado. Você pode chamar os métodos dele para acessar propriedades do dispositivo, como orientação atual ou tamanho de exibição. A classe UiDevice também permite realizar o seguinte ações:

  1. Mudar a rotação do dispositivo.
  2. Pressionar as teclas de hardware, como "Aumentar volume".
  3. Pressionar os botões "Voltar", "Início" ou "Menu".
  4. Abrir a aba de notificações.
  5. Fazer uma captura de tela da janela atual.

Por exemplo, para simular o pressionamento do botão home, chame o método UiDevice.pressHome() .

APIs do UI Automator

As APIs UI Automator permitem criar testes robustos sem precisar saber sobre os detalhes de implementação do app que você está segmentando. Você pode usar estas APIs para capturar e manipular componentes de IU em vários apps:

  • UiObject2: representa um elemento da interface visível no dispositivo.
  • BySelector: especifica critérios para corresponder a elementos da interface.
  • By: cria BySelector de maneira concisa.
  • Configurator: permite definir parâmetros importantes para executar testes do UI Automator.
.

Por exemplo, o código a seguir mostra como escrever um script de teste que abre um aplicativo do Gmail no dispositivo:

Kotlin

device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.pressHome()

val gmail: UiObject2 = device.findObject(By.text("Gmail"))
// Perform a click and wait until the app is opened.
val opened: Boolean = gmail.clickAndWait(Until.newWindow(), 3000)
assertThat(opened).isTrue()

Java

device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.pressHome();

UiObject2 gmail = device.findObject(By.text("Gmail"));
// Perform a click and wait until the app is opened.
Boolean opened = gmail.clickAndWait(Until.newWindow(), 3000);
assertTrue(opened);

Configurar o UI Automator

Antes de criar seu teste de interface com o UI Automator, configure local do código-fonte e dependências do projeto, conforme descrito em Configurar projeto para o AndroidX Test.

No arquivo build.gradle do módulo do app Android, defina uma dependência. referência à biblioteca UI Automator:

Kotlin

dependencies {
  ...
  androidTestImplementation('androidx.test.uiautomator:uiautomator:2.3.0-alpha03')
}

Groovy

dependencies {
  ...
  androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.3.0-alpha03'
}

Para otimizar os testes do UI Automator, inspecione primeiro as propriedades os componentes de IU e garantir que eles estejam acessíveis. Essas dicas de otimização são descritos nas próximas duas seções.

Inspecionar a IU em um dispositivo

Antes de projetar seu teste, inspecione os componentes de interface que estão visíveis na dispositivo. Para garantir que os testes do UI Automator acessem esses componentes, verifique se esses componentes têm rótulos de texto visíveis, android:contentDescription ou ambos.

A ferramenta uiautomatorviewer oferece uma interface visual conveniente para inspecionar a hierarquia de layouts e exibir as propriedades dos componentes de IU que são visíveis em primeiro plano no dispositivo. Essas informações permitem criar mais testes refinados usando o UI Automator. Por exemplo, você pode criar um seletor de interface que corresponda a uma propriedade visível específica.

Para iniciar a ferramenta uiautomatorviewer:

  1. Abra o app de destino em um dispositivo físico.
  2. Conecte o dispositivo à máquina de desenvolvimento.
  3. Abra uma janela de terminal e navegue até o diretório <android-sdk>/tools/.
  4. Execute a ferramenta com este comando:
 $ uiautomatorviewer

Para ver as propriedades de IU do app:

  1. Na interface uiautomatorviewer, clique no botão Device screenshot.
  2. Passe o cursor sobre o snapshot no painel esquerdo para ver os componentes da interface. identificados pela ferramenta uiautomatorviewer. As propriedades estão listadas no menores painel direito e a hierarquia de layout no painel superior direito.
  3. Também é possível clicar no botão Toggle NAF Nodes para ver os componentes da interface. que não são acessíveis ao UI Automator. Apenas informações limitadas podem ser disponíveis para esses componentes.

Para saber mais sobre os tipos comuns de componentes de IU fornecidos pelo Android, consulte Criação de do App Engine.

Garantir que a atividade esteja acessível

O framework de testes UI Automator tem um desempenho melhor em apps que foram implementados Recursos de acessibilidade do Android. Quando você usa elementos de interface do tipo View ou uma subclasse de View do SDK, não é necessário implementar acessibilidade suporte, porque essas classes já fizeram isso para você.

No entanto, alguns apps usam elementos personalizados de IU para oferecer uma experiência do usuário mais avançada. Esses elementos não oferecem compatibilidade automática com acessibilidade. Se o seu app contiver instâncias de uma subclasse de View que não seja do SDK, adicione recursos de acessibilidade a esses elementos completando etapas a seguir:

  1. Crie uma classe concreta que estenda ExploreByTouchHelper.
  2. Associe uma instância da sua nova classe a um elemento de interface personalizado específico ao: chame setAccessibilityDelegate().

Para mais orientações sobre como adicionar recursos de acessibilidade à visualização personalizada elementos, consulte Como criar visualizações personalizadas acessíveis. Para saber mais sobre práticas recomendadas gerais para acessibilidade no Android, consulte Como tornar os apps mais Acessível.

Criar uma classe de teste do UI Automator

Crie sua classe de teste do UI Automator da mesma forma que um teste do JUnit 4. . Para saber mais sobre como criar classes de teste do JUnit 4 e como usar o JUnit 4 declarações e anotações, consulte Criar uma classe de teste de unidade de instrumentação.

Adicione a anotação @RunWith(AndroidJUnit4.class) no início do teste. definição da classe. Também é necessário especificar a classe AndroidJUnitRunner, fornecidos no AndroidX Test como executor de testes padrão. Esta etapa é descrita Para mais detalhes, consulte Executar testes do UI Automator em um dispositivo ou emulador.

Implemente o seguinte modelo de programação na classe de teste do UI Automator:

  1. Receba um objeto UiDevice para acessar o dispositivo que você quer testar chamando método getInstance() e passando um objeto de Instrumentation como argumento.
  2. Receba um objeto UiObject2 para acessar um componente de IU que é exibido na do dispositivo (por exemplo, a visualização atual em primeiro plano), chamando o método findObject().
  3. Simular uma interação específica do usuário a ser realizada nesse componente de IU, chamando um método UiObject2; por exemplo, chamar scrollUntil() para rolar e setText() para editar um campo de texto. É possível chamar as APIs nas etapas 2 e 3 repetidamente conforme necessário para testar interações de usuário mais complexas que envolvem vários componentes de IU ou sequências de ações do usuário.
  4. Verificar se a interface reflete o estado ou comportamento esperado após essas interações são realizadas.

Essas etapas são abordadas com mais detalhes nas seções abaixo.

Acessar componentes de IU

O objeto UiDevice é a principal forma de acessar e manipular do dispositivo. Nos testes, você pode chamar métodos UiDevice para verificar o estado de várias propriedades, como a orientação atual ou o tamanho da tela. O teste pode usar o objeto UiDevice para realizar ações no nível do dispositivo. por exemplo, forçar o dispositivo a uma rotação específica, pressionar o hardware do botão direcional e pressionando os botões Home e Menu.

Uma prática recomendada é iniciar o teste na tela inicial do dispositivo. De a tela inicial (ou algum outro local inicial que você escolheu no dispositivo), é possível chamar os métodos fornecidos pela API UI Automator para selecionar e interagir com elementos de IU específicos.

O snippet de código a seguir mostra como o teste pode receber uma instância do UiDevice e simular o pressionamento do botão home:

Kotlin

import org.junit.Before
import androidx.test.runner.AndroidJUnit4
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
...

private const val BASIC_SAMPLE_PACKAGE = "com.example.android.testing.uiautomator.BasicSample"
private const val LAUNCH_TIMEOUT = 5000L
private const val STRING_TO_BE_TYPED = "UiAutomator"

@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = 18)
class ChangeTextBehaviorTest2 {

private lateinit var device: UiDevice

@Before
fun startMainActivityFromHomeScreen() {
  // Initialize UiDevice instance
  device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

  // Start from the home screen
  device.pressHome()

  // Wait for launcher
  val launcherPackage: String = device.launcherPackageName
  assertThat(launcherPackage, notNullValue())
  device.wait(
    Until.hasObject(By.pkg(launcherPackage).depth(0)),
    LAUNCH_TIMEOUT
  )

  // Launch the app
  val context = ApplicationProvider.getApplicationContextC<ontext(>)
  val intent = context.packageManager.getLaunchIntentForPackage(
  BASIC_SAMPLE_PACKAGE).apply {
    // Clear out any previous instances
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
  }
  context.startActivity(intent)

  // Wait for the app to appear
  device.wait(
    Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
    LAUNCH_TIMEOUT
    )
  }
}

Java

import org.junit.Before;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
...

@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class ChangeTextBehaviorTest {

  private static final String BASIC_SAMPLE_PACKAGE
  = "com.example.android.testing.uiautomator.BasicSample";
  private static final int LAUNCH_TIMEOUT = 5000;
  private static final String STRING_TO_BE_TYPED = "UiAutomator";
  private UiDevice device;

  @Before
  public void startMainActivityFromHomeScreen() {
    // Initialize UiDevice instance
    device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

    // Start from the home screen
    device.pressHome();

    // Wait for launcher
    final String launcherPackage = device.getLauncherPackageName();
    assertThat(launcherPackage, notNullValue());
    device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),
    LAUNCH_TIMEOUT);

    // Launch the app
    Context context = ApplicationProvider.getApplicationContext();
    final Intent intent = context.getPackageManager()
    .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
    // Clear out any previous instances
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
    context.startActivity(intent);

    // Wait for the app to appear
    device.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
    LAUNCH_TIMEOUT);
    }
}

No exemplo, a instrução @SdkSuppress(minSdkVersion = 18) ajuda a garantir que os testes só sejam executados em dispositivos com Android 4.3 (API de nível 18) ou versões mais recentes, conforme exigido pelo framework UI Automator.

Use o método findObject() para extrair um UiObject2 que representa uma visualização que corresponde a um determinado critério do seletor. É possível reutilizar o UiObject2 instâncias que você criou em outras partes do teste do aplicativo, conforme necessário. Observe que o framework de teste UI Automator pesquisa a exibição atual em busca de um corresponder sempre que seu teste usar uma instância de UiObject2 para clicar em uma interface ou consultar uma propriedade.

O snippet a seguir mostra como o teste pode criar UiObject2. instâncias que representam um botão Cancelar e OK em um aplicativo.

Kotlin

val okButton: UiObject2 = device.findObject(
    By.text("OK").clazz("android.widget.Button")
)

// Simulate a user-click on the OK button, if found.
if (okButton != null) {
    okButton.click()
}

Java

UiObject2 okButton = device.findObject(
    By.text("OK").clazz("android.widget.Button")
);

// Simulate a user-click on the OK button, if found.
if (okButton != null) {
    okButton.click();
}

Especificar um seletor

Se quiser acessar um componente de interface específico em um aplicativo, use o By para criar uma instância de BySelector. BySelector representa uma consulta de elementos específicos da interface exibida.

Se mais de um elemento correspondente for encontrado, o primeiro elemento correspondente na a hierarquia de layout é retornada como a UiObject2 de destino. Ao criar um BySelector, é possível encadear várias propriedades para refinar pesquisa. Se nenhum elemento de interface correspondente for encontrado, uma null será retornada.

Você pode usar os métodos hasChild() ou hasDescendant() para aninhar várias instâncias de BySelector. Por exemplo, o exemplo de código abaixo mostra como seu teste pode especificar uma pesquisa para encontrar os primeiros ListView que tem um elemento de interface filho com a propriedade text.

Kotlin

val listView: UiObject2 = device.findObject(
    By.clazz("android.widget.ListView")
        .hasChild(
            By.text("Apps")
        )
)

Java

UiObject2 listView = device.findObject(
    By.clazz("android.widget.ListView")
        .hasChild(
            By.text("Apps")
        )
);

Pode ser útil especificar o estado do objeto nos seus critérios de seleção. Para exemplo, se você quiser selecionar uma lista de todos os elementos marcados para que possa desmarque-as, chame o método checked() com o argumento definido como verdadeiro.

Realizar ações

Depois que o teste receber um objeto UiObject2, você poderá chamar os métodos da a classe UiObject2 para realizar interações do usuário no componente da IU; representado por esse objeto. É possível especificar ações como:

  • click() : clica no centro dos limites visíveis do elemento da interface.
  • drag() : arrasta esse objeto para coordenadas arbitrárias.
  • setText() : define o texto em um campo editável depois de limpar o no conteúdo do campo. Por outro lado, o método clear() limpa o texto existente. em um campo editável.
  • swipe() : realiza a ação de deslizar na direção especificada.
  • scrollUntil(): realiza a ação de rolagem na direção especificada até que o valor de Condition ou EventCondition seja atendido.

O framework de testes UI Automator permite enviar uma Intent ou iniciar uma Activity sem usar comandos shell, obtendo um Context usando getContext().

O snippet a seguir mostra como o teste pode usar uma Intent para iniciar a app em teste. Essa abordagem é útil quando você está interessado apenas em testar do app de calculadora e não se importam com a tela de início.

Kotlin

fun setUp() {
...

  // Launch a simple calculator app
  val context = getInstrumentation().context
  val intent = context.packageManager.getLaunchIntentForPackage(CALC_PACKAGE).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
  }
  // Clear out any previous instances
  context.startActivity(intent)
  device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT)
}

Java

public void setUp() {
...

  // Launch a simple calculator app
  Context context = getInstrumentation().getContext();
  Intent intent = context.getPackageManager()
  .getLaunchIntentForPackage(CALC_PACKAGE);
  intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);

  // Clear out any previous instances
  context.startActivity(intent);
  device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT);
}

Verificar resultados

O InstrumentationTestCase estende o TestCase. Assim, você pode usar métodos Assert do JUnit padrão para testar se os componentes de IU do app retornam os resultados esperados.

O snippet a seguir mostra como o teste pode localizar vários botões em um calculadora, clique neles em ordem e verifique se o resultado correto está exibidos.

Kotlin

private const val CALC_PACKAGE = "com.myexample.calc"

fun testTwoPlusThreeEqualsFive() {
  // Enter an equation: 2 + 3 = ?
  device.findObject(By.res(CALC_PACKAGE, "two")).click()
  device.findObject(By.res(CALC_PACKAGE, "plus")).click()
  device.findObject(By.res(CALC_PACKAGE, "three")).click()
  device.findObject(By.res(CALC_PACKAGE, "equals")).click()

  // Verify the result = 5
  val result: UiObject2 = device.findObject(By.res(CALC_PACKAGE, "result"))
  assertEquals("5", result.text)
}

Java

private static final String CALC_PACKAGE = "com.myexample.calc";

public void testTwoPlusThreeEqualsFive() {
  // Enter an equation: 2 + 3 = ?
  device.findObject(By.res(CALC_PACKAGE, "two")).click();
  device.findObject(By.res(CALC_PACKAGE, "plus")).click();
  device.findObject(By.res(CALC_PACKAGE, "three")).click();
  device.findObject(By.res(CALC_PACKAGE, "equals")).click();

  // Verify the result = 5
  UiObject2 result = device.findObject(By.res(CALC_PACKAGE, "result"));
  assertEquals("5", result.getText());
}

Executar testes do UI Automator em um dispositivo ou emulador

Você pode executar testes do UI Automator no Android Studio ou no usando uma linha de comando. Especifique AndroidJUnitRunner como o padrão instrumentation runner no projeto.

Mais exemplos

Interagir com a interface do sistema

O UI Automator pode interagir com tudo na tela, incluindo elementos fora do seu app, conforme mostrado nos seguintes snippets de código:

Kotlin

// Opens the System Settings.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.executeShellCommand("am start -a android.settings.SETTINGS")

Java

// Opens the System Settings.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.executeShellCommand("am start -a android.settings.SETTINGS");

Kotlin

// Opens the notification shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.openNotification()

Java

// Opens the notification shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.openNotification();

Kotlin

// Opens the Quick Settings shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.openQuickSettings()

Java

// Opens the Quick Settings shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.openQuickSettings();

Kotlin

// Get the system clock.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock"))
print(clock.getText())

Java

// Get the system clock.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock"));
print(clock.getText());

Aguardar transições

Desativar o recurso
Figura 1. O UI Automator desativa o modo Não perturbe em um dispositivo de teste.

As transições de tela podem levar tempo, e a previsão da duração delas não é confiável. Por isso, você deve fazer com que o UI Automator aguarde depois de realizar as operações. Automatizador de interface oferece vários métodos para isso:

O snippet de código a seguir mostra como usar o UI Automator para desativar o recurso Não Modo Perturbe nas Configurações do sistema usando o método performActionAndWait() que aguarda transições:

Kotlin

@Test
@SdkSuppress(minSdkVersion = 21)
@Throws(Exception::class)
fun turnOffDoNotDisturb() {
    device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
    device.performActionAndWait({
        try {
            device.executeShellCommand("am start -a android.settings.SETTINGS")
        } catch (e: IOException) {
            throw RuntimeException(e)
        }
    }, Until.newWindow(), 1000)
    // Check system settings has been opened.
    Assert.assertTrue(device.hasObject(By.pkg("com.android.settings")))

    // Scroll the settings to the top and find Notifications button
    var scrollableObj: UiObject2 = device.findObject(By.scrollable(true))
    scrollableObj.scrollUntil(Direction.UP, Until.scrollFinished(Direction.UP))
    val notificationsButton = scrollableObj.findObject(By.text("Notifications"))

    // Click the Notifications button and wait until a new window is opened.
    device.performActionAndWait({ notificationsButton.click() }, Until.newWindow(), 1000)
    scrollableObj = device.findObject(By.scrollable(true))
    // Scroll down until it finds a Do Not Disturb button.
    val doNotDisturb = scrollableObj.scrollUntil(
        Direction.DOWN,
        Until.findObject(By.textContains("Do Not Disturb"))
    )
    device.performActionAndWait({ doNotDisturb.click() }, Until.newWindow(), 1000)
    // Turn off the Do Not Disturb.
    val turnOnDoNotDisturb = device.findObject(By.text("Turn on now"))
    turnOnDoNotDisturb?.click()
    Assert.assertTrue(device.wait(Until.hasObject(By.text("Turn off now")), 1000))
}

Java

@Test
@SdkSuppress(minSdkVersion = 21)
public void turnOffDoNotDisturb() throws Exception{
    device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    device.performActionAndWait(() - >{
        try {
            device.executeShellCommand("am start -a android.settings.SETTINGS");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }, Until.newWindow(), 1000);
    // Check system settings has been opened.
    assertTrue(device.hasObject(By.pkg("com.android.settings")));

    // Scroll the settings to the top and find Notifications button
    UiObject2 scrollableObj = device.findObject(By.scrollable(true));
    scrollableObj.scrollUntil(Direction.UP, Until.scrollFinished(Direction.UP));
    UiObject2 notificationsButton = scrollableObj.findObject(By.text("Notifications"));

    // Click the Notifications button and wait until a new window is opened.
    device.performActionAndWait(() - >notificationsButton.click(), Until.newWindow(), 1000);
    scrollableObj = device.findObject(By.scrollable(true));
    // Scroll down until it finds a Do Not Disturb button.
    UiObject2 doNotDisturb = scrollableObj.scrollUntil(Direction.DOWN,
            Until.findObject(By.textContains("Do Not Disturb")));
    device.performActionAndWait(()- >doNotDisturb.click(), Until.newWindow(), 1000);
    // Turn off the Do Not Disturb.
    UiObject2 turnOnDoNotDisturb = device.findObject(By.text("Turn on now"));
    if(turnOnDoNotDisturb != null) {
        turnOnDoNotDisturb.click();
    }
    assertTrue(device.wait(Until.hasObject(By.text("Turn off now")), 1000));
}

Outros recursos

Para saber mais sobre como usar o UI Automator em testes do Android, consulte a recursos a seguir.

Documentação de referência:

Amostras