|
257 |
|
Введение
Практические примеры по работе с xpath
приводилсь в статье.
Xpath
- активно используется в автоматизации веб-тестирования при написании UI
-тестов.
Данный язык помогает перемещаться по DOM-структуре XML-файла (используется для навигации и поиска внутри xml), а также возвращает элементы (узлы XML-документа), соответствующие запросам.
Язык является РЕГИСТРОЗАВИСИМЫМ!!! Все команды всегда с маленькой буквы!
Пути в xpath
Абсолютные пути
Абсолютный xpath начинается с одного слэша ( /
) и указывает на полный путь из корневого узла (root) к целевому (target). Например, следующее выражение в XPath:
/html/body/div[1]/h1
вернёт нам все элементы типа <h1>
, которые расположены по указанному пути. Из примера ниже мы получим только первый и второй элемент, но не третий:
<html>
<head><head>
<body>
<div>
<h1>Первый элемент</h1>
<h1>Второй элемент</h1>
</div>
<div>
<h1>Третий элемент</h1>
</div>
</body>
<footer/>
</html>
Чтобы получить третий элемент, требуется изменить наш запрос на:
/html/body/div[2]/h1
Обращение к верхнеуровнему элементу
Запрос вида:
/html
Найдёт только элемент <html>
, но не найдёт никакие другие, т. к. верхнеуровневый элемент типа <html>
только один.
Если же мы хотим выйти на какие-либо дочерние элементы с помощью абсолютного пути, то требуется всегда следовать полной структуре файла от самого начала:
/html/head/meta
/html/body/div
Если мы укажем просто /head/meta
, то ничего не найдётся, так как в файле нет верхнеуровнего элемента типа <head>
. Главный предок всех элементов в данном случае - это <html>
, поэтому при использовании абсолютного пути поиск всегда требуется начинать именно с этого элемента.
Относительные пути
Для применения относительного пути используется двойной слэш ( //
). При таком подходе указанный в запросе путь:
- ищется в любом месте документа
- не имеет никакого значения
- где он распологается
- кто у него родитель
Главное - чтобы этот адрес существовал в документе. Напр., запрос вида:
//div
вернёт абсолютно все элементы типа <div>
, найденные в любом месте документа.
Чтобы найти все ссылки, расположенные внутри любого списка, можно воспользоваться синтаксисом вида:
//ul//a
Комбинирование подходов
Для поиска какого-либо элемента чаще используются комбинированные подходы. Изначально вместо главного предка документа мы отталкиваемся от какого-либо элемента, напр., <ul>
//ul
Далее, нам, например, нужны только те ссылки, которые находятся исключительно внутри элементов <div>
, а у элементов <div>
на любом уровне есть родитель / предок типа <ul>
:
//ul//div/a
Тем самым, мы использовали:
//ul
- чтобы найти все элементы типа<ul>
//div
- чтобы найти только те элементы<div>
, у которых есть предок типа<ul>
/a
- чтобы найти только те элементы<a>
, у которых есть родитель типа<div>
Важно понимать, что - добавляя новый элемент в xpath
-путь - мы полностью меняем запрос, а с ним и возвращаемый элемент:
//ul
- вернутся все элементы типа<ul>
//ul//div
- вернутся все элементы типа<div>
, у которых есть предок типа<ul>
//ul//div/a
- вернутся все элементы типа<a>
, у которых есть родитель типа<div>
, при этом у элементаdiv
один из предков обязательно должен быть<ul>
.
Отношение элементов друг к другу
Выше уже упоминались такие понятия как предок
, родитель
, дочерние элементы
(чайлд). Именно они и будут рассмотрены далее.
Родитель
Родитель - это первый вышестоящий элемент от рассматриваемого. Пример:
<html>
<head><head>
<body>
<div id="par_1">
<h1>Первый элемент</h1>
<h2>Второй элемент</h1>
</div>
<div>
<h3>Третий элемент</h1>
</div>
</body>
<footer/>
</html>
У всех трёх элемнтов типа <h*>
родителями являются элементы div
. Только это два разных div
-а, то есть, два разных родителя:
- h1, h2 имеют родителя
<div id="par_1">
- h3 имеет в качестве родителя элемент
<div>
У элементов <div id="par_1">
и <div>
родителем является элемент <body>
.
У элементов <head>
, <body>
, <footer>
родителем является элемент <html>
Предок
Предок - это любые вышестоящие элементы в цепочке до нашего элемента, определяемые по связи типа "родитель". Напр.:
<html>
<head><head>
<body>
<div id="par_1">
<h1>Первый элемент</h1>
<h2>Второй элемент</h1>
</div>
<div>
<h3>Третий элемент</h1>
</div>
</body>
<footer/>
</html>
У элементов <h1>
и <h2>
имеются:
- один родитель (
<div id="par_1">
) - два предка (
<body>
,<html>
)
У элемента <h1>
имеется:
- один родитель (<div>
)
- те же предки, что и у элементов <h1>
и <h2>
Дочерние элементы и наследники
<html>
<head><head>
<body>
<div id="par_1">
<h1>Первый элемент</h1>
<h2>Второй элемент</h1>
</div>
<div>
<h3>Третий элемент</h1>
</div>
</body>
<footer/>
</html>
Дочерними элементами для <html>
являются:
<head>
<body>
<footer>
Если следовать логике здравого смысла, то абсолютно все элементы, находящиеся по структуре ниже <html>
, по сути тоже являются дочерними.
Но - чтобы не путаться - под дочерними будем понимать только "первую очередь" элементов, то есть, только те, которые находятся на один уровень ниже.
Те же элементы - которые имеют "вложенность" более одного уровня - будем называть наследниками. Тем самым у элемента <html>
имеется:
- три дочерних элемента: -
<head>
,<body>
,<footer>
- и много наследников:
<div id="par_1">
,<div>
,<h1>
,<h2>
,<h3>
Альтернативные названия:
дочерний элемент
: чайлд, чайлды, чилдренпотомок
: наследник
Практические примеры поиска
Xpath
имеет мощнейщий синтаксис для поиска элементов, с помощью которого можно создать селектор абсолютно на любой элемент. Поможет в этом совместное использование:
- предикатов
- атрибутов
xpath
-функций- логических операторов
- осей
xpath
- специального синтаксиса для перемещения между элементами
Применение предикатов
Использование атрибутов
Под атрибутами подразумевается любое ключевое слово, хранимое внутри элемента, напр.:
<table width="100%" style="background: #3f4137; padding: 10px 5px 10px 5px">
<div id="previous_pages" onclick="show_page_by_button(-10)" style="text-align: center;">
<label style="font-size: 16px;">
<input id="current_page" type="text" size="3" value="1" style="text-align: center; font-size: 16px;">
<label id="max_page" style="font-size: 16px; color: white">
У первого элемента из примера выше (table
) есть атрибуты width
и style
. У второго - id
, onclick
, style
. И так далее.
Эти атрибуты можно и нужно использовать для более точного определения элемента.
Так, например, если на главной странице сайта https://g-oak.ru
воспользоваться xpath
-ом типа:
//table
То будет найдено 11 таблиц. Чтобы получить доступ к одной, конкретной из них, нужно проанализировать, чем она отличается от других. Таблица, которая нам требуется, единственная из всех содержит значение width="100%"
.
Получается, для обращения к ней будет достаточно использовать запрос типа:
//table[@width="100%"]
тем самым, отфильтровав все остальные таблицы и получив уникальный, тот самы элемент.
Синтаксис обращения к атрибуту элемента выглядит следующим образом:
//элемент[@атрибут="значение атрибута"]
Другие примеры:
//div[@id="previous_pages"]
//input[@id="current_page"]
//label[@id="max_page"]
Поиск по номеру элемента
Другим способом выбора элемента является обращение к элементу по номеру. Но нужно понимать, что такой подход работает, только если элементы находятся на одном уровне. Рассмотрим пример:
<body>
<div>1</div>
<div>
<label value="10">1
<img src="1.png">
</label>
<label id="important">2</label>
</div>
<div>3</div>
Чтобы вернуть только второй label
можно воспользоваться любым из следующих вариантов:
//label[2]
//*/label[2]
//body/*/label[2]
//div/label[2]
и т. д.
Но! Стоит изменить структуру, напр., на такую:
<body>
<div>1</div>
<div>
<label value="10">1
<img src="1.png">
</label>
</div>
<div>
<label id="important">2</label>
</div>
И больше по запросу //label[2]
у нас ничего не найдётся. Дело в том, что данной командой мы говорим:
- верни нам любой второй label
- который находится внутри одного родителя
А таких элементов в этом случае у нас нет, т. к. оба лейбла являются первыми элементами и их можно найти следующими запросами:
//label
//label[1]
Важно помнить, что отсчёт ведётся именно от родителя (внутри одного узла).
Подробнее о предикатах
Рассмотренные выше способы на умном языке называются термином предикаты xpath
.
Они дают дополнительные возможности для поиска элементов:
[1] - указание номера элемента (начинаются с 1)
@ - использовать какой-либо аттрибут элемента
- `>` - больше
- `<` - меньше
- `>=` - больше или равно
- `<=` - меньше или равно
- `=` - равно
- `!=` - не равно
Особое внимание стоит обратить на возможности сравнения. Зачастую используется именно знак равно и забываются остальные варианты.
Чтобы вернуть лейбл из примера выше, мы можем исключить второй (ненужный) лейбл следуюшим образом:
//label[@id!="important"]
Тем самым нам вернётся первый лейбл.
Примеры ниже ни к чему не относятся, просто показывают синтаксис:
//label[@id="important"]
//label[@value>=10]
//label[@value>=10]/img
Использование функций xpath
Чтобы писать более сложные xpath
, также потребуется изучать его функции:
text()
contains()
starts-with()
ends-with()
- не использовать (доступен только в xpath 2.0, не реализован в большинстве браузеров)
Главное в их применении - это запомнить особенности синтаксиса.
text()
Предположим у нас есть элемент вида:
<label id="index_title"># Синтаксис xpath</label>
Да, мы его можем найти по уникальному id
:
//label[@id="index_title"]
Но так как в данной главе мы изучаем функцию text()
, то будем его искать следующим образом:
//label[text()="# Синтаксис xpath"]
Запоминаем, что это функция, поэтому:
- не надо ставить никаких
@
перед ней - после слова
text
идут скобки()
К сожалению, у этой функции имеется своя специфика в поведении. Следующий элемент не удастся найти с помощью ранее указанного шаблона:
<label id="index_title"># Синтаксис xpath
</label>
Причина в разрыве строки между словами xpath </label>
, поэтому - в качестве решения - пользуемся функцией contains()
.
contains()
Функция contains
является более широкой чем text()
и принимает на вход два аргумента:
//элемент[contains(арг1, арг2)]
Запоминаем:
contains()
прописывается внутри квадратных скобок- принимает через запятую два аргумента на вход, где:
- arg_1 - это где ищем текст
- arg_2 - какой текст
- в качестве arg_1
- может выступать как функция (
text()
) - так и атрибут элемента (объявляется через символ
@
)
- может выступать как функция (
- аргументы отделяются друг от друга через запятую
- не надо пытаться писать сюда никаких
=
и пр.
- не надо пытаться писать сюда никаких
contains() + text()
Как уже объяснялось выше, функция text()
не очень хорошо работаем с примером ниже:
<label id="index_title"># Синтаксис xpath
</label>
Но данная проблема легко решается с помощью следующего запроса:
//label[contains(text(), "# Синтаксис xpath")]
contains() + атрибуты
Функция contains()
может использоваться также для поиска части текста в абсолютно любом атрибуте.
Допустим, у нас имеется элемент:
<div id="previous_pages" onclick="show_page_by_button(-10)" style="text-align: center; padding: 2px; border: 1px solid yellow; margin: 2px; border-radius: 5px; background: lightgray; color: rgb(249, 249, 249); display: flex; align-items: center; justify-content: center;">
и мы хотим найти его по части атрибута style
, а именно по тексту background: lightgray
.
Для этого пишем запрос вида:
//div[contains(@style, "background: lightgray")]
Также мы его можем найти и по любому другому атрибуту:
//div[contains(@id, "previous")]
//div[contains(@onclick, "utton(-10)")]
starts-with()
Все моменты, которые необходимо было озвучить, уже упомянуты в главе contains()
. Функция starts-with()
ничем не отличается от contains()
в плане синтаксиса.
Единственная разница состоит в механизме поиска:
- если
contains()
ищет любое совпадение части текста в любом месте - то
starts-with()
ищет элементы, которые начинаются с переданного текста
Использование с функцией text()
<label id="index_title"># Синтаксис xpath
</label>
//label[starts-with(text(), "# Син")]
Использование с атрибутом
`<div id="previous_pages" onclick="show_page_by_button(-10)">`
//div[starts-with(@onclick, "show_pag")]
ends-with()
Доступен только в xpath 2.0, не реализован в большинстве браузеров. В качестве решения можно использовать css-селекторы => span[id$='конец_текста']
Использование логических операторов
В XPath имеются логические операторы and, or и not, для указания нескольких условий поиска и сочетания условий. Они РЕГИСТРОЗАВИСИМЫ, использовать только строчное написание.
and - совпадение двух условий
<li class="article_entry">
//li[contains(@class, "article") and contains(@class, "_entry")]
<div id="previous_page" onclick="show_page_by_button(-1)">
//div[@id="previous_page" and starts-with(@onclick, "show")]
or - совпадение одного из условий
//div[@id="previous_page" or @id="previous_pages"]
//div[contains(@id, "pages") or starts-with(@id, "previous")]
//div[contains(@id, "pa") or contains(@id, "ges") or starts-with(@id, "previous")]
not
Отрицание not
имеет несколько случаев применения, а именно:
- проверка на отсутствие атрибута
- отрицание функции или проверки атрибута
проверка отсутствия атрибута
Элемент, у которого есть атрибута style
:
//div[@style]
Элемент, у которого нет атрибута style
:
//div[not(@style)]
отрицание условия атрибута
Все элементы div
, у которых нет атрибута id
равного "background_main"
//div[not(@id="background_main")]
Все элементы div
, у которых есть атрибут id
, но он не равен "background_main"
//div[@id!="background_main"]
отрицание функции
//li[not(contains(@class, "article"))]
//table[not(starts-with(@id, "table_"))]
Логическое объединение условий
Для объединения нескольких условий их можно заключать в скобки, используя символический синтаксис, показанный далее:
//*[(... and ...) or ...]
//*[((... and ...) or ...) and ...]
//*[not((... and ...) or ...) or ...]
Навигация по DOM-структуре
В этой части главы будут рассмотрены дополнительные способы "переключения" между элементами.
Использование осей xpath
Оси — это база языка xpath. Выделяют следующие оси:
parent
ancestor
ancestor-or-self
child
descendant
following
following-sibling
preceding
preceding-sibling
Ранее уже описывались некоторые из них, как например:
parent
- родительancestor
- предкиchild
- дочерние элементыdescendant
- потомки
Данные оси являются ключевыми словами, использующимися в xpath
-запросах. Они представляют из себя дополнительный механизм поиска элементов и позволяют более гибко переключаться между элементами.
Допустим, нам нужен локатор для определённого элемента, у которого нет никакого уникального атрибута и за него никак не зацепиться. Но при этом у него есть чайлд с уникальным id
. Тогда мы можем написать xpath для чайлда и, отталкиваясь от него, легко выйти на родителя - то есть, изначально искомый нами элемент. Подробнее в примерах ниже.
parent
Ось parent
(родитель) позволяет выбрать родителя какого-либо элемента:
<div id='body'>
<div id='A1'></div>
<div id='A2'>
<div id='B1'></div>
</div>
<div id='A3'></div>
</div>
При работе с осями необходимо учитывать особый синтаксис, а именно:
- использовать одинарный слеш (абсолютный путь) перед названием оси
- использовать двойной знак двоеточия после названия оси
- а также прописывать тип верхнеуровнего элемента
- в данном случае родителем является
div
- если мы не знаем, какой тип имеет родитель, то можно использовать знак
*
- в данном случае родителем является
//div[@id='B1']/parent::div
//div[@id='B1']/parent::*
# => A1
ancestor
Ось ancestor
(предок) позволяет выбрать всех предков какого-либо элемента, включая родителя, так как родитель тоже относится к предкам.
<div id='body'>
<div id='A1'></div>
<div id='A2'>
<div id='B1'></div>
</div>
<div id='A3'></div>
</div>
Предками элемента //div[@id='B1']
являются элементы: <div id='A2'>
, body
//div[@id='B1']/ancestor::*
Также нам ничто не мешает использовать и далее использовать синтаксис xpath для уточнения выбора предка:
//div[@id='B1']/ancestor::*
# div id=body, div id=A2
//div[@id='B1']/ancestor::div
# div id=body, div id=A2
//div[@id='B1']/ancestor::div[@id="body"]
# div id=body
//div[@id='B1']/ancestor::*[@id="body"]
# div id=body
То есть, после применения ancestor
нам доступны все предки и мы можем выбрать из них конкретный элемент (и только из них).
ancestor-or-self
Пример синтаксиса:
//div[@id='B1']/ancestor-or-self::*
# div id=body, div id=A2, div id=B1
Делает то же самое, что и ancestor
:
- включает родителя
- включает всех предков
с той лишь разницей, что "в цепочку предков" добавляется и сам искомый элемент. В примере выше - это <div id='B1'>
.
child
<div id='body'>
<div id='A1'></div>
<div id='A2'>
<div id='B1'></div>
</div>
<div id='A3'></div>
</div>
Child
(дочерний) описывался ранее в статье. Ось child
позволяет выбрать только все ПЕРВЫЕ дочерние элементы искомого узла (чайлды чайлдов недоступны).
//div[@id='body']/child::*
# => A1, A2, A3
descendant
Ось descendant
(потомок) целиком возвращает абсолютно все вложенные элементы всех своих чайлдов абсолютно на всех уровнях.
<div id='body'>
<div id='A1'></div>
<div id='A2'>
<div id='B1'></div>
</div>
<div id='A3'></div>
</div>
Потомками элемента div id='body'
//div[@id='body']/descendant::*
являются все вложенные в него элементы: A1, A2, B1, A3
Выбор конкретного потомка из всех потомков:
//div[@id='body']/descendant::div[@id="A2"]
A2
Проверка структуры кода
Следующие оси возвращают результаты в зависимости от структуры дерева:
following
following-sibling
preceding
preceding-sibling
Принцип их работы заключается в проверке, выше или ниже в структуре дерева / кода страницы находится элемент.
following
Ось following
(следующий) возвращает все элементы, находящиеся ниже искомого по структуре дерева, исключая своих потомков, но включая всех чужих потомков. Поиск осуществляется по всему документу, а не только по текущей ноде.
<div id='body'>
<div id='A1'></div>
<div id='A2'>
<div id='B1'></div>
</div>
<div id='A3'></div>
</div>
Элемент A1
находится выше всех (в ноде <div id='body'>
):
//div[@id="A1"]/following::*
Поэтому в качестве результата вернутся все нижестоящие элементы: A2
, B1
(потомок A2), A3
.
Если же проделать то же самое применительно к A2
:
//div[@id="A2"]/following::*
То вернётся только элемент A3
. Элемент B1 хоть и находится ниже по коду, но он является потомком искомого элемента, поэтому не учитывается.
Если применить эту ось к элементу A3
:
//div[@id="A3"]/following::*
то не вернётся ничего, т. к. это самый нижний элемент, далее по коду ничего нет.
preceding
Ось preceding
(предшествующий) является противоположностью оси following
, то есть меняется направление поиска.
//div[@id="A3"]/preceding::*
Вернёт узлы: B1
, A2
, A1
, так как поиск идёт от искомого элементу вверх по структуре документа.
following-sibling
Ось following-sibling
(следующий братский) возвращает все элементы, находящиеся ниже искомого по структуре дерева:
- исключаются абсолютно все потомки
- возвращаются только "братские" элементы
Под "братскими" понимаются элементы, которые находятся в той же ноде, то есть, на том же уровне вложенности.
<div id='body'>
<div id='A1'></div>
<div id='A2'>
<div id='B1'></div>
</div>
<div id='A3'></div>
</div>
На запрос вида:
//div[@id="A1"]/following-sibling::*
результатом будут только элементы: A2
, A3
.
preceding-sibling
Ось preceding-sibling
(предшествующий братский) возвращает все элементы, находящиеся выше искомого по структуре дерева:
- исключаются абсолютно все потомки
- возвращаются только "братские" элементы
Под "братскими" понимаются элементы, которые находятся в той же ноде, то есть, на том же уровне вложенности.
//div[@id='A3']/preceding-sibling*
# => A2, A1
Прочие способы навигации
Помимо ранее упомянутых возможностей перемещения по структуре дерева:
- абсолютный путь (
/
) - относительный путь (
//
) - оси
xpath
имеются и другие возможности, а именно:
*
- любой элемента/..
,/../.
,/.././*
- перейти на уровень выше (обращение к родителю).//
- поиск по контексту текущего узла
Примеры применения:
//*[@class="article_entry"]/.././li[2]
Находим любой элемент класса "article_entry", поднимаемся к его родителю, от родителя берём второй элемент li
. То же самое можно реализовать с помощью оси parent
:
//*[@class="article_entry"]/parent::*/li[2]
Отобразить все дочерние элементы только типа li
, внутри списка:
//ul[@id="list-posts"]/./li
//ul[@id="list-posts"]/li
//ul[@id="list-posts"]/child::li
//li/parent::*[@id="ul_mainmenu"]/li
Вернуть не только дочерние элементы, но и всех потомков только типа li
, внутри списка:
//ul[@id="list-posts"]/.//li
//ul[@id="list-posts"]//li
//ul[@id="list-posts"]//child::li
Выводы
На примерах выше было показано, как широк синтаксис xpath
. Есть множество разных способов - лёгких и простых - как вернуть узел или узлы документа. Преимущество xpath
перед тем же css
состоит в возможности перемещаться по структуре документа.
Используя CSS, можно идти только в глубину (без возможности обратиться к родительским элементам).