Cómo escribir pruebas automatizadas con UI Automator

UI Automator es un framework de pruebas de IU adecuado para IU funcionales entre apps. pruebas en el sistema y las apps instaladas. Las APIs de UI Automator te permiten interactuar con elementos visibles en un dispositivo, independientemente de qué Activity esté en enfoque, por lo que te permite realizar operaciones como abrir el menú Configuración o el selector de aplicaciones en un dispositivo de prueba. La prueba puede buscar un componente de IU utilizando descriptores convenientes, como el texto que se muestra en ese componente o su descripción del contenido.

El framework de prueba de UI Automator es una API basada en instrumentación que funciona con el ejecutor de pruebas AndroidJUnitRunner Es ideal para escribir pruebas automatizadas de tipo caja opaca, en las que el código de prueba no se basa en detalles de implementación de la app de destino.

Estas son algunas de las funciones clave del framework de prueba de UI Automator:

Cómo acceder al estado del dispositivo

El framework de prueba de UI Automator proporciona una clase UiDevice para acceder. y realizar operaciones en el dispositivo en el que se ejecuta la app de destino. Puedes llamar a sus métodos para acceder a propiedades del dispositivo, como la orientación actual o el tamaño de visualización. La clase UiDevice también te permite realizar las siguientes tareas: acciones:

  1. Cambiar la rotación del dispositivo.
  2. Presiona teclas de hardware, como "subir el volumen".
  3. Presionar los botones Atrás, Página principal o Menú.
  4. Abrir el panel de notificaciones.
  5. Tomar una captura de pantalla de la ventana actual.

Por ejemplo, para simular la presión del botón de la página principal, llama a UiDevice.pressHome(). .

API de UI Automator

Las APIs de UI Automator te permiten escribir pruebas sólidas sin necesidad de saber sobre los detalles de implementación de la app objetivo. Puedes usar estas APIs para capturar y manipular componentes de la IU en varias apps:

  • UiObject2: Representa un elemento de la IU que es visible en el dispositivo.
  • BySelector: Especifica los criterios para los elementos de la IU coincidentes.
  • By: Construye BySelector de manera concisa.
  • Configurator: Te permite establecer parámetros clave para ejecutar pruebas de UI Automator.

Por ejemplo, el siguiente código muestra cómo escribir una secuencia de comandos de prueba que abre una aplicación de Gmail en el 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);

Cómo configurar UI Automator

Antes de compilar la prueba de la IU con UI Automator, asegúrate de configurar la prueba. ubicación del código fuente y las dependencias del proyecto, tal como se describe en Configura el proyecto para AndroidX Test.

En el archivo build.gradle del módulo de tu app para Android, debes establecer una dependencia. Referencia a la 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 optimizar las pruebas de UI Automator, primero debes inspeccionar el componentes de IU y asegúrate de que sean accesibles. Estas sugerencias de optimización que se describe en las siguientes dos secciones.

Cómo inspeccionar la IU en un dispositivo

Antes de diseñar la prueba, inspecciona los componentes de la IU que están visibles en la dispositivo. Para asegurarte de que las pruebas de UI Automator puedan acceder a estos componentes, comprobar que estos componentes tengan etiquetas de texto visibles valores android:contentDescription o ambos.

La herramienta uiautomatorviewer proporciona una interfaz visual práctica para inspeccionar la jerarquía de diseño y ver las propiedades de los componentes de la IU que son visibles en el primer plano del dispositivo. Esta información te permite crear más con UI Automator. Por ejemplo, puedes crear un selector de IU que coincida con una propiedad visible específica.

Para iniciar la herramienta uiautomatorviewer, haz lo siguiente:

  1. Inicia la app de destino en un dispositivo físico.
  2. Conecta el dispositivo a la máquina de desarrollo.
  3. Abre una ventana de terminal y navega al directorio <android-sdk>/tools/.
  4. Ejecuta la herramienta con este comando:
 $ uiautomatorviewer

Para ver las propiedades de la IU de tu aplicación, haz lo siguiente:

  1. En la interfaz de uiautomatorviewer, haz clic en el botón Device Screenshot.
  2. Coloca el cursor sobre la instantánea, en el panel de la izquierda, para ver los componentes de la IU. identificados por la herramienta uiautomatorviewer. Las propiedades se enumeran en la menos panel derecho y la jerarquía de diseño en el panel superior derecho.
  3. De manera opcional, haz clic en el botón Toggle NAF Nodes para ver los componentes de la IU. que no son accesibles para UI Automator. Solo la información limitada disponibles para estos componentes.

