На сайте hh.ru есть около 100 вакансий, где навык составления XPath важен для работодателя, также в интернетах полно материалов, вроде шпаргалок по составлению локаторов или ворк-шопов на ютубе. Как-то у меня спросили на собеседовании про то, какой из языков построения локаторов использовать лучше XPath vs CSS, и я ответил — лучше использовать тестовые аттрибуты, а если мы их используем то и использовать эти языки необязательно. Скорее всего такой ответ не устроил, но я ответил честно, т.к на предыдущем месте мы старались не использовать XPath для решения этой задачи.
Что это за зверь XPath
XPath (XML Path Language) — это язык путей, использующий синтаксис, отличный от XML, для обеспечения адресации различных частей XML-документа. Существует несколько стандартов данного языка, XPath 3.1 - опубликован в 2017 году (с поддержкой карт, массивов и JSON).
Селекторы XPath обычно называются «xpaths», и один xpath указывает пункт назначения от корня до желаемой конечной точки.
Операторы
Операторы и
/
, //[...]
Оператор объединения узлов,
|
Булевы операторы
and
иor
, и функцияnot()
Арифметические операторы
+
,-
,*
,div
иmod
Операторы сравнения
=
,!=
,<
,>
,<=
, >=
Функции
concat(), substring(), contains(), substring-before() и многое другое
Как это используется в тестах
В тестах мы вынуждены взаимодействовать с элементами HTML-страницы, а для этого нам нужны селекторы, чтобы обращаться к кнопкам, формам ввода, и другим элементам веб-интерфейса.
К примеру, у нас есть простой html-документ, и нам нужно обратиться к элементу а, находящемуся внутри div
<head>
<title>Xpath & CSS</title>
</head>
<body>
<h1>Hello World</h1>
<p>some description text: </p>
<div>
<a class="link" href="https://helloworld.com">example link</a>
</div>
</body>
XPath-selector
/html/body/div[1]/p/a
CSS-selector
body > div > a
В тестах можем обращаться к этому локатору так
WebElement passwordByXPath = driver.findElement(By.xpath("/html/body/div[1]/p/a"));
//или вот так
WebElement passwordByXPath2 = driver.findElement(By.xpath("//a[contains(text(),"example link")]"));
WebElement passwordByCSS = driver.findElement(By.css("body > div > a"));
Почему этим пользуемся и почему это не очень
Предположу что есть несколько причин, по которым этот подход еще используется:
исторически так сложилось
фронтенд не подготовлен для разработки автотестов (разработчики не предусмотрели тестовые локаторы-аттрибуты), выкручиваемся запросами c условиями
Если причиной является отсутствие уникальных тестовых аттрибутов и мы строим локатор, исходя из условий, скорее всего мы создаём нестабильный локатор, в не зависимости от того, насколько правильно он составлен.
Если мы завязались на название класса, то в любой момент он может измениться, например при пересборке веб-пака, а при изменении расположения или при оборачивании в новый tag - изменится путь и локатор будет невалидным.
Для тестов нам нужен стабильный, уникальный локатор, для этого правильным решением будет использовать тестовый аттрибут.
Преимущества использования тестовых аттрибутов
повышается стабильность, т.к не используются условия, функции
снижение вариативности
поддержка автоматической нумерации (для множественных объектов одного типа)
Из минусов — упрощается парсинг, если локатор доступен извне.
Поддержка тестовых атрибутов популярными фреймворками
фреймворк | использование | тестовые аттрибуты |
Playwrigth | page.getByTestId('directions').click(); | data-pw |
Cypress.io | cy.get('[data-cy="submit"]').click(); | data-cy |
WebdriverIO | $('button[data-testid="submit"]') | data-testid |
Если внутри компании не принято решение о кастомном названии для тестовых аттрибутов, то можно использовать data-testid
, которое поддерживается и рекомендуется большинством фреймворков для использования.
В Selenium из коробки отсутствует поддержка тест-аттрибутов, но можно выкрутиться вот так, а в дальнейшем, можно поддержать тестовое название внутри проекта
driver.findElement(By.css('[data-testid="entry-btn"]')
Как расставить тестовые локаторы
Самое простое решение — создать задачу на доработку фронтенда, и закинуть в беклог, а затем при планировании, взять её в спринт, согласно приоритету. Но, если есть свободное время и желание, можно самостоятельно локально поднять и поковырять фронтенд. Обратить внимание на компонент, если он существует.
Вариант первый (и самый простой) — добавление локатора напрямую
import React, { Component } from 'react';
import { Button } from "@material-ui/core";
function ViewIntro({ onOrder }) {
return (
<Button
variant=”contained”
color=”primary”
data-testid=”entry-btn” //просто добавляем аттрибут здесь и проверяем через консполь
onClick={onOrder}>
Order your 🍕
</Button>
);
}
А можно добавить data-testid в сам компонент и при использовании компонента передать значение dataTestId
const Component = ({ variant, color, dataTestId, onOrder }) => (
<Button
variant={variant}
color={color}
data-testid={dataTestId}
onClick={onOrder}>
Order your 🍕
</Button>
);
Итог
строить локаторы, используя все возможности XPath/СSS - на основе названий классов, вложенности, и всего, что может в любой момент измениться — плохо
для автоматизации желательно использовать уникальные тестовые атрибуты
для тестовых атрибутов можно использовать название
data-testid
, которое поддерживается несколькими фреймворкамидля обращения к этим тестовым локаторам можно использовать внутренние методы или реализовать свой собственный, а сами локаторы поместить в привычный PageObject класс
Спасибо за прочтение