Разделы портала

Онлайн-тренинги

.
Вот почему мы всегда пишем селекторы на XPath
23.11.2021 00:00

Автор: Поздняков Антон Игоревич (Инженер по автоматизированному тестированию), ООО "Хоппер ИТ"

Сегодня хочется поговорить про XPath — мощный и гибкий инструмент для работы с веб-интерфейсами, который при этом почему-то остается не особенно популярным. Статей и мануалов по XPath очень много, и в этом посте я постараюсь рассказать, как мы применяем данный инструмент и почему считаем его более эффективным, чем другие подходы. Если вам знаком термин “селектор”, а тем более — если вы слышали про XPath, добро пожаловать под кат, там много полезного!

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

  • Писать код надо понятно и однотипно, потому что вы — не единственный инженер;

  • Всегда надо предусматривать возможность оперативно переписать селектор;

  • Для целого множества сайтов и версток необходимо обеспечить единый подход;

  • Важно гарантировать однозначность каждого селектора; 

  • И, наконец, каждый селектор должен быть максимальную информативным. 

С учетом всех этих требований, работа инженера-тестировщика становится не такой уж простой, потому что выполнять задачи необходимо с определенным уровнем унификации. И именно поэтому мы полностью отдали предпочтение  XPath (XML Path Language) для написания селекторов. 

Существует мнение (и оно довольно распространенное), что XPath это что-то громоздкое, со сложным синтаксисом. А некоторые также ставят под сомнение скорость поиска по XPath. Например, в одном из популярных курсов обучения по автоматизации тестирования с помощью Selenium, вы можете увидеть вот такие мысли: 

Но наша практика показывает, что это не совсем так…а может быть даже совсем не так. Мы сделали ставку на XPath, потому что наша команда пишет автоматизированные тесты для заказчиков — фактически тысячи тестов. Чтобы увязать их с внедрением систем комплексного мониторинга, об этом я уже писал в прошлом посте. При таких объемах в условиях командной работы, стандартизация подходов является необходимостью, в том числе и для составления селекторов. 

XPath: плюсы и минусы

Начнем с минусов — то есть с того, почему XPath не любят. 

Минус №1. Холивары о скорости работы селекторов на XPath и, например, CSS действительно не затихают. Мы ввязываться в них не будем и не станем утверждать, что тот или иной подход работает быстрее. Но при этом стоит отметить, что, учитывая общее время выполнения UI теста, разница в скорости работы селектора вообще не существенна.

Минус №2. Многие считают селекторы XPath неинформативными. И это мнение обычно формируется после работы с плагинами для браузеров и стандартными средствами браузеров по поиску XPath. Действительно, селектор вида //div[1]/div[2]/ul/li/a не вызывает оптимизма. И мы, кстати, рекомендуем, не пользоваться подобными инструментами.

Минус №3. При всей мощности XPath остаются вопросы, которые он не может решить. Например, на XPath не получится сделать селекторы к псевдоклассам, и содержимому Shadow DOM. Но, как говорится, каждому инструменту — своя сфера применения.

С плюсами XPath все гораздо проще, ведь они очевидны и лежат на поверхности: 

Плюс №1. Возможность поиска по тексту элемента. Первое, что мы встречаем в любом web-приложении — это текст. Текст в том числе располагается на кнопках, ссылках, выпадающих меню. И если свойства таких элементов могут измениться, то текст чаще всего останется прежним. Таким образом, даже изменения верстки никак не повлияют на XPath-селекторы.

Как следствие, через XPath можно искать элементы с изменяемым текстом. Например, если нужно выбрать в календаре “позавчера”, можно прописать дату в явном виде в селекторе XPath. Также благодаря этой функции появляется возможность создавать селекторы для сложных таблиц. Например, это будет полезно, если вам необходимо выбрать ячейку в некой строке, причем строку и столбец можно найти только по тексту, так как номера строки и столбца могут меняться. 

Плюс №2. Наличие встроенных функций, таких как contains(), starts-with(), sibling,  normalize-space() и прочих в совокупности с логическим операторами and, not , or позволяет создавать гибкие и универсальные локаторы.

Все это очень полезно, когда речь заходит о реальной практике. Вот 3 примера, в которых преимущества XPath видны, так сказать, невооруженным взглядом:

Пример №1. Селектор авторских постов в блоге

Берем первый попавшийся html код.

<div id="posts" class="post-list">
  <div id="post1" class="item">
    <div class="title">Как я провел лето</div>
    <img src="./images/summer.png">
  </div>
  <div id="post2" class="item">
    <div class="title second">Ходили купаться</div>
    <img src="./images/bad_dog.jpg">
  </div>
  <div id="post3" class="item">
    <div class="title">С друзьями</div>
    <img src="./images/friends.jpg">
  </div>
</div>

Давайте напишем селектор для  <div class="title second">Ходили купаться</div>

Если делать это через CSS, то селектор будет выглядеть так: .second

Но обратите внимание, что у post1 и post3, нет классов first и third! Чем руководствуются разработчики подобной верстки и какие у них мотивы — мы не узнаем никогда, но с определенной долей вероятности можем утверждать, что скоро класса second тоже не будет и значит наши тесты упадут.

Сохраняя приверженность CSS, можно переписать селектор на: #post2 .title

Да, такой селектор будет жить…но вполне возможно, что тоже не долго.