Para obtener más información sobre los tipos comunes de componentes de interfaz de usuario proporcionados por Android, consulta Información sobre Interfaz de usuario.

Asegúrate de que se pueda acceder a tu actividad

El framework de prueba de UI Automator tiene un mejor rendimiento en las apps que se implementaron. Funciones de accesibilidad de Android. Cuando usas elementos de la IU de tipo View. una subclase de View del SDK, no necesitas implementar accesibilidad porque estas clases ya lo hicieron por ti.

Sin embargo, algunas apps usan elementos de IU personalizados para proporcionar una experiencia del usuario enriquecida. Estos elementos no proporcionarán compatibilidad de accesibilidad automática. Si su aplicación contiene instancias de una subclase de View que no pertenece al SDK, crea asegúrate de agregar funciones de accesibilidad a estos elementos completando el los siguientes pasos:

  1. Crea una clase concreta que extienda ExploreByTouchHelper.
  2. Asocia una instancia de tu nueva clase con un elemento de IU personalizado específico. Para ello, haz lo siguiente: llamando a setAccessibilityDelegate().

Para obtener orientación adicional sobre cómo agregar funciones de accesibilidad a la vista personalizada consulta Cómo compilar vistas personalizadas accesibles. Para obtener más información prácticas recomendadas generales sobre accesibilidad en Android; consulta Cómo crear apps más Accesible.

Cómo crear una clase de prueba de UI Automator

Tu clase de prueba de UI Automator debe escribirse de la misma manera que una prueba de JUnit 4. . Obtén más información sobre la creación de clases de prueba de JUnit 4 y el uso de JUnit 4. aserciones y anotaciones, consulta Cómo crear una clase de prueba de unidades instrumentadas.

Agrega la anotación @RunWith(AndroidJUnit4.class) al comienzo de la prueba. definición de la clase. También debes especificar la clase AndroidJUnitRunner, que se proporcionan en AndroidX Test, como ejecutor de pruebas predeterminado. Este paso se describe con más detalle, en Cómo ejecutar pruebas de UI Automator en un dispositivo o emulador.

Implementa el siguiente modelo de programación en la clase de prueba de UI Automator:

  1. Obtén un objeto UiDevice para acceder al dispositivo que quieres probar llamando a el método getInstance() y pasarle un objeto Instrumentation como el argumento.
  2. Obtén un objeto UiObject2 para acceder a un componente de la IU que se muestra en el (por ejemplo, la vista actual en primer plano), llamando al findObject().
  3. Simula una interacción específica del usuario para realizar en ese componente de la IU llamar a un método UiObject2; por ejemplo, llama scrollUntil() para desplazarte y setText() para editar un campo de texto. Puedes llamar a las APIs en los pasos 2 y 3. repetidamente, según sea necesario, para probar interacciones de usuario más complejas que involucren varios componentes de la interfaz de usuario o secuencias de acciones del usuario.
  4. Comprobar que la IU refleje el estado o comportamiento esperados después de que estos usuarios se llevan a cabo interacciones.

Estos pasos se explican con más detalles en las siguientes secciones.

Cómo acceder a los componentes de la IU

El objeto UiDevice es la forma principal en la que accedes a los archivos y los manipulas. estado del dispositivo. En las pruebas, puedes llamar a los métodos UiDevice para verificar el estado de varias propiedades, como la orientación actual o el tamaño de visualización. La prueba puede usar el objeto UiDevice para realizar acciones a nivel del dispositivo. como forzar una rotación específica del dispositivo, presionar el hardware del pad direccional y cómo presionar los botones Inicio y Menú.

Se recomienda comenzar la prueba desde la pantalla principal del dispositivo. Desde la pantalla principal (o alguna otra ubicación de inicio que hayas elegido en el dispositivo) puedes llamar a los métodos proporcionados por la API de UI Automator para seleccionar e interactuar con elementos específicos de la IU.

En el siguiente fragmento de código, se muestra cómo la prueba podría obtener una instancia de UiDevice y simular la presión de un botón de inicio:

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

En el ejemplo, la sentencia @SdkSuppress(minSdkVersion = 18) ayuda a garantizar que las pruebas solo se ejecutarán en dispositivos con Android 4.3 (nivel de API 18) o versiones posteriores, según lo requiera el framework de UI Automator.

