Написание автоматизированных тестов с помощью UI Automator (старое руководство)

UI Automator — это фреймворк для тестирования пользовательского интерфейса, подходящий для кросс-приложенийного функционального тестирования пользовательского интерфейса как в системе, так и в установленных приложениях. API UI Automator позволяют взаимодействовать с видимыми элементами на устройстве, независимо от того, какая Activity находится в фокусе, что позволяет выполнять такие операции, как открытие меню настроек или панели запуска приложений на тестовом устройстве. Ваш тест может находить компоненты пользовательского интерфейса, используя удобные дескрипторы, такие как текст, отображаемый в этом компоненте, или описание его содержимого.

Фреймворк тестирования UI Automator представляет собой API, основанный на инструментировании, и работает с инструментом запуска тестов AndroidJUnitRunner . Он отлично подходит для написания непрозрачных автоматизированных тестов в стиле «блочных», где тестовый код не зависит от внутренних деталей реализации целевого приложения.

Ключевые особенности фреймворка тестирования UI Automator включают в себя следующее:

  • API для получения информации о состоянии и выполнения операций на целевом устройстве. Подробнее см. в разделе Доступ к состоянию устройства .
  • API, поддерживающие кросс-прикладное тестирование пользовательского интерфейса. Подробнее см. в разделе API UI Automator .

Доступ к состоянию устройства

Фреймворк тестирования UI Automator предоставляет класс UiDevice для доступа к устройству, на котором запущено целевое приложение, и выполнения операций на нём. Вы можете вызывать его методы для доступа к свойствам устройства, таким как текущая ориентация или размер экрана. Класс UiDevice также позволяет выполнять следующие действия:

  1. Измените поворот устройства.
  2. Нажмите аппаратные клавиши, например «увеличение громкости».
  3. Нажмите кнопки «Назад», «Домой» или «Меню».
  4. Откройте панель уведомлений.
  5. Сделайте снимок экрана текущего окна.

Например, чтобы имитировать нажатие кнопки «Домой», вызовите метод UiDevice.pressHome() .

API-интерфейсы UI Automator

API UI Automator позволяют писать надежные тесты, не разбираясь в деталях реализации целевого приложения. Вы можете использовать эти API для захвата и управления компонентами пользовательского интерфейса в нескольких приложениях:

  • UiObject2 : представляет элемент пользовательского интерфейса, видимый на устройстве.
  • BySelector : Задает критерии для сопоставления элементов пользовательского интерфейса.
  • By : Создает BySelector в краткой форме.
  • Configurator : позволяет задать ключевые параметры для запуска тестов UI Automator.

Например, следующий код показывает, как можно написать тестовый сценарий, который открывает приложение Gmail на устройстве:

Котлин

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

Ява

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

Настройка UI Automator

Перед созданием теста пользовательского интерфейса с помощью UI Automator обязательно настройте расположение исходного кода теста и зависимости проекта, как описано в разделе Настройка проекта для теста AndroidX .

В файле build.gradle вашего модуля приложения Android необходимо установить ссылку на зависимость от библиотеки UI Automator:

Котлин

dependencies { ... androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0") }

Круто

dependencies { ... androidTestImplementation "androidx.test.uiautomator:uiautomator:2.3.0" }

Чтобы оптимизировать тестирование UI Automator, необходимо сначала проверить компоненты пользовательского интерфейса целевого приложения и убедиться в их доступности. Советы по оптимизации описаны в следующих двух разделах.

Проверьте пользовательский интерфейс на устройстве

Перед разработкой теста проверьте компоненты пользовательского интерфейса, которые видны на устройстве. Чтобы тесты UI Automator могли получить доступ к этим компонентам, проверьте, есть ли у них видимые текстовые метки, значения android:contentDescription или и то, и другое.

Инструмент uiautomatorviewer предоставляет удобный визуальный интерфейс для проверки иерархии макета и просмотра свойств компонентов пользовательского интерфейса, видимых на переднем плане устройства. Эта информация позволяет создавать более детальные тесты с помощью UI Automator. Например, можно создать селектор пользовательского интерфейса, соответствующий определённому видимому свойству.

Чтобы запустить инструмент uiautomatorviewer :

  1. Запустите целевое приложение на физическом устройстве.
  2. Подключите устройство к машине разработки.
  3. Откройте окно терминала и перейдите в каталог <android-sdk>/tools/ .
  4. Запустите инструмент с помощью этой команды:
 $ uiautomatorviewer

Чтобы просмотреть свойства пользовательского интерфейса вашего приложения:

  1. В интерфейсе uiautomatorviewer нажмите кнопку «Снимок экрана устройства» .
  2. Наведите указатель мыши на снимок на левой панели, чтобы увидеть компоненты пользовательского интерфейса, идентифицированные инструментом uiautomatorviewer . Свойства перечислены на нижней правой панели, а иерархия макета — на верхней правой панели.
  3. При желании нажмите кнопку «Переключить узлы NAF», чтобы увидеть компоненты пользовательского интерфейса, недоступные для UI Automator. Информация об этих компонентах может быть ограничена.

Чтобы узнать о распространенных типах компонентов пользовательского интерфейса, предоставляемых Android, см. раздел Пользовательский интерфейс .

Убедитесь, что ваша деятельность доступна

Тестовый фреймворк UI Automator лучше работает в приложениях, в которых реализованы специальные возможности Android. При использовании элементов пользовательского интерфейса типа View или подкласса View из SDK вам не нужно реализовывать поддержку специальных возможностей, так как эти классы уже сделали это за вас.

Однако некоторые приложения используют пользовательские элементы пользовательского интерфейса для обеспечения более расширенного пользовательского опыта. Такие элементы не обеспечивают автоматической поддержки специальных возможностей. Если ваше приложение содержит экземпляры подкласса View , не входящего в SDK, убедитесь, что вы добавили функции специальных возможностей к этим элементам, выполнив следующие действия:

  1. Создайте конкретный класс, расширяющий ExploreByTouchHelper .
  2. Свяжите экземпляр нового класса с определенным элементом пользовательского интерфейса, вызвав setAccessibilityDelegate() .

Дополнительные рекомендации по добавлению функций доступности к пользовательским элементам представления см. в статье Создание доступных пользовательских представлений . Подробнее об общих рекомендациях по обеспечению доступности на Android см. в статье Повышение доступности приложений .

Создайте тестовый класс UI Automator

Тестовый класс UI Automator должен быть написан так же, как тестовый класс JUnit 4. Подробнее о создании тестовых классов JUnit 4 и использовании утверждений и аннотаций JUnit 4 см. в разделе Создание класса инструментированного модульного теста .

Добавьте аннотацию @RunWith(AndroidJUnit4.class) в начало определения тестового класса. Также необходимо указать класс AndroidJUnitRunner , предоставленный в AndroidX Test, в качестве средства запуска тестов по умолчанию. Этот шаг подробно описан в разделе Запуск тестов UI Automator на устройстве или эмуляторе .

Реализуйте следующую модель программирования в тестовом классе UI Automator:

  1. Получите объект UiDevice для доступа к устройству, которое вы хотите протестировать, вызвав метод getInstance() и передав ему объект Instrumentation в качестве аргумента.
  2. Получите объект UiObject2 для доступа к компоненту пользовательского интерфейса, отображаемому на устройстве (например, текущее представление на переднем плане), вызвав метод findObject() .
  3. Смоделируйте конкретное взаимодействие пользователя с этим компонентом пользовательского интерфейса, вызвав метод UiObject2 ; например, вызовите scrollUntil() для прокрутки и setText() для редактирования текстового поля. Вы можете вызывать API из шагов 2 и 3 по мере необходимости для тестирования более сложных взаимодействий пользователя, включающих несколько компонентов пользовательского интерфейса или последовательности действий пользователя.
  4. Проверьте, отражает ли пользовательский интерфейс ожидаемое состояние или поведение после выполнения этих взаимодействий с пользователем.

Эти шаги более подробно описаны в разделах ниже.

Доступ к компонентам пользовательского интерфейса

Объект UiDevice — это основной способ доступа к состоянию устройства и управления им. В тестах вы можете вызывать методы UiDevice для проверки состояния различных свойств, таких как текущая ориентация или размер экрана. Ваш тест может использовать объект UiDevice для выполнения действий на уровне устройства, таких как принудительный поворот устройства в определённое положение, нажатие аппаратных кнопок D-pad, а также кнопок «Домой» и «Меню».

Рекомендуется начинать тест с главного экрана устройства. С главного экрана (или с любого другого выбранного вами начального экрана устройства) вы можете вызывать методы API UI Automator для выбора и взаимодействия с определёнными элементами пользовательского интерфейса.

В следующем фрагменте кода показано, как ваш тест может получить экземпляр UiDevice и имитировать нажатие кнопки «Домой»:

Котлин

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.getApplicationContext<Context>()
  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
    )
  }
}

Ява

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

В этом примере оператор @SdkSuppress(minSdkVersion = 18) помогает гарантировать, что тесты будут запускаться только на устройствах с Android 4.3 (уровень API 18) или выше, как того требует фреймворк UI Automator.

Используйте метод findObject() для получения объекта UiObject2 , представляющего представление, соответствующее заданному критерию селектора. При необходимости вы можете повторно использовать экземпляры UiObject2 , созданные в других этапах тестирования приложения. Обратите внимание, что тестовая среда UI Automator выполняет поиск совпадений на текущем дисплее каждый раз, когда ваш тест использует экземпляр UiObject2 для щелчка по элементу пользовательского интерфейса или запроса свойства.

В следующем фрагменте показано, как ваш тест может создавать экземпляры UiObject2 , представляющие кнопку «Отмена» и кнопку «ОК» в приложении.

Котлин

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

Ява

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

Укажите селектор

Если вам нужен доступ к определённому компоненту пользовательского интерфейса в приложении, используйте класс By для создания экземпляра BySelector . BySelector представляет собой запрос определённых элементов в отображаемом пользовательском интерфейсе.

Если найдено более одного подходящего элемента, в качестве целевого объекта UiObject2 возвращается первый подходящий элемент в иерархии макета. При построении BySelector можно объединить несколько свойств для уточнения поиска. Если подходящего элемента пользовательского интерфейса не найдено, возвращается значение null .

Вы можете использовать метод hasChild() или hasDescendant() для создания нескольких вложенных экземпляров BySelector . Например, в следующем примере кода показано, как ваш тест может задать поиск для поиска первого элемента ListView с дочерним элементом пользовательского интерфейса со свойством text.

Котлин

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

Ява

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

Может быть полезно указать состояние объекта в критерии селектора. Например, если вы хотите выбрать список всех отмеченных элементов, чтобы затем очистить их, вызовите метод checked() с аргументом true.

Выполнять действия

После того, как ваш тест получит объект UiObject2 , вы можете вызывать методы класса UiObject2 для выполнения пользовательских взаимодействий с компонентом пользовательского интерфейса, представленным этим объектом. Вы можете указать следующие действия:

  • click() : Щелкает по центру видимых границ элемента пользовательского интерфейса.
  • drag() : перетаскивает данный объект в произвольные координаты.
  • setText() : устанавливает текст в редактируемом поле после очистки его содержимого. Метод clear() , наоборот, очищает существующий текст в редактируемом поле.
  • swipe() : выполняет действие смахивания в указанном направлении.
  • scrollUntil() : выполняет действие прокрутки в указанном направлении до тех пор, пока не будет выполнено Condition или EventCondition .

Тестовая среда UI Automator позволяет отправлять Intent или запускать Activity без использования команд оболочки, получая объект Context через getContext() .

В следующем фрагменте кода показано, как ваш тест может использовать намерение для запуска тестируемого приложения. Этот подход полезен, если вас интересует только тестирование приложения-калькулятора, и вам не нужен лаунчер.

Котлин

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

Ява

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

Проверить результаты

InstrumentationTestCase расширяет TestCase , поэтому вы можете использовать стандартные методы JUnit Assert для проверки того, что компоненты пользовательского интерфейса в приложении возвращают ожидаемые результаты.

В следующем фрагменте показано, как ваш тест может найти несколько кнопок в приложении калькулятора, нажать на них по порядку, а затем проверить, отображается ли правильный результат.

Котлин

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

Ява

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

Запуск тестов UI Automator на устройстве или эмуляторе

Тесты UI Automator можно запускать из Android Studio или из командной строки. Обязательно укажите AndroidJUnitRunner в качестве инструментария по умолчанию в вашем проекте.

Больше примеров

Взаимодействие с системным пользовательским интерфейсом

UI Automator может взаимодействовать со всем на экране, включая системные элементы за пределами вашего приложения, как показано в следующих фрагментах кода:

Котлин

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

Ява

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

Котлин

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

Ява

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

Котлин

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

Ява

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

Котлин

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

Ява

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

Ждать переходов

Выключить функцию «Беспокоить»
Рисунок 1. UI Automator отключает режим «Не беспокоить» на тестовом устройстве.

Переходы между экранами могут занимать время, и предсказать их длительность ненадёжно, поэтому следует настроить UI Automator на ожидание после выполнения операций. UI Automator предоставляет для этого несколько методов:

В следующем фрагменте кода показано, как использовать UI Automator для отключения режима «Не беспокоить» в настройках системы с помощью метода performActionAndWait() , который ожидает переходов:

Котлин

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

Ява

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

Дополнительные ресурсы

Дополнительную информацию об использовании UI Automator в тестах Android можно найти в следующих ресурсах.

Справочная документация:

Образцы

  • BasicSample : базовый пример UI Automator.