XPath активно используется в мире автотестов веб (ибо веб-страница - частный случай XML-документа).
В целом XPath штука несложная, если разобраться с двумя основными концепциями: осями и предикатами.
XPath axes - оси XPath
Оси - это направления поиска относительно текущего элемента. Они помогают разбить документ на несколько множеств и указать, в каком из этих множеств искать нужный элемент.Не все из существующих осей широко применяются в контексте веб-автотестов (например namespace:: не имеет особого смысла). Некоторые оси применяются постоянно, но не всегда это очевидно (self:: или descendant-or-self::).
Список существующих осей:
Чтобы разобраться со смыслом основных осей, рассмотрим выдуманную веб-страницу. Как всякий XML-документ, веб-страницу можно рассматривать как дерево, узлы которого пронумерованы сверху вниз и слева направо.
Чтобы было легче видеть за этим деревом веб-документ, опишем эту же структуру стандартными HTML тегами:
Числа рядом с именами тегов проставлены, чтоб было проще сопоставлять картинки. Эти числа - номера узлов дерева.
Чтобы было нагляднее, в качестве текущего элемента выберем узел №7 и рассмотрим, как мы можем добраться до остальных узлов дерева с помощью осей XPath.
Ось self:: позволяет обратиться к текущему элементу, если это зачем-то нужно.
Краткая нотация этой оси - точка.
У элемента есть предки и потомки.
Предки - элементы, расположенные на пути от корня дерева до текущего элемента. Множество предков лежит на оси ancestor::.
Потомки - элементы, расположенные в дереве ниже текущего элемента. Они лежат на оси descendant::.
В терминах HTML тегов предки - это теги, которые не были закрыты до того места, где был открыт тэг текущего элемента. А потомки - это элементы, расположенные между открывающим и закрывающим тегами текущего элемента.
На картинке ниже показано, какие элементы будут найдены с помощью того или иного XPath. Легко заметить, что к одному и тому же элементы можно обратиться с помощью различных путей.
Кстати, все приведённые пути начинаются точки, то есть с оси self::, чтобы задать текущий элемент в качестве точки отсчёта. Без этой точки поиск начнётся от корня дерева.
Также стоит отметить правило нумерации: элементы оси всегда нумеруются относительно текущего элемента: ближайший элемент имеет номер 1, следующий 2 и так далее. Можно искать среди всех элементов лежащих на оси или указать имя тега, чтобы сузить пространство поиска. Соответствующим образом меняется нумерация: так, td11 - четвёртый среди всех потомков, но третий элемент td среди потомков. Поэтому запросы ./descendant::*[4] и ./descendant::td[3] в данном случае эквивалентны.
Следующие две оси - parent:: и child::, означающие, соответственно, родителя (он всегда ровно один, если речь не о корневом элементе) и детей текущего узла.
У этих осей есть краткие нотации.
Примеры поиска элементов на этих осях:
Идём далее.
Предшествующие (preceding::) и последующие (following::) элементы.
В терминах дерева предшествующие - это элементы, чьи порядковые номера меньше номера текущего элемента, за исключением предков. Последующие - элементы с номерами большими, чем у текущего за исключением потомков.
В терминах HTML предшествующие - это элементы, теги которых были закрыты до открывающего тега текущего элемента. А последующие - те, чьи теги были открыты уже после закрытия тега текущего элемента.
На практике использование этих осей я встречал редко, но на всякий случай вновь примеры доступа к элементам лежащим на этих осях:
Ещё две интересные оси - preceding-sibling:: (предшествующие братья или старшие братья) и following-sibling:: (последующие или младшие братья).
Как следует из названий, на этих осях расположены элементы, имеющие того же родителя, что и текущий элемент, и имеющие меньшие или большие порядковые номера.
Примеры XPath для этих осей:
Кроме перечисленных, есть ещё несколько осей, которые на этих картинках не видны.
Ось атрибутов attribute:: (или @ в краткой нотации) позволяет обратиться к атрибутам элементов: например, атрибуту href элемента а9.
text() позволяет найти текстовые элементы документа (которые, в частности, есть между любыми двумя соседними тегами, даже если там нет явно написанного текста).
node() - узловые элементы документа.
* - узловые и текстовые элементы.
Также есть оси ancestor-or-self:: и descendant-or-self::, смысл которых ясен из названия, и которые редко используются в явном виде.
Зато часто используется запрос /descendant-or-self::node()/ более известный виде краткой нотации //.
На этом про оси всё. Напоследок ещё раз список осей, с краткими нотациями:
Предикаты XPath
Предикаты XPath - это дополнительные условия, помогающий уточнить запрос.
На самом деле в примерах выше постоянно использовался простейший предикат - указание позиции элемента.
Однако пользоваться только позицией было бы не очень удобно, особенно в мире динамических веб-страниц.
Предикаты позволяют указать значения атрибутов искомого элемента.
Пример ниже показывает, какое множество элементов находит тот или иной запрос в HTML коде справа.
Также популярны предикаты работающие текстом внутри элемента: "text()" и ".".
На первый взгляд они выглядят похоже: оба запроса для span на картинке ниже возвращают одно и то же.
Однако если посмотреть на запросы для кнопок - возникают вопросы:
- //button[text()="Click"] не находит ничего,
- //button[.="Click"]] находит нужную кнопку, а
- //button[.="Press"]] вновь ничего не находит.
Причина в том, что предикат text()="something" ищет элемент, у которого есть собственный текстовый узел с таким значением. А .="something" ищет элемент, текстовое представление которого совпадает с указанным значением. При этом текстовое представление элемента учитывает все текстовые узлы внутри элемента - как собственные, так и дочерних элементов. Иными словами, тестовое представление узла - это конкатенция всех текстовых узлов внутри элемента.
Поэтому
- //button[text()="Click"] не находит ничего - ведь "Click" содержится не в собственном текстовом узле кнопки,
- //button[.="Click"]] находит нужную кнопку: текстовое представление кнопки именно "Click"
- //button[.="Press"]] вновь ничего не находит, так помимо "Press" текстовое представление кнопки содержит ещё и два переноса строки.
Кроме того имеется набор функций, позволяющих сравнивать строки, считать элементы и т.д. Подробно останавливаться на этом не буду.
Хороший XPath
Напоследок некоторые мысли, что отличает "хороший" XPath-запрос от "плохого" в контексте веб-тестирования.
Хороший XPath должен быть
- точным - указывать на нужный элемент страницы
- уникальным - указывать в идеале ровно на один элемент (а не просто на первый из массы похожих)
- быстрым - быстро находить элемент на странице
- не хрупким - таким, чтоб не приходилось его менять после каждого изменения вида страницы
- описательным и лаконичным - то есть по возможности коротким, но при этом хорошо, если читая XPath-запрос человек понимает, что этот запрос ищет (да, это немного противоречивые требования).
Все эти критерии ситуативны и требуют применения здравого смысла.
Обычно не возникает проблем с точностью и скоростью поиска.
С другими тремя пунктами сложнее.
Чтоб было понятнее, о чём речь, приведу примеры.
Не уникальный XPath: //span[contains(text(), 'already exists')] - сложно сказать сколько подобных элементов окажется на странице. Хотя, в контексте конкретного теста мы можем точно знать, что такой элемент ровно один.
Хрупкий XPath:
- //span[text()='Open']/../../button[contains(@id, 'actions-button-%s')]
- //span[contains(text(),'%s')]/..//div[@class='panel']/div/span/div/span[3]//span[1]
Лесенки вверх (../../), длинные списки элементов (/div/span/div/span[3]//span[1]) это те места, которые могут легко и неожиданно ломаться при изменении веб-страницы.
Лесенки в большинстве случаев можно устранить, используя вместо них предикаты и вложенные запросы
//button[span[text()='Open']][contains(@id, 'actions-button-%s')]
Не лаконично-описательный XPath:
- //span[@aria-label='%s. Press the Enter key.']/../..
- //p[text()='%s']/../../../td[1]/input
- //td[text()='%s']/following-sibling::td[count(//div[@id='usage_stat']//th[text()='Amount']/preceding-sibling::th)-1]
Кстати, эти не лаконично-описательные XPath'ы одновременно ещё и хрупки. Есть ощущение, что часто эти свойства ходят парой: если сломано одно из них, до и другое скорее всего тоже.
Иногда полезно вообще попробовать использовать вместо XPath другой инструмент. Например, вместо построения сложного запроса ищущего что-то в таблице (
<table>
), может оказаться проще вычитать всю таблицу в хэш-таблицу (aka мапу, словарь) и потом вычитывать нужные данные оттуда.Ссылки
Несколько полезных ссылок, помогающих разобраться с XPath:https://msdn.microsoft.com/ru-ru/library/ms256086(v=vs.120).aspx
http://scraping.pro/5-best-xpath-cheat-sheets-and-quick-references/
http://pragmatictestlabs.com/2016/09/27/mastering-xpath-for-selenium-test-automation/
https://ru.wikipedia.org/wiki/XPath
На этом всё.
Хороших XPath'ов.
Комментариев нет:
Отправить комментарий