Уникальные элементы для автотестов: где они обитают и как их искать

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Привет, Хабр! Меня зовут Вера Соколова, я Android-разработчик в команде автотестирования проекта Мой МТС.

Автотесты — очень полезная штука в разработке, так как они упрощают жизнь ручным тестировщикам. На релизе во время smoke-прогона у коллег и так очень много работы, а тут небольшая, но порой ощутимая помощь в виде автотестов, когда после запуска руками тестировщики проверяют только то, что не автоматизировано или если тесты где-то не прошли.



Но автотесты нужно еще написать и первостепенная задача — поиск элементов. Чтобы кликнуть на кнопочку, ее нужно сначала найти. С какими трудностями при этом можно столкнуться и как эти трудности преодолеть я расскажу ниже, добро пожаловать под кат!

А зачем вообще искать?

Любой тест — это взаимодействие. В случае с автотестированием — это взаимодействие с элементами на экране мобильного телефона: кнопками, текстами, чекбоксами, диалогами, таббарами. Когда ручной тестировщик прокликивает элементы — у него не возникает вопросов, куда конкретно кликать, это описано в кейсе. Но как научить машину кликать на «вон ту большую красную кнопку»? Задача усложняется, если таких больших красных кнопок на экране несколько, а система блочная, элементы в ней друг от друга не зависят и переиспользуются.



Что мы подразумеваем под уникальным элементом? Это элемент, который можно идентифицировать на экране в единственном экземпляре. Всего одна большая красная кнопка, всего один заголовок таббара, всего одна кнопка «назад». Яркий пример не уникальных элементов — повторяющиеся айтемы списка, с ними часто возникают проблемы. У каждого элемента должно быть что-то особенное, отличительная черта, иначе драйвер его не найдет.

Казалось бы, нужно взять и найти такие элементы, в чем проблема-то? Но не все так просто, как кажется.

Стратегии поиска элементов

В зависимости от используемого драйвера/платформы есть несколько вариантов поиска. В нашем случае речь пойдет про Android и 2 драйвера — UIAutomator2 и Espresso. В таблице — стратегии, которые мы использовали в автотестах:



Рассмотрим стратегии по отдельности.

Content description

Content description — в дословном переводе это «описание контента». (cпасибо, кэп!) Этот параметр в android задуман как метка, позволяющая пользователям с ограниченными возможностями распознавать элементы интерфейса при помощи программы чтения с экрана. А теперь представьте какой-нибудь аватар на главном экране приложения. Если в атрибут content-desk этого элемента зашить значение вида avatar_main_screen, то при чтении программой пользователь явно услышит не то, что должен. Поэтому от Content description мы отказались почти сразу.

ID и первые проблемы. Нумерация элементов и необходимость наличия в ресурсах

Использование идентификаторов — это самый предпочтительный способ. Но есть нюансы. Казалось бы, пронумеруй все элементы списка, проставь динамически идентификаторы во время выполнения программы в зависимости от контента и готово. Не все так просто.

Допустим, у нас есть некий список, который состоит из айтемов:



Проблема 1: при динамической смене id конечный идентификатор должен быть прописан в ресурсах приложения. Если список состоит из 10 элементов, в ресурсах должны быть прописаны все 10 id: item0, item1, … item9, иначе во время выполнения при попытке сменить идентификатор приложение упадет, не найдя нужный id.

<code>
<item type="id" name="item" />
<item type="id" name="item0" />
<item type="id" name="item1" />
<item type="id" name="item2" />
<item type="id" name="item3" />
<item type="id" name="item4" />
<item type="id" name="item5" />
<item type="id" name="item6" />
<item type="id" name="item7" />
<item type="id" name="item8" />
<item type="id" name="item9" />
<code>

Решение: в Android-проекте нужно добавить обработку ошибки Resource not found exception. Так мы изменяем только те идентификаторы элементов, которые прописаны в ресурсах.
Проблема 2: Каждый айтем — это контейнер с некоторым набором элементов (иконка, текст, описание). Представьте, что для каждого такого элемента придётся в ресурсах прописывать id. Тогда они быстро превратятся в помойку из кучи пронумерованных элементов.

