UI Automator – это фреймворк для тестирования пользовательского интерфейса, который подходит для кросс-апп тестирования системных и установленных приложений. UI Automator API позволяет взаимодействовать с видимыми элементами на устройстве независимо от того, какое действие Activity находится в фокусе. Таким образом, вы можете выполнять такие операции, как открытие меню настроек или панели запуска приложений на тестовом устройстве. Тест может найти элемент интерфейса по удобным дескрипторам, например по тексту, который отображается в этом элементе, или по описанию его контента.
Фреймворк UI Automator – это API на основе инструментария, который работает с исполнителем тестов AndroidJUnitRunner. Он хорошо подходит для написания автоматизированных тестов в стиле непрозрачного ящика, где тестовый код не зависит от деталей внутренней реализации целевого приложения.
Ключевые особенности фреймворка UI Automator:
- API для получения информации о состоянии и выполнения операций на целевом устройстве. Подробнее о доступе к состоянию устройства…
- API, поддерживающие тестирование пользовательского интерфейса в разных приложениях. Подробную информацию вы найдете в статье UI Automator API.
Доступ к состоянию устройства
Фреймворк UI Automator предоставляет класс UiDevice для доступа к устройству, на котором запущено целевое приложение, и выполнения операций на нем. Вы можете вызывать его методы, чтобы получать доступ к свойствам устройства, например текущей ориентации или размеру экрана. Класс UiDevice также позволяет выполнять следующие действия:
- Измените ориентацию устройства.
- Нажмите аппаратные клавиши, например "Увеличить громкость".
- Нажмите кнопку "Назад", "Домой" или "Меню".
- Откройте панель уведомлений.
- Сделайте скриншот текущего окна.
Например, чтобы сымитировать нажатие кнопки главного экрана, вызовите метод 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:
- Запустите целевое приложение на физическом устройстве.
- Подключите устройство к компьютеру для разработки.
- Откройте окно терминала и перейдите в каталог
<android-sdk>/tools/. - Запустите инструмент с помощью следующей команды:
$ uiautomatorviewer
Чтобы посмотреть свойства интерфейса для приложения, выполните следующие действия:
- В интерфейсе
uiautomatorviewerнажмите кнопку Device Screenshot (Скриншот устройства). - Наведите указатель мыши на снимок на панели слева, чтобы увидеть компоненты интерфейса, определенные инструментом
uiautomatorviewer. Свойства перечислены на панели справа внизу, а иерархия макета – на панели справа вверху. - При необходимости нажмите кнопку Toggle NAF Nodes (Переключить узлы NAF), чтобы посмотреть компоненты интерфейса, недоступные для UI Automator. Для этих компонентов может быть доступна только ограниченная информация.
Подробнее о распространенных типах компонентов пользовательского интерфейса, предоставляемых Android…
Убедитесь, что ваше действие доступно
Фреймворк UI Automator лучше работает с приложениями, в которых реализованы специальные возможности Android. Если вы используете элементы интерфейса типа View или подкласс View из SDK, вам не нужно реализовывать поддержку специальных возможностей, так как она уже реализована в этих классах.
Однако в некоторых приложениях используются специальные элементы интерфейса, которые делают его более удобным.
Такие элементы не будут автоматически поддерживать специальные возможности. Если в вашем приложении есть экземпляры подкласса View, не относящиеся к SDK, добавьте для них специальные возможности, выполнив следующие действия:
- Создайте конкретный класс, расширяющий ExploreByTouchHelper.
- Свяжите экземпляр нового класса с определенным элементом интерфейса, вызвав метод setAccessibilityDelegate().
Дополнительные рекомендации по добавлению специальных возможностей в пользовательские элементы View можно найти в статье Создание доступных пользовательских представлений. Подробнее о том, как сделать приложения более доступными…
Создайте тестовый класс UI Automator.
Тестовый класс UI Automator должен быть написан так же, как тестовый класс JUnit 4. Подробнее о том, как создавать тестовые классы JUnit 4 и использовать утверждения и аннотации JUnit 4, рассказывается в статье Создание класса инструментального модульного тестирования.
Добавьте аннотацию @RunWith(AndroidJUnit4.class) в начале определения тестового класса. Вам также необходимо указать класс AndroidJUnitRunner, предоставляемый в AndroidX Test, в качестве средства запуска тестов по умолчанию. Подробнее об этом шаге рассказывается в разделе Запустите тесты UI Automator на устройстве или эмуляторе.
Реализуйте следующую модель программирования в тестовом классе UI Automator:
- Получите объект
UiDevice, чтобы получить доступ к устройству, которое вы хотите протестировать. Для этого вызовите метод getInstance() и передайте ему объект Instrumentation в качестве аргумента. - Получите объект
UiObject2, чтобы получить доступ к компоненту пользовательского интерфейса, который отображается на устройстве (например, текущему представлению на переднем плане), вызвав метод findObject(). - Смоделируйте определенное взаимодействие пользователя с компонентом интерфейса, вызвав метод
UiObject2. Например, вызовите scrollUntil() для прокрутки и setText() для редактирования текстового поля. Вы можете вызывать API на шагах 2 и 3 повторно, чтобы протестировать более сложные взаимодействия с пользователем, включающие несколько компонентов пользовательского интерфейса или последовательностей действий пользователя. - Проверьте, отражает ли пользовательский интерфейс ожидаемое состояние или поведение после выполнения этих действий пользователя.
Подробные инструкции приведены в разделах ниже.
Доступ к компонентам интерфейса
Объект 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());
Дождитесь переходов
Переходы между экранами могут занимать некоторое время, и предсказать их продолжительность невозможно, поэтому после выполнения операций UI Automator должен подождать. Для этого в UI Automator есть несколько методов:
UiDevice.performActionAndWait(Runnable action, EventCondition<U> condition, long timeout). Например, чтобы нажать кнопку и подождать, пока появится новое окно, вызовитеdevice.performActionAndWait(() -> button.click(), Until.newWindow(), timeout).UiDevice.wait(Condition<Object, U> condition, long timeout): например, чтобы дождаться определенногоUiObject2на устройстве, вызовитеdevice.wait(Until.hasObject(By.text("my_text")), timeout);.UiObject2.wait(@NonNull Condition<Object, U> condition, long timeout)Например, чтобы дождаться установки флажка, вызовитеcheckbox.wait(Until.checked(true), timeout);.UiObject2.clickAndWait(@NonNull EventCondition<U> condition, long timeout). Например, чтобы нажать кнопку и подождать, пока появится новое окно, вызовитеbutton.clickAndWait(Until.newWindow(), timeout);.UiObject2.scrollUntil(@NonNull Direction direction, @NonNull Condition<Object, U> condition). Например, чтобы прокрутить экран вниз до появления нового объекта, вызовитеobject.scrollUntil(Direction.DOWN, Until.hasObject(By.text('new_obj')));.UiObject2.scrollUntil(@NonNull Direction direction, @NonNull EventCondition<U> condition): например, чтобы прокрутить страницу до конца, вызовитеobject.scrollUntil(Direction.DOWN, Until.scrollFinished(Direction.DOWN));.
В следующем фрагменте кода показано, как использовать 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.