Usa el método findObject() para recuperar un UiObject2 que represente una vista que coincide con un criterio de selección determinado. Puedes reutilizar el UiObject2 que hayas creado en otras partes de las pruebas de tu app, según sea necesario. Ten en cuenta que el framework de prueba de UI Automator busca en la pantalla actual un coincidir cada vez que la prueba use una instancia de UiObject2 para hacer clic en una IU elemento o consultar una propiedad.

En el siguiente fragmento, se muestra cómo la prueba podría construir UiObject2 que representan los botones Cancelar y Aceptar en una app.

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

Cómo especificar un selector

Si quieres acceder a un componente de interfaz de usuario específico de una aplicación, usa la Clase By para construir una instancia BySelector. BySelector representa una consulta de elementos específicos de la IU que se muestra.

Si se encuentra más de un elemento coincidente, el primer elemento coincidente de la la jerarquía de diseño se muestra como el UiObject2 de destino. Cuando se construye un BySelector, puedes encadenar varias propiedades para definir mejor tus búsqueda. Si no se encuentra ningún elemento de IU que coincida, se muestra null.

Puedes usar el método hasChild() o hasDescendant() para anidar varias instancias de BySelector. En el siguiente ejemplo de código, se muestra cómo la prueba podría especificar una búsqueda para encontrar el primer ListView que tiene un elemento secundario de la IU con la propiedad de texto.

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

Puede ser útil especificar el estado del objeto en los criterios del selector. Para ejemplo, si deseas seleccionar una lista de todos los elementos marcados para poder Desmárcalas, llama al método checked() con el argumento establecido en verdadero.

Realizar acciones

Una vez que la prueba haya obtenido un objeto UiObject2, puedes llamar a los métodos en la clase UiObject2 para realizar interacciones del usuario en el componente de IU representados por ese objeto. Puedes especificar acciones como las siguientes:

  • click() : Hace clic en el centro de los límites visibles del elemento de la IU.
  • drag() : Arrastra este objeto a coordenadas arbitrarias.
  • setText() : Establece el texto en un campo editable, después de borrar la el contenido del campo. Por el contrario, el método clear() borra el texto existente. en un campo editable.
  • swipe() : Realiza la acción de deslizamiento hacia la dirección especificada.
  • scrollUntil(): Realiza la acción de desplazamiento en la dirección especificada. hasta que se cumpla Condition o EventCondition.

El framework de prueba de UI Automator te permite enviar un Intent o iniciar una Activity sin usar comandos de shell, mediante la obtención de un Context objeto a través de getContext().

En el siguiente fragmento, se muestra cómo la prueba puede usar un Intent para iniciar la la app que se está probando. Este enfoque es útil cuando solo te interesa probar la app de la calculadora y no te interesa el selector.

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

Cómo verificar los resultados

InstrumentationTestCase extiende TestCase para que puedas usar métodos estándar Assert de JUnit para probar que devuelvan los componentes de la IU de la app. los resultados esperados.

En el siguiente fragmento, se muestra cómo la prueba puede ubicar varios botones en una haz clic en ellos en orden y, luego, verifica que el resultado correcto que se muestra.

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

Cómo ejecutar pruebas de UI Automator en un dispositivo o emulador

Puedes ejecutar pruebas de UI Automator desde Android Studio o desde desde la línea de comandos. Asegúrate de especificar AndroidJUnitRunner como valor predeterminado. de instrumentación en tu proyecto.

Más ejemplos

Cómo interactuar con la IU del sistema

UI Automator puede interactuar con todo lo que aparece en la pantalla, incluso con el sistema fuera de tu app, como se muestra en los siguientes fragmentos 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());

Espera las transiciones

Desactivar interrumpir
Figura 1: UI Automator desactiva el modo No interrumpir en un dispositivo de prueba.

Las transiciones de pantalla pueden llevar tiempo y predecir su duración no es confiable, así que Deberías hacer que UI Automator espere después de realizar las operaciones. UI Automator proporciona varios métodos para esto:

En el siguiente fragmento de código, se muestra cómo usar UI Automator para desactivar el modo Do Not El modo interrumpir en la configuración del sistema usa el método performActionAndWait() que espera las transiciones:

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

Recursos adicionales

Para obtener más información sobre el uso de UI Automator en pruebas de Android, consulta el los siguientes recursos.

Documentación de referencia:

Ejemplos