Используйте устаревший API UI Automator

UI Automator — это фреймворк для тестирования пользовательского интерфейса, подходящий для функционального тестирования UI-интерфейсов различных приложений, как системных, так и установленных. 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()

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

Настройте UI Automator

Перед созданием UI-теста с помощью UI Automator убедитесь, что вы настроили расположение исходного кода теста и зависимости проекта, как описано в разделе «Настройка проекта для AndroidX Test» .

В файле 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, а также нажатие кнопок Home и Menu.

Рекомендуется начинать тестирование с главного экрана устройства. С главного экрана (или с любого другого выбранного вами начального места на устройстве) вы можете вызывать методы, предоставляемые 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
    )
  }
}

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

В приведенном примере оператор `@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()
}

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

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

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

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

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

Котлин

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

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

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

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

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

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

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

Котлин

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

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

Класс 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)
}

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

Запустите тесты 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")

Java

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

Java

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

Котлин

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

Котлин

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

Дождитесь переходов

Отключить функцию «Раздражать»
Рисунок 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))
}

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

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

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

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

Образцы

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