<code>
    <!--Блок item_container-->
    <item type="id" name="itemContainer" />
    <item type="id" name="itemContainer0" />
    <item type="id" name="itemContainer1" />
    <item type="id" name="itemContainer2" />
    <item type="id" name="itemContainer3" />
    <item type="id" name="itemContainer4" />
    <item type="id" name="itemContainer5" />
    <item type="id" name="itemContainer6" />
    <item type="id" name="itemContainer7" />
    <item type="id" name="itemContainer8" />
    <item type="id" name="itemContainer9" />
    <item type="id" name="itemTitle" />
    <item type="id" name="itemTitle0" />
    <item type="id" name="itemTitle1" />
    <item type="id" name="itemTitle2" />
    <item type="id" name="itemTitle3" />
    <item type="id" name="itemTitle4" />
    <item type="id" name="itemTitle5" />
    <item type="id" name="itemTitle6" />
    <item type="id" name="itemTitle7" />
    <item type="id" name="itemTitle8" />
    <item type="id" name="itemTitle9" />

    <item type="id" name="itemIcon" />
    <item type="id" name="itemIcon0" />
    <item type="id" name="itemIcon1" />
    <item type="id" name="itemIcon2" />
    <item type="id" name="itemIcon3" />
    <item type="id" name="itemIcon4" />
    <item type="id" name="itemIcon5" />
    <item type="id" name="itemIcon6" />
    <item type="id" name="itemIcon7" />
    <item type="id" name="itemIcon8" />
    <item type="id" name="itemIcon9" />

<code>

Решение: нужно нумеровать только контейнеры (родительские блоки) первых 10-15 элементов. Для тестов этого количества обычно достаточно. Элементы внутри контейнера чаще всего уникальные. Далее при написании теста сначала ищем родителя, а по нему уже находим нужный элемент.

XPath

XPath — поиск элементов по дереву xml. Это, наверное, самая нежелательная стратегия поиска. Но она самая доступная, поскольку все остальные варианты требуют дополнительных манипуляций со стороны приложения.

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

<code>
public final By redButton =    By.xpath( "/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout");
<code>


Мы используем XPath для ускорения написания теста, когда идентификатора нет и разработчику нужно время на то, чтобы добавить его. Выручает он и в тех случаях, когда нужно обратиться к каким-либо системным элементам, например, к ланчеру Android или залезть в настройки, тогда без этого инструмента никак. А также в тех случаях, когда остальные стратегии поиска недоступны.

Работу можно упростить, используя возможности и сокращения языка xpath. Для упрощения составления более короткого xpath удобно использовать онлайн-инструменты, например раз и два. Есть множество сервисов, в которых можно подобрать более короткую комбинацию для xpath, предварительно загнав xml разметки экрана.

Мы пришли также к составлению xpath для поиска более чем по одному родителю, когда в некоторую функцию передаем список идентификаторов, а на выходе получаем склеенную строчку xpath:

<code> 
public By androidXpath(String... elementIds) {
   StringBuilder xpath = new StringBuilder();
for (String elementId : elementIds) {       xpath.append("//*[ends-with(@resource-id,\"/").append(elementId).append("\")]");
   }
   return By.xpath(xpath.toString());
}
<code>

Идентификаторы последовательно передаются через запятую в порядке от родителя верхнего уровня до конечного элемента. Уровень вложенности значения не имеет. Главное — уникальность каждого n-ого родителя среди прочих контейнеров.