Мы видим из кода, что речь идет о постах, авторских статьях или блоге. Но что будет, когда автор добавит очередной текст? Ведь логично, что новый пост должен быть первым, и тогда нумерация сдвинется, наш селектор #post2 .title будет ссылаться на пост с заголовком Как я провел лето. А неправильный селектор ещё хуже, чем нерабочий селектор, потому что узнаем мы о неправильном селекторе не сразу, если вообще узнаем.

Тем временем, на XPath селектор может выглядеть следующим образом:

//[normalize-space(.)='Ходили купаться' and contains(@class, 'title')]

Выглядит громоздко. Но сколько плюсов: 

  1. Мы привязали селектор к самому тексту заголовка, который никто не вправе изменять, кроме автора.

  2. normalize-space(.) исключает проблему случайных/лишних пробелов.

  3. Верстальщик может без последствий добавлять/изменять список применяемых классов —  селектор останется рабочим.

И последняя, но очень важная возможность: Представьте, что перед вами задача не написать тест и селекторы, а отредактировать селектор в существующем тесте..., на проде..., срочно, а ещё лучше вчера…

Что проще найти и изменить, например в тысяче строк кода?

Вариант 1: 

Вариант 2:

Для нас очевиден Вариант №2.

Пример №2. Поиск по DOM-дереву

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

Возьмем реальный код

<div class="holder  col-md-8 col-sm-7 col-xs-12 ">
<select data-placeholder="Выбрать..." class="form-control chosen master-field" 
name="field[new_object_assignment_key]" data-error-message="Поле заполнено некорректно" required="required" style="display: none;">
<option value="">Выбрать...</option>
<option value="006002010">«Тропа» здоровья</option>
</select>
  	<div class="chosen-container chosen-container-single chosen-container-active loaded chosen-with-drop" title="" style="width: 505px;">
<a class="chosen-single chosen-default">
<span style="max-width: 390px;">Выбрать...</span>
<div><b></b></div>
</a>
<div class="chosen-drop">
<div class="chosen-search">
<input class="chosen-search-input" type="text" autocomplete="off">
</div>
<ul class="chosen-results" tabindex="5000" style="overflow: hidden; outline: none;">
<li class="active-result result-selected highlighted" data-option-array-index="0">Выбрать...</li>
<li class="active-result" data-option-array-index="1">«Тропа» здоровья</li>            
</ul>
</div>
</div>

Это пример выпадающего меню на странице. Сначала нам надо кликнуть по ссылке (тег <a>), потом заполнить опцию (тег <input>), а затем выбрать ее (тег <li>). Таких меню на странице может быть несколько.

Посмотрите как изящно и однозначно выглядят селекторы XPath в таком случае: 

Клик по меню: 

//select[@name=’field[new_object_assignment_key]’]/..//a

Ввод опции: 

//select[@name=’field[new_object_assignment_key]’]/..//input

Выбор опции:

//select[@name='field[new_object_assignment_key]']/..//li[normalize-space(.)='«Тропа» здоровья']

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

Пример №3. XPath справляется там, где другие не справляются

Таблицы довольно часто встречаются на web страницах. При этом нередко речь заходит о множестве таблиц на одной странице, а каждая из них может содержать сотни строк.

Ниже мы приводим только часть (упрощенного) кода. 

  <table>
	<tr>...</tr>
	<tr>...</tr>
      <tr><td>Конкурсы</td></tr>
      <tr>
        <td>#1</td>        
        <td>Закупка 15</td>
        <td>Описание
          <a href="">ссылка</a>
        </td>
        <td>Примечания
          <a href="">ссылка</a>
        </td>
      </tr>
      <tr><td>Сделки</td></tr>
      <tr>
        <td>#3</td>
        <td>Закупка 4</td>
        <td>На согласовании
          <a href="">ссылка</a>
        </td>
        <td>Примечания
          <a href="">ссылка</a>
        </td>
      </tr>
	<tr>...</tr>
	<tr>...</tr>	
	<tr>...</tr>      
    </table>

Как сделать селектор к ссылке ячейки “На согласовании”? Классы остались где-то наверху, теги все одинаковые, атрибутов нет от слова “совсем”…XPath тут справляется “на ура”, благодаря своим функциям и полнотекстовому поиску: 

//[contains(.,'Сделки')]//td[contains(.,'На согласовании')]//a

Такой селектор легко читается, а значит в него легко внести правки, если это необходимо.

Кстати, тут XPath демонстрирует дополнительную гибкость. Если в примере выше “На согласовании” будет целое множество “Закупок”, то мы сможем добавить номер закупки как ещё одно условие. Например, вот так: 

//*[contains(.,'Сделки')]//tr[contains(.,'Закупка 4')]//td[contains(.,'На согласовании')]//a

Итого: почему именно XPath?

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

К тому же XPath позволяет осуществлять поиск вообще по любому атрибуту элемента. Разработчики зачастую добавляют свои атрибуты ко множеству тегов. Через CSS и стандартными методами фреймворков тестирования их не найти. XPath здесь тоже выручает, например, вот так можно сделать селектор по кастомному атрибуту:

//input[@data-input-type='SNILS’]

Подводя итог, скажу, что мы выбрали для себя XPath как наиболее удобное средство для создания селекторов и рады поделиться своим опытом как с заказчиками, так и с коллегами “по цеху”. Но не любой селектор, написанный на XPath однозначно хорош. В следующем посте я подробно расскажу о “плохих” и “хороших” практиках использования XPath, которые мы определили, набивая свои собственные шишки. А сейчас прошу всех заинтересованных поучаствовать в нашем опросе.

Обсудить в форуме