Привет, Хабр! Сегодня хочется поговорить про 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')]
Выглядит громоздко. Но сколько плюсов:
Мы привязали селектор к самому тексту заголовка, который никто не вправе изменять, кроме автора.
normalize-space(.) исключает проблему случайных/лишних пробелов.
Верстальщик может без последствий добавлять/изменять список применяемых классов — селектор останется рабочим.
И последняя, но очень важная возможность: Представьте, что перед вами задача не написать тест и селекторы, а отредактировать селектор в существующем тесте..., на проде..., срочно, а ещё лучше вчера…
Что проще найти и изменить, например в тысяче строк кода?
Вариант 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, которые мы определили, набивая свои собственные шишки. А сейчас прошу всех заинтересованных поучаствовать в нашем опросе.