Теги и эспрессо

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

  1. Файлик build_config.xml с описанием параметров зашивается в проект автотестов и содержит информацию о min sdk, версию грэдла и другие данные. Они нужны как ключ к основному проекту. То есть эти данные повторяют данные, зашитые в основном проекте.
    <code></li>
    	<li>{</li>
    	<li> "toolsVersions": {</li>
    	<li>   "gradle": "5.6",</li>
    	<li>   "androidGradlePlugin": "3.4.2",</li>
    	<li>   "compileSdk": 28,</li>
    	<li>   "buildTools": "28.0.3",</li>
    	<li>   "minSdk": 18,</li>
    	<li>   "targetSdk": 28,</li>
    	<li>   "kotlin": "1.3.71"</li>
    	<li> }</li>
    	<li>}</li>
    	<li><code>


    Местоположение файла нужно указать в капабилити с ключом appium:espressoBuildConfig
  2. Конфликт ресурсов. Когда смежные библиотеки имеют одни и те же идентификаторы ресурсов — тесты не стартанут. Решение: прописать в файле build_config.xml исключения:
    <code></li>
    	<li>{</li>
    	<li> "toolsVersions": {</li>
    	<li>   "gradle": "5.6",</li>
    	<li>   "androidGradlePlugin": "3.4.2",</li>
    	<li>   "compileSdk": 28,</li>
    	<li>   "buildTools": "28.0.3",</li>
    	<li>   "minSdk": 18,</li>
    	<li>   "targetSdk": 28,</li>
    	<li>   "kotlin": "1.3.71"</li>
    	<li> },</li>
    	<li> "additionalAppDependencies": [</li>
    	<li>   "com.google.android.material:material:1.0.0",</li>
    	<li>   "androidx.lifecycle:lifecycle-extensions:2.1.0"</li>
    	<li> ]</li>
    	<li>}</li>
    	<li><code>
  3. Периодические ошибки вроде “драйвер не стартанул”, особенно при первом его запуске. Тут пока сделать ничего не удалось, приходится страдать.
  4. На момент написания статьи, для тестов с Espresso-driver в проекте используется последняя стабильная версия appium 1.22.0. Android-проект перешёл на использование agp 7 (android gradle plugin) и java 11. И возникла проблема со сборкой appium-espresso-driver в связи с несовместимыми изменениями, которые поломали билд конфиг драйвера. В репозитории appium-espresso-driver уже висит открытый ПР на эту тему, но пока мы ждем когда его смержат.

Несмотря на все минусы тегов в их использовании есть существенный плюс по сравнению с id: теги можно менять во время работы приложения и ни в какие ресурсы прописывать не надо. Однако подружиться с Espresso окончательно так и не удалось, поэтому теги используются крайне редко.



Послесловие и итоги


Про внешние сдк, внедрённые в проект

С сдк вариантов два: либо использовать локаторы в том виде, в котором они уже зашиты разработчиками сторонних библиотек, либо расковыривать библиотеку и переписывать как надо. Нужно ли это вообще? Скорее всего нет.

А есть ли разница, телефон или планшет?

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

А как это помогает?

Автотесты дают нам как минимум — более читаемый код, как максимум — счастливых ручных тестировщиков, которым на релизе нужно проверять значительно меньше кейсов. Например, в последнем релизном прогоне было 183 кейса. 22 упало, 22 скрыто, итого — 140 кейсов на сегодняшний день.

Надеюсь, что наш опыт в поиске уникальных элементов будет вам полезен. С радостью отвечу на вопросы в комментариях! Если же у вас есть свои лайфхаки при поиске той самой большой красной кнопки на экране при написании автотестов — обязательно расскажите о нем.
Источник: https://habr.com/ru/company/ru_mts/blog/596633/


Интересные статьи

Интересные статьи

Кто бы что ни говорил, но я считаю, что изобретение велосипедов — штука полезная. Использование готовых библиотек и фреймворков, конечно, хорошо, но порой стоит их отложить и создать ...
Как широко известно, с 1 января 2017 года наступает три важных события в жизни интернет-магазинов.
Эта статья для тех, кто собирается открыть интернет-магазин, но еще рассматривает варианты и думает по какому пути пойти, заказать разработку магазина в студии, у фрилансера или выбрать облачный серви...
В 1С Битрикс есть специальные сущности под названием “Информационные блоки, сокращенно (инфоблоки)“, я думаю каждый с ними знаком, но не каждый понимает, что это такое и для чего они нужны
Здравствуйте. Я уже давно не пишу на php, но то и дело натыкаюсь на интернет-магазины на системе управления сайтами Битрикс. И я вспоминаю о своих исследованиях. Битрикс не любят примерно так,...