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

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

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

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

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

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

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

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

Например, чтобы сымитировать нажатие кнопки главного экрана, вызовите метод UiDevice.pressHome().

UI Automator API

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

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

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

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

Как настроить UI Automator

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

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

Kotlin

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

Яркий

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

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

Проверка интерфейса на устройстве

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

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

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

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

Чтобы посмотреть свойства интерфейса для приложения, выполните следующие действия:

  1. В интерфейсе uiautomatorviewer нажмите кнопку Device Screenshot (Скриншот устройства).
  2. Наведите указатель мыши на снимок на панели слева, чтобы увидеть компоненты интерфейса, определенные инструментом uiautomatorviewer. Свойства перечислены на панели справа внизу, а иерархия макета – на панели справа вверху.
  3. При необходимости нажмите кнопку Toggle NAF Nodes (Переключить узлы NAF), чтобы посмотреть компоненты интерфейса, недоступные для UI Automator. Для этих компонентов может быть доступна только ограниченная информация.

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

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

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

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

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

Дополнительные рекомендации по добавлению специальных возможностей в пользовательские элементы View можно найти в статье Создание доступных пользовательских представлений. Подробнее о том, как сделать приложения более доступными…

Создайте тестовый класс 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, а также кнопок "Домой" и "Меню".

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

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

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.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, представляющие кнопки "Отмена" и "ОК" в приложении.

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

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

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

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

Чтобы вложить несколько экземпляров BySelector, используйте метод hasChild() или hasDescendant(). Например, в следующем примере кода показано, как в тесте можно указать поиск, чтобы найти первый элемент ListView, у которого есть дочерний элемент пользовательского интерфейса со свойством 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")
        )
);

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

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

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

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

Фреймворк UI Automator позволяет отправлять намерения или запускать действия без использования команд оболочки. Для этого нужно получить объект Context с помощью getContext().

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

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

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

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

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

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

Как запустить тесты UI Automator на устройстве или в эмуляторе

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

Другие примеры

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

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

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

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

Отключи режим &quot;Не беспокоить&quot;
Рисунок 1. UI Automator отключает режим "Не беспокоить" на тестовом устройстве.

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

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

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

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

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

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

Образцы

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