Привет! Меня зовут Иван, я руковожу горизонталью автоматизации тестирования в Skyeng. Часть моей работы — обучать ручных тестировщиков ремеслу автоматизации. И тема с поиском локаторов, по моему опыту, самая тяжкая для изучения. Здесь куча нюансов, которые надо учитывать. Но стоит разобраться, и локаторы начинают бросаться в глаза сами. Хороший автоматизатор должен идеально уметь находить читабельные и краткие локаторы на странице. Об этом и пойдет речь ниже.
Наливаем чай-кофе и погнали!
Что такое локатор
Локатор — обычный текст, которой идентифицирует себя как элемент DOM’а страницы. Простым языком: с помощью локатора на странице можно найти элементы. В случае CSS — локатор включает в себя набор уникальных атрибутов элемента, а в случае XPath — это путь по DOM’у к элементу.
Если вы изучали CSS ранее, то в конструкции ниже p будет являться локатором элемента, также и атрибут color: red может являться его локатором. Атрибут элемента это всё, что идёт после тега. Например, в теге <p class=”element” id=”value”> атрибутами являются class и id.
p: {
color: red;
}
Сразу оговорка по терминологии, локатор = селектор.
Локатор — это название селектора на русском. Иногда встречаю в интернете, что селектор относится только к CSS, но это не совсем так. XPath-локатор тоже может быть, просто означает он путь к элементу в DOM’е. Давайте похоливарим в комментах, чем же всё-таки локатор отличается от селектора
DOM страницы — это HTML-код, написанный человеком или сгенерированный фреймворком, который преобразуется браузером в DOM. То есть набор объектов, где каждый объект — это HTML-тег.
Есть очень много видов локаторов, но чаще всего в работе применяется лишь часть из них. Их можно искать по следующим видам:
-
имя элемента
-
id
-
классы
-
кастомные атрибуты
-
родители и дети элементов
-
ссылки
-
и так далее.
Полное строение элемента
Элемент состоит из имени, то есть самого HTML-тега. Например, div, span, input, button и другие. Внутри него перечислены атрибуты, которые отвечают за все возможные свойства элемента. Например, цвет, размер, действие, которое будет происходить по клику на элемент.
У элемента может быть родитель и ребёнок. Родитель может быть один, а детей может быть несколько. Если детей несколько, то они являются соседями и каждый из них образует свою ось. 1 ребёнок = 1 ось со своими особенностями и своими вложенными элементами. А — родитель, B D E F W X Y — дети A. У каждого элемента есть свои дети, свои дальнейшие ветки, это и называется оси.
Поиск локаторов в браузере
Для поиска элементов в DOM’е страницы нужны средства разработчиков в браузере. Рассмотрим их на примере Chrome. Они же называются DevTools (F12). Нас интересует вкладка Elements, именно там находятся все элементы. Чтобы найти локатор в поле Elements, нужно нажать Ctrl+F. Внизу появится небольшое поле поиска, с ним мы будем работать всё время.
Давайте попробуем найти элемент по названию HTML-тега. Искать просто: в строке поиска вводим название тега. Скорее всего этот локатор элемента будет не уникальным и по его значению найдутся много элементов. Для тестов важно, чтобы был только один элемент для взаимодействия. Если одному локатору будут соответствовать несколько элементов, то тест или будет взаимодействовать с первым из них, или просто упадёт с ошибкой. Элементы можно искать не только с помощью тегов (p, span, div и т.д.), но и с помощью атрибутов тега. Например, color=”red” и class=”button”. Подробнее об этом чуть ниже.
Микро-задание: попробуй открыть DevTools на этой страничке (F12) и найти (Ctrl + F) количество элементов с тегом button.
P.S. поздравляю, ты уже написал свой первый локатор! Дальше — больше
Уникальные локаторы
Где будем практиковаться? https://eu.battle.net/login/ru/ — простая и понятная форма авторизации.
Рассмотрим поиск на примере формы авторизации и регистрации. В коде страницы есть 2 поля («Почта» и «Пароль») и кнопка «Авторизация». Сравним, по каким атрибутам можно найти локатор и определим уникальные атрибуты.
Подробно разберём, как можно найти локатор поля Почта:
Разберём, как можно найти локатор поля Пароль:
Разберём, как можно найти локатор поля Авторизация:
Начнём с разбора не уникальных локаторов. Если по локатору находятся 2 и более элементов на HTML-странице, такой локатор можно назвать неуникальным. Тест при обнаружении большого количества элементов по данному локатору упадёт или возьмёт первый. Ненадежно, точно не наш бро.
Уникальный, но non-suitable локатор. Если мы в DevTools введем вышеуказанные названия, то найдется элемент. И здесь мы опускаемся до следующего уровня написания локаторов — уровня понятности, читаемости и надёжности локатора.
-
title=»Электронная почта или телефон» — считается плохим паттерном писать локаторы с русским текстом. Тем более в примере текст в title еще и длинный, это визуально громоздко. На текст завязываться можно в крайнем случае, но нужно быть готовым к тому, что тексты часто меняются, любая правка может сломать автотесты.
-
title=»Пароль» — аналогично ^
-
type=»text» — представь, ты открываешь среду разработки и видишь локатор “тип=текст”. Совсем не ясно, к какому элементу относится локатор. Со смысловой точки зрения, это неудачный локатор, потому что он не передаёт смысл локатора.
-
type=»password» — этот атрибут говорит о том, что у поля тип «password» и все символы, которые мы вводим заменяются на звёздочки/точки. При добавлении еще одного поля с type=”password” (например, поле «Подтвердите пароль») локатор сразу станет неактуальным. Стараемся думать наперёд.
Уникальные локаторы. Они найдут только один элемент, они осмысленные, иногда читабельные и краткие. Как раз уникальные атрибуты — это class, id, name и подобные. Они точно наши бро!
Небольшой итог
Хороший локатор — краткий, читабельный и осмысленный. Например, у поля «Пароль» хорошо иметь в локаторе слово password.
Возникает вопрос, почему class=»btn-block btn btn-primary submit-button btn-block» был вынесен в категорию уникальных? Такие локаторы встречаются повсеместно, и именно их мы берём за основу и приводим к красивому виду.
Поиск элементов с помощью CSS
id и class — самые важные атрибуты, с помощью которых мы будем искать бóльшую часть элементов на странице. Есть очень много тонкостей по работе с ними, постараемся рассмотреть все из них.
Кнопка «Авторизация» имеет несколько классов в одном:
-
btn-block
-
btn
-
btn-primary
-
submit-button
-
btn-block
Каждый из этих классов определяет свой визуал кнопки. Например, btn-primary определяет цвет кнопки, submit-button увеличивает её размер (это лишь догадки, основное значение знают только Blizzard). Несколько классов внутри атрибута class разделяются пробелом.
Наличие более одного класса внутри атрибута говорит о том, что он комбинированный. Бывают и комбинированные атрибуты кроме классов. Но классы необязательно будут уникальны для одного элемента. В данном случае у кнопки «Авторизация» такие атрибуты:
class="btn-block btn btn-primary submit-button btn-block"
Но если добавить туда кнопку «Регистрация», то может отличаться лишь один класс. Например, он будет выглядеть следующим образом:
class="btn-block btn btn-primary registration-button btn-block"
Сразу заметно, что отличается всего лишь один класс — submit-button сменился на registration-button. Остальные свойства могут иметь и другие кнопки.
Читабельность локатора
Допустим, мы ищем элемент по полному классу. Это хороший и действенный способ. Почти всегда элемент будет уникальным, но очень нечитабельным и громоздким, как в случае с кнопкой «Авторизация».
class с помощью CSS можно записать следующим образом:
-
.locator (точка — сокращенная запись class’а)
-
или выделяем название и значение класса в квадратные скобочки: [class=”value”]
Полный класс элемента кнопки «Авторизация» состоит из 5 классов: btn-block btn btn-primary submit-button btn-block, а выглядеть полный локатор будет так:
[class=”btn-block btn btn-primary submit-button btn-block”]
Разделение происходит с помощью пробела внутри. Для класса его сокращенной формой является точка, поэтому можно записать локатор так:
btn-block.btn.btn-primary.submit-button.btn-block
Да, стало короче, но всё равно есть смысловая перегрузка. Сокращаем дальше.
Отдельно здесь стоит добавить про поиск по подстроке. Запись [class=”локатор”] ищет только всю строку класса элемента. Если мы напишем [class=”btn-block”] или любой другой класс, то кнопка «Авторизация» не будет найдена. Но если мы запишем локатор полностью [class=”btn-block btn btn-primary submit-button btn-block”], то кнопка найдётся.
Из данной ситуации помогает найти выход символ звёздочки. Он ищет ПОДстроку в строке, то есть часть локатора может найти элемент.
Краткость локатора
Про подстроку
Можно почитать на википедии, там приведён доступный пример для общего понимания поиска по подстроке. Также поиск по подстроке можно сравнить с методом includes из JS
Локатор кнопки«Авторизация» [class=”btn-block btn btn-primary submit-button btn-block”] можно записать следующим образом:
-
[class*=”btn-block”]
-
[class*=”submit-button”]
-
[class*=”btn-block btn”]
-
[class*=”btn btn-primary”]
-
[class*=”primary submit”] (конец одного класса и начала другого, но только в том случае, если они написаны подряд, друг за другом)
-
можно даже сократить название подкласса: не длинное submit-button, а просто submit, например, [class*=”submit”]. Можно даже сократить слово submit — [class*=”sub”].
Важно понимать, это будет работать, если классы идут только последовательно. Если мы укажем [class*=”btn-block submit-button”], то локатор работать не будет, потому что между btn-block и submit-button идут несколько классов: btn и btn-primary. Но это можно обойти, разделив локатор на 2 разных. Например, 2 класса слитно — [class*=”btn-block”][class*=”submit-button”]. Это работает и часто пригождается, когда нужно уточнить, в каком именно элементе мы ищем определенный класс.
Также можно комбинировать краткую запись с помощью точки и тега элемента:
-
.submit-button = [class*=”submit-button”]
-
.btn = [class*=”btn”]
-
.btn-block = [class*=”btn-block”]
-
button[class*=”submit-button”] = button.submit-button
-
button[class*=”btn”] = button.btn
-
button[class*=”btn”][class*=“submit-button”] = button.btn.submit-button
-
button[class*=”submit”]
Краткую запись (через точку) предпочтительнее использовать, чем полную (в квадратных скобках).
Лаконичность локатора
Мы можем определить кнопку «Авторизация» по классу submit-button. Это не самый лаконичный локатор, но дословно означает действие отправки данных на сервер с формы авторизации. Но что делать, если у кнопки нет контекста? Например, классы кнопки Авторизации будут выглядеть так: [class=”btn-block btn btn-primary btn-block”]. Если нет контекста из слова submit (отправка), то можно очень быстро потеряться и сразу не ясно, к какому элементу относится этот локатор. В данном случае нам поможет название текущего элемента или его родителя.
Для наглядности рассмотрим весь блок с кнопкой «Авторизация».
Как вариант — к локатору можно добавить сам тег button. Например, button[class*=”btn”] (сократил класс для наглядности). В таком случае можно взять тег или класс родителя за основу, а именно div или [class=»control-group submit no-cancel»]. Если нужно указать родителя, то эта связь пишется через пробел. Через пробел можно обращаться на любой уровень вложенности, например, из form сразу прыгнуть к button. Полный путь будет выглядеть так: form div button.
С полученными знаниями можно расширить пул локаторов:
-
form button
-
form [type=”submit”]
-
#password-form #submit (решётка — сокращённая форма id, точка — сокращённая форма class)
-
и еще много-много локаторов, которые можно найти комбинаторикой, главное, чтобы по итогу локатор выглядел кратко и лаконично, передавал суть элемента
А как с ID
С ID работает всё точно также, только краткая запись ID — это решётка, например, <form id=”password-form”> можно записать как form#password-form, по такому же принципу, как и с классом
Поиск по кастомным атрибутам
Кастомные атрибуты тоже заслуживают упоминания. У элемента могут быть не только классы и айдишники, но и еще бесконечно множество атрибутов. В исключительных случаях можно искать элементы по этим атрибутам, но только в случае их приличного вида. Например, в случае кнопки «Авторизация» указаны несколько необычных атрибутов, которые вряд ли можно использовать за основу для её поиска:
-
data-loading-text
-
tabindex=»0″
Очень хорошей практикой на проекте является обвешивание интерактивных элементов кастомным атрибутом data-qa или data-qa-id. Например, <button id=”css-1232” data-qa=”login-button”>. Если поменяют локатор, то этот атрибут останется и тесты будут стабильными долгое время. Добавлять эти атрибуты могут фронтенд-разработчики или автоматизаторы, если имеют доступ к коду фронтенда и возможность пушить в него правки.
Локаторы можно и нужно комбинировать! Элементы, состоящие из нескольких классов, айди и других атрибутов, можно объединять в один локатор. Например, возьмем элемент формы, который находится выше кнопки «Авторизация»: form#password-form[method=”post”][class*=”username”]
Итоги поиска локаторов с помощью CSS
-
классы и id можно писать сокращенно с помощью точки и решетки
-
<button class=”login”>: .login = [class=”login”] = [class*=”log”] = button.login = button[class=”login”]
-
<button id=”size”>: #size = [id=”size”] = [id*=”ze”] = button#size = button[id=”size”]
-
всё, что не class, и не id в сокращённом виде пишем в [] (квадратных скобках), например, [name=”phone”], [data-qa-id=”regButton”]
-
если тег лежит внутри другого тега, то переходим к нему через пробел (независимо от степени вложенности), например, <span> -> <button> -> <a> = span a = button a = span button a
Поиск элементов с помощью XPath
XPath в корне отличается от CSS как идеей, так и реализацией. XPath — это полноценный язык для поиска элементов в дереве, причём неважно каком, будь это XML или XHTML. Можно использовать XPath в веб-страницах, нативной мобильной вёрстке и других инструментах.
Я изучал XPath больше месяца с нуля. Проблема была в том, что я никак не понимал принцип его работы — мы ходим от элемента к элементу, но не ясно, как это происходит, как писать красивые пути, какие преимущества у такого подхода. Неделями изучал документацию, статьи на блогах (к сожалению, тогда еще не было человекопонятных статей на Хабре) и видео в ютубе. Мне очень помогло одно видео, где автор объяснял базовые принципы XPath, после чего меня осенило и в голове сложилась картинка. Поэтому хочу поделиться с вами этой информацией, чтобы сократить время на изучение тонны материала. Изучение XPath самостоятельно полезно, но я бы с огромным удовольствием потратил полтора месяца на вещи поважнее.
Предположим, у нас есть следующая структура документа:
<div class="popup">
<div id="payment-popup">
<button name="regButton">
<span href="/doReg">Кнопка</span>
</button>
</div>
</div>
XPath — это путь от элемента к элементу. Можно представить, что структура тегов — это дерево каталогов, как в любой ОС. Например, в данном случае теги можно представить в виде папок: div -> div -> button -> span. В терминале по ним можно переключаться через команду cd, а именно: cd div/div/button/span
div/div/button/span — это и есть путь к элементу с помощью XPath, только первый элемент ищут по всему дереву элементов, поэтому пишут // в начале строки. В данном случае это будет выглядеть так: //div/div/button/span. 2 слэша можно использовать не только в начале — они обозначают то, что мы ищем элемент где-то внутри. Например, //div//span — элемент будет найден, мы пропустили второй div и button.
Главная отличительная особенность XPath — возможность проходить не только от родителя к детям, но и от детей к родителям. Например, есть структура:
<div class=”popup”>
<div id=”payment-popup”>
<button name=”regButton”>
<span href=”/doReg” />
</button>
<button name=”loginButton”>
<span href=”/doLogin” />
</button>
</div>
</div>
Мы можем перейти от кнопки doLogin в кнопку doReg вот так:
//*[@href=”/doLogin”]/../..//*[@href=”/doReg”]
Чтобы перейти на уровень выше, как и терминале ОС, нужно написать 2 точки, как показано в примере. С помощью 2 точек мы поднимаемся с уровня span сначала до button, а с button до общего div.
Главный вопрос, который может возникнуть, а где это может пригодиться? Практически всюду, где есть одинаковые блоки, которые отличаются по какому-то одному признаку. Возьмем страницу RDR2 в Epic Games. На середине страницы сейчас перечислены 3 издания:
В DevTools отчётливо видно, что блоки идентичные. Отличия только в названии издания, описании и цене.
Есть задача: нажмите на кнопку «Купить сейчас» у издания Red Dead Online. Для этого надо завязаться на текст издания, подняться до первого общего элемента у названия издания и кнопки и опуститься до кнопки «Купить сейчас».
//*[contains(text(), “Red Dead Online”)]/ancestor::*[contains(@data-component, "OfferCard")]//*[contains(@data-component, "Purchase")]
Лайфхак: как найти первый общий элемент у двух элементов?
Нажимаем на любом элементе ПКМ -> Посмотреть код, открывается вкладка Elements. Наводим курсором на текущий элемент и он выделяется синим цветом. Просто тащим курсор наверх, пока визуально не найдём элемент, который объединяет 2 элемента — в нашем случае текст и кнопку «Купить сейчас».
В XPath, как и в CSS, можно искать по элементам и по атрибутам в элементе. Например:
<div class=”popup”>
<div id=”payment-popup”>
<button name=”regButton”>
<span href=”/doReg” />
</button>
<button name=”loginButton”>
<span href=”/doLogin” />
</button>
</div>
</div>
Можно найти кнопку регистрации:
-
//*[@href=”/doReg”] или //span[@href=”/doReg”]
-
//*[@name=”regButton”] или //button[@name=”regButton”]
Как мы можем заметить — звёздочка заменяет название элемента. Где стоит звёздочка, означает, что элемент может называться как угодно. Главное, чтобы внутри него был заданный атрибут. Если мы хотим указать конкретный элемент, то подставляем его вместо звёздочки. Например, путь //span[@href=”/doReg”] — сразу говорит нам, что в элементе span мы ищем @href=”/doReg”, но если нам не важен элемент, то тогда span заменяем на звёздочку //*[@href=”/doReg”].
Атрибуты всегда пишутся со знаком @ в начале, это тоже особенность языка.
Еще следует упомянуть переходы по смежным осям. В примере выше есть 2 разные оси — 2 button: элементы одинаковые, но отвечают за разные кнопки. Это можно сделать с помощью зарезервированных слов: following-sibling и preceding-sibling.
Например, нам нужно достать кнопку Войти, зная кнопку Регистрация: //*[@name=”regButton”]/following-sibling::*[@name=”loginButton”]. Если нужно найти кнопку Регистрации зная кнопку Войти, то делается это точно также, только ищем в осях, идущих до кнопки Регистрации: //*[@name=”loginButton”]/preceding-sibling::*[@name=”regButton”]. Переходы между осями или дереву (вверх-вниз) всегда происходит через 2 точки, если мы пишем полное название направления, например, following-sibling::, ancestor::
Не всегда есть возможность искать элементы по полному названию класса, так как оно может являться достаточно большим и нечитабельным. В CSS мы это делали с помощью символа звёздочки. Здесь звёздочку заменяет слово contains и работает точно также, как и в CSS. Например, ищем кнопку Войти: //*[contains(@name, “Login”)]. Как мы видим, contains — это что-то вроде функции в XPath. 1 параметр — атрибут, в котором ищем часть текста, 2 — сам текст.
Последней функцией, которую мы рассмотрим, будет text(). Она позволяет искать элемент по тексту, который в нём находится. Например, есть HTML-разметка:
<button>
<span>Кнопка Войти</span>
</button>
<button>
<span>Кнопка Регистрация</span>
</button>
Чтобы найти текст по точному совпадению, нужно писать следующий путь: //*[text()=”Кнопка Войти”]. Но если мы захотим искать по 1 слову, то на помощь приходит комбинация со словом contains, а именно: //*[contains(text(), “Войти”)].
Коротко про «Гибкие локаторы»
Термин «гибкий локатор» применяется к поиску локаторов через CSS и с XPath. Называется он гибким, потому что независимо от текста внутри — локатор не изменится. Для примера снова возьмём страничку с игрой RDR2. На ней есть 3 издания. Сами локаторы не меняются, меняется только текст (название, описание, цена). Общий шаблон локатора будет выглядеть так: //*[contains(text(), “Название издания”)]/ancestor::*[contains(@data-component, «OfferCard»)]//*[contains(@data-component, «Purchase»)]. Текст уже можем в него передавать любой, какой захотим. Так вот именно этот локатор будет называться гибким — его тело остаётся неизменным, а меняются лишь параметры внутри него. В автоматизации мы очень часто пользуемся гибкими локаторами.
Выводы
Мы разобрали 2 основных способа поиска элементов на странице, с помощью CSS и XPath. Небольшое сравнение этих методов:
Плюсы CSS |
Минусы CSS |
— краткий — читабельный — простой для освоения и полностью граничит с изучением базового CSS — что-то вроде мифа — он работает быстрее, то есть быстрее ищет элемент на странице, но на фоне мощности современных процессоров эта разница во времени неощутима и составляет пару миллисекунд |
— может переходить только от родителя к ребёнку, но не наоборот — вверх подниматься нельзя — более ограниченный набор функций для поиска элементов, например, нельзя искать элемент по тексту, который в нём находится — CSS заточен только под веб-страницы |
Плюсы XPath |
Минусы XPath |
— полноценный язык для поиска элементов не только в вебе, но и в других средах и документах — позволяет перемещаться по дереву вниз и вверх — гибко работает с осями элементов — есть очень много функций, которые помогают в поиске локаторов, например, поиску по тексту в элементе или аналог normalize-space, который убирает пробелы у строки по бокам |
— громоздкий — нечитабельный — сложен в освоении — работает дольше, чем поиск по CSS, хоть и незначительно |
В тестах лучше использовать CSS, но это не всегда реально. Именно поэтому в таких случаях приходит на помощь XPath.
Полезные ссылки
CSS:
-
https://flukeout.github.io/ — практика в поиске локаторов.
-
https://code.tutsplus.com/ru/tutorials/the-30-css-selectors-you-must-memorize—net-16048 — полезно узнать про различные виды селекторов. Мы используем не все, но всегда бывает ситуация, когда раз в жизни придётся использовать тот или иной локатор.
-
https://appletree.or.kr/quick_reference_cards/CSS/CSS%20selectors%20cheatsheet.pdf — локаторы наглядно.
-
https://learn.javascript.ru/css-selectors — оформление в виде документации.
XPath:
-
https://topswagcode.com/xpath/ — практика в поиске локаторов.
-
https://www.w3schools.com/xml/xpath_nodes.asp — подробнее про ноды.
-
https://www.w3schools.com/xml/xpath_syntax.asp — синтаксис.
-
https://www.w3schools.com/xml/xpath_axes.asp — оси.
-
https://soltau.ru/index.php/themes/dev/item/413-kratkoe-rukovodstvo-po-xpath — более подробная информация с примерами на русском.
CSS Locators for Web Testing — a tutorial
XPath is widely used in web testing.
However, experience shows us that XPath selectors are in many (if not most) most cases the worst option when it comes to readability and maintainability.
In this tutorial, you will learn how to use CSS to write more readable and more robust WebDriver locators.
Why should we (mostly) prefer CSS over XPath when choosing a locator strategy?
The most important reason is maintainability.
CSS locators are more concise and easier to read (once you are familiar with the CSS language).
It is generally easier to make robust, flexible selectors using CSS (the equivalent selectors in XPath will be longer and harder to read).
In addition, CSS is natively supported by all browsers, which can lead to performance improvements in some situations.
Locating elements in Selenium
Before we look at CSS in detail, here is a brief recap on the various ways we can locate elements on a page in our WebDriver tests.
FindBy annotations
One common approach is to use the @FindBy
annotations inside a page object. If you are using XPath locators, you might see code like the following:
@FindBy(xpath = "//input[@id='firstName']") private WebElement firstNameField; public void enterFirstName(String firstName) { firstNameField.sendKeys(firstName); }
Lean Page Objects and Action Classes
Many more experienced teams find that Page Objects often become large and unwieldy, and prefer to apply a stronger separation of concerns between locating the elements on a page, and interacting with them. This approach is known as Lean Page Objects. In Lean Page Objects, or when using the Screenplay pattern, we locate elements using a By
class or even a simple String:
public static final By FIRST_NAME = By.xpath("//input[@id='firstName']");
Or
public static final String SURNAME = "//input[@id='surname']";
These locators are used in methods that are defined in Action Classes or Screenplay tasks, e.g.
public void enterUserName(UserDetails newUser) { $(FIRST_NAME).sendKeys(newUser.getFirstName()); $(SURNAME).sendKeys(newUser.getLastName()); }
Finding collections
We often need to retrieve collections of related objects, rather than a specific value.
For example, suppose we needed to read the list of available sizes for an item displayed on the screen.
@FindBy(xpath = "//div[@class='product-details']//select[@name='size']/option") private List<WebElement> sizeOptions; ... public List<String> getAvailableSizes() { return sizeOptions.stream() .map(WebElementFacade::getText) .collect(Collectors.toList()); }
Alternatively, using Action Classes in Serenity, it is common to locate the collection of elements and convert them into a more usable format on-the-fly:
public static final String PRODUCT_SIZES = "//div[@class='product-details']//select[@name='size']/option"; ... public List<String> getAvailableSizes() { return findAll(CART_ITEMS).stream() .map(WebElementFacade::getText) .collect(Collectors.toList()); }
All of these approaches can benefit from using CSS locators rather than XPath. In the next section, we will learn how to locate elements on a page using CSS.
CSS Locators
Locating elements by ID
Identifiers are generally the most reliable way to locate an element on a page.
In Selenium, we can use the By.id()
locator to locate an element using the identifier.
However there are times when using a CSS selector is also convenient.
In XPath, we can locate an element with a given ID using the @id
attribute, like this:
In CSS, we can use the ‘#’ symbol to locate an input
element with a given ID:
However since the ID uniquely identifies an element, we can drop the input
tag name, so our CSS expression becomes the following:
Locating elements by CSS Class
In well designed web applications, CSS classes are not only used to define the graphical styling, but also to describe the elements on the page. For example, in the following code, CSS classes on the various elements help identify the meaning of each piece of information.
<div id="item-prices"> <ul> <li>Blue shirt: $<span class="price right-align item-cost">60</span></li> <li>Gray pants: $<span class="price right-align item-cost">40</span></li> <li>Postage: $<span class="price right-align postage-cost">5</span></li> </ul> </div>
In XPath, we can check the @class
attribute, but for XPath, the class attribute is an attribute like any other.
An XPath expression to find the postage cost would look like this:
//span[contains(@class,'postage-cost')]
To locate an element with a given class, we use the «.» symbol. For example, we could locate the cost of postage with the following CSS:
Locating elements by attribute
It is also possible to locate elements using values of their attributes.
For example, consider the following HTML code:
<input type="email" class="form-control" id="field122" name="email" aria-describedby="emailHelp" placeholder="Enter email">
This input
field contains an id value, but it does not seem very meaningful.
In this case, the name
attribute would be a more reliable way to locate the field.
Using XPath, we could use an expression like the following:
In CSS, we can do something similar:
We can also shorten this to match any element with a name attribute equal to «email»:
And if we need to locate an element with several specific attribute values, we can just combine selectors.
Suppose we had the following HTML code:
<input class="btn btn-primary" type="submit" name="user-details">
If the name attribute was not unique, we could locate this element by combining the name and the type, like this:
[name='email'][type='submit']
Direct Children
In XPath, you use the single-slash «/» to identify the direct child of an element. For example, in the following code, the h5
element is a direct child of the div
element.
<div id="order-details" class="card-body"> <h5 class="card-title">Order</h5>
In XPath, you could locate the h5
element using a direct child operator:
//div[@id='order-details']/h5
In CSS, we use «>» to indicate a direct child. So the equivalent of the previous XPath expression would be the following:
We can also locate elements using other CSS operators, as shown here:
#order-details > .card-title
This way, if the tag used for the title changes, the locator will not need to be changed.
Indirect Children
You often need identify an element that is a direct or indirect descendant of another known element. For example, the h5
element below is an indirect descendant of the top-level div
:
<div class="card" id="perso" > <div class="card-body"> <h5 class="card-title">Personal Details</h5>
In XPath, we use the double-slash operator («//») to locate direct or indirect descendants:
//div[@id='perso']//h5[@class='card-title']
In CSS, we just use whitespace:
Both direct and indirect children can be very useful when finding collections of elements. For example, the following HTML describes a collection of badges:
<div id="available-colors"> <span>Available:</span> <div class="color-list"> <span class="color badge badge-primary">Blue</span> <span class="color badge badge-danger">Red</span> <span class="color badge badge-warning">Yellow</span> <span class="color badge badge-success">Green</span> </div> </div>
The following CSS selector will identify the set of four colours shown here:
//div[@id='available-colors']//span[contains(@class,'badge')]
The equivalent in CSS would be the following:
Working with collections
We can also use CSS to locate specific element in a collection.
For example, suppose we wanted to find the second color badge in the previous HTML extract.
In XPath, we could do something like this:
//div[@id='available-colors']/div/span[2]
In CSS, we would use the nth-child
or nth-of-type
selector:
#available-colors span:nth-child(2)
The nth-child
selector matches element that is the nth element in a collection. So this expression returns the 2nd element in the collection that match the #available-colors span
CSS selector: ‘Red’.
Working with partial attribute names
Sometimes it is useful to find elements using partial matches on attribute values. For example, suppose we have the following HTML code:
<input class="form-control" id="persondetailsform_field127_street" placeholder="Enter Street">
In XPath, we could locate this element using an expression like the following:
//input[contains(@id,'street')]
The equivalent in CSS would use the $= operator, which matches an attribute value with a given suffix:
In CSS, we can use the following operators to perform partial matches:
-
^=: Match a prefix (
input[id^='persondetailsform']
) -
$=: Match a suffix (
input[id$='street']
) -
*=: Match a substring (
input[id*='details']
)
When to use XPath
XPath is required if you need to locate an element based on it’s text content.
For example, suppose we have the following HTML code:
<button type="button" class="btn btn-primary">Modify order</button>
The best way to locate this button would be to use the following XPath:
//button[. = 'Modify order']
Важным различием между CSS и XPath локаторами является то, что, используя XPath, мы можем производить перемещение как в глубину DOM иерархии, так и возвращаться назад. Что же касается CSS, то тут мы можем двигаться только в глубину. Это значит, что с XPath можем найти родительский элемент, по дочернему.
Автор: Энди Найт (Andy Knight)
Оригинал статьи
Перевод: Ольга Алифанова
Если вы занимаетесь тест-автоматизацией через веб-интерфейс (например, при помощи Selenium WebDriver), то, возможно, тратите много рабочего времени на поиск элементов на странице – к примеру, кнопок, полей ввода и блоков. Поиск нужных элементов может быть сложным делом, особенно в тех случаях, когда у них нет уникальных идентификаторов или имен классов. Это руководство поможет вам профессионально находить любые веб-элементы.
Что такое веб-элементы?
Веб-элемент – это индивидуальная сущность, генерирующаяся на веб-странице. Элементы – это все то, что пользователь видит (а иногда и не видит) на странице – заголовки, кнопки «ОК», поля ввода, текстовые блоки… Элементы в HTML определяются через имя тэга, атрибуты и содержание. У них также могут быть дочерние элементы – например, таблицы. CSS может применяться к элементам и менять их цвета, размеры и расположение. Языки программирования обычно получают доступ к веб-элементам как к нодам в объектной модели документа (DOM).
Что такое локаторы веб-элементов?
Веб-элементы и локаторы – это разные вещи. Локатор веб-элемента – это объект, который находит и возвращает веб-элементы на странице по заданному запросу. Короче говоря, локаторы находят элементы.
Зачем нужны локаторы? Как пользователи, мы взаимодействуем с веб-страницами визуально. Мы смотрим, скроллим, кликаем и печатаем посредством браузера. Тест-автоматизация, однако, взаимодействует со страницами программно: ей нужен закодированный способ поиска и манипулирования теми же самыми элементами. Традиционная автоматизация не будет «смотреть» на страницу, как человек – вместо этого она будет искать через DOM.
(Более современные технологии автоматизации позволяют визуальное тестирование – об этом будет сказано чуть позже).
Selenium WebDriver разделяет вопросы поиска элементов и взаимодействия с ними. Вызовы WebDriver для этих двух целей часто идут подряд.
1 |
// Пример для WebDriver: ввод поискового запроса на www.google.com |
WebDriver предоставляет следующие типы запросов для локаторов через «By»:
provides the following locator query types using “By”:
- Class name (Имя класса)
- CSS Selector
- ID
- Link text (текст ссылки)
- Name (имя)
- Partial link text (частичный текст ссылки)
- Tag name (название тэга)
- XPath
Какой из них лучше? Обсудим далее.
Локаторы могут возвращать несколько элементов, или вообще их не вернуть! Вот пример:
1 |
// Получение результатов поиска Google |
Большие тест-фреймворки часто используют шаблоны дизайна для структурирования локаторов и взаимодействий. Модель Page Object организует локаторы и методы действий в классы – по странице или по компоненту. Однако я крайне рекомендую шаблон Screenplay, а не Page Object, потому что части Screenplay лучше подаются повторному использованию и масштабированию. Вне зависимости от модели локаторы необходимы.
Как найти элементы?
Элементы могут с трудом поддаваться поиску, когда вы пишете локаторы для тест-автоматизации. Для упрощения работы я использую инструменты разработчика Chrome вместе с моей IDE. Почему Chrome?
- Все так делают (посмотрите на информацию о долях рынка у браузеров).
- Инструменты Chrome легко использовать, и они дают много полезной информации.
Чтобы исследовать любую страницу в Chrome, просто кликните правой кнопкой на любом месте страницы:
Вуаля! Инструменты разработчика откроются. Для поиска веб-элементов мы воспользуемся вкладкой Elements.
В Chrome легко выделить элемент визуально. Выберите инструмент «Select» в верхнем левом углу панели инструментов разработчика (она выглядит как квадрат с курсором). Иконка должна поменять цвет на синий.
Затем переместите курсор к нужному элементу на странице. Вы увидите, как каждый элемент подсвечивается, когда вы наводите на него мышь. Соответствующий исходный HTML-код на вкладке Elements также будет подсвечен. Чудненько! Кликните на нужном элементе, чтобы подсветка сохранилась после смещения курсора.
Теперь вы можете изучить тег элемента, классы, атрибуты, содержание, родительские и дочерние элементы.
Как писать хорошие локаторы?
Поиск элемента – это полдела. Создание уникального запроса для локатора – вот вторая половина. Если локатор чересчур широк, он будет возвращать ложноположительные значения. При слишком узком подходе он начнет ломаться при любом изменении DOM, и его будет сложно читать другим людям. Лучший подход здесь такой – пишите наиболее простой запрос, который уникально идентифицирует целевой элемент или элементы.
Мой список предпочтения типов запросов в порядке убывания:
- ID (если уникален)
- Имя (если уникально)
- Имя класса
- CSS-селектор
- XPath без текста или индексирования
- Текст ссылки/частичный текст ссылки.
- XPath с текстом и/или индексированием.
Уникальные ID, имена и имена классов крайне упрощают создание локаторов: запросы будут краткими и не требуют дополнительных якорей. Всегда ратуйте, чтобы ваши разработчики использовали уникальные идентификаторы (например, имена классов) для всех элементов. Однако у многих элементов таких идентификаторов нет, и локаторам приходится полагаться на более сложные CSS-селекторы и (содрогнувшись) XPath. Если это случилось с вами, вот мои рекомендации:
- Используйте родительские элементы как якоря, если у них есть уникальный идентификатор:
- Пример CSS-селектора: “#some-list > li”
- XPath-пример: “//ul[@id=’some-list’]/li”
- Избегайте XPath с текстом/индексированием при любой возможности.
- Плохой пример: “//div[3]//span[text()=’hello’]”
- Это наиболее хрупкие тесты.
- Используйте функцию «contains», проверяя классы в XPath.
- Пример: “//div[contains(@class, ‘some-class’)]”
- У элементов зачастую больше одного класса.
- “contains” проверит подстроку вместо полной строки класса.
- Но будьте осторожны, потому что под выдачу попадут также “some-class2”!
Всегда тестируйте локаторы, в них часто встречаются ошибки синтаксиса и ложноположительные значения. Chrome DevTools упрощает их тестирование – нажмите Ctrl+F на вкладке элементов и вставьте запрос локатора в поле поиска. DevTools подсветит все соответствующие элементы по порядку. Шик-блеск-красота!
Если я не могу разобраться, почему локатор в тесте не срабатывает, я делаю следующее:
- Запускаю тест без дебага со своей IDE.
- Устанавливаю на локаторе брейкпойнт.
- Жду, пока тест остановится.
- Захожу в DevTools в активном окне Chrome.
- Проверяю DOM и тестирую локатор на живой странице.
Что делать, если тесты нестабильны?
Тестирование через Web UI часто критикуют за нестабильность, потому что тесты часто падают из-за непредвиденных причин. Однако большая часть ненадежности, с которой сталкиваются тестировщики Web UI (и, зачастую, пользователи Selenium WebDriver как такового) связана с тем, что все Web-взаимодействия изначально создают гоночные условия. Автоматизация и браузер работают независимо друг от друга, и взаимодействие должно синхронизироваться с состоянием страницы. В противном случае WebDriver будет выдавать исключения из-за таймаутов, устаревших и не найденных элементов. В ряде случаев эти проблемы возникают не каждый раз, поэтому их тяжело отследить и исправить.
Лучший способ избежать гоночных условий таков – всегда ожидайте существования элемента, прежде чем взаимодействовать с ним. Это кажется элементарным, но про это легко забыть. Пакеты Selenium WebDriver всегда предлагают какую-то разновидность объекта WebDriverWait, заставляющего драйвер ожидать истинности определенного условия перед дальнейшими действиями. Простейший способ проверить, существует ли элемент – это проверить список элементов, возвращаемый вызовом FindElements (для списка элементов) и убедиться, что он непустой. Добавление дополнительного вызова для каждого взаимодействия может показаться затратным, однако дизайн-шаблоны хорошо спроектированных фреймворков (например, Screenplay) могут автоматически осуществлять подобные проверки.
Еще одна хорошая практика – всегда получать «свежие» элементы. Иногда автоматизация вначале получит ряд элементов, а затем через второй запрос получит следующую часть. Или же, в случае с Page Object Factory (никогда ей не пользуйтесь – если в трех словах, она отвратительна), элементы получаются один раз при конструировании Page Object, а затем на них ссылаются. Вне зависимости от способа – чем дольше существует объект на веб-странице, тем более он подвержен тому, чтобы устареть и вызвать исключения. Я видел элементы, необъяснимо устаревающие, даже если они еще присутствовали на странице. Всегда запрашивайте элемент тогда, когда он нужен – в этом случае он не успеет устареть!
Как искусственный интеллект может помочь в тестировании Web UI?
Ряд новых основанных на ИИ проектов и продуктов направлен на улучшение автоматизированного тестирования Web UI по сравнению с традиционными методами:
- Applitools расширяет автоматизацию Selenium WebDriver, добавляя проверки на необычные визуальные различия.
- Testim может автоматически исправлять сломанные локаторы, что позволяет избежать нестабильности тестов, связанной с изменениями фронтэнда.
- Mabl – это помощник, который выучит и запустит тесты, которому его научили разработчики, без создания какого-либо кода.
- Test.ai прогоняет распространенные пользовательские тесты вроде авторизации, поиска и покупок в мобильных приложениях, основываясь на знаниях ИИ о ряде других приложений.
- Rainforest QA использует краудсорсинг и ИИ для прогона ручных тестов, конкретизированных командой, так, что они получаются почти как автоматизированные.
Множество инструментов тестирования, основанных на ИИ, очень полезны, но держите в уме, что под капотом они все-таки пользуются локаторами.
Обсудить в форуме
В этой записи я хочу поделиться с вами Видеуроком от портала
automated-testing.info, показывать нам все это будет Михаил Поляруш. Вообще как правильно писать css и xpath локаторы я уже писал в статье xpath и css
Но это видео благодаря очень хорошему объяснению раскроет тему настолкьо подробно, что у вас уже никогда не возникнет вопросов, как правильно составить xpath или css селектор, благодаря это видео вы научитесь составлять запросы любой сложности. Лично я от видео был просто в восторге.
Ниже прикладываю шпаргалку по css и xpath, которую я записал, смотря на этот курс:
css локаторы:
div#pocks — ищем див, у котрого айдишник равен pocks
div.perl — ищем див, у которого класс обзывается perl
body[vlink=1] — ищем тег боди, у котрого атрибут vlink=1
body[vlink*=1] — ищем тег боди, у котрого атрибут vlink содержит в себе единичку
body[vlink$=1] — ищем тег боди, у которого атрибут vlink заканчивается на единичку
body[vlink^=1] — ищем тег боди, у которого атрибут vlink начинается на единичку
Пробел находит все потомков у элемента. Пример:
div#ires a — находит все ссылки у дива с айдишником ires
div#ires a:nth-of-type(1) — находит все ссылки у дива с айдишником ires первые
div >a — все дивы, у которых сразу за ними есть потомок а
div+div — находит див который идет сразу за первым дивом
div+a — все дивы за которыми сразу идут a элементы(ссылки)
div ~ div — пропускает элемент за элементом
a:contains(«ggdgdgd») — находит а
На 37 минуте примеры, где можно потренькаться
*.warning — любой элемент с классом warning
div * p — ищем элемент p у котрого есть предок div и между ними могут быть элементы
h1.opener+h2 — ищем элемент h2 соседм перед которым элемент h1 имеющий класс opener
a[rel~=»copyring»] — ищем ссылку с атрибутом rel, у которого внутри есть класс со значнием copyring
span[hello=’Cleveland’][goodbye=’Columbus’] — ищет элемент span, у котрого есть атрибут hello со значнеием Cleveland и атрибут goodbye со значнием Columbus
div.flyout > a — Найти все ссылки, которые находятся сразу после div элемента с классом flyout
div#action_list_body_current li:nth-of-type(1) — Найти вторую задачу в списке current
#quick search a[accesskey =»p»] -Найти вторую картинку с атрибутом accesskey «p» в quick search
#context_list a:contains(‘line’) -найти контекст в таблице Contexts который содержит текст «line»
xpath локаторы:
/body/.. — родитель бади, тобишь тег штмль
В чем отличие xpath от css, в икспасе мы можем ходить снизу вверх, а в цсс только сверху вниз. //
//a[text()=’some value’] — найти ссылку с текстом some value
author[last-name [position()=1]= «Bob»] — найти элемент author у которого есть элемент last-name и у last-name это первая позиция
//div[@id=’header’] — элемент див с айди хедер
//div[1] — первый див
//div[position()=1] — как я понимаю все равно что //div[1]
//div[2 and 3] — второй и третий див
В xpath отношения элементов определяют оси
// — означает что ищем по всем вложенным элементам
/descendant:div[@id=’header’] — находит всех потомков дива с айди хеадер
book/*/last-name — находим элемент бук после котрого идет любой элемент а за ним сразу идет элемент ластнейм
*[@specialty] — любой элемент с атрибутом specialty
author[first-name][3] — элемент с названием author у которого есть потомок элемент first-name и он третий
author[not(degree or award) and publication] — находим элемент author у котрого нет потомка элемента degree или award, но есть элемент publication
ancestor::author[parent::book][1] — находим предка у которого есть название элемента author и у которого есть непорсдетсвенный родитель book и выбираем первую позицию
//a[text() =»Preferences»][ancestor::*[@id=’header’]] — найти ссылку Preferences в верхнем меню (идем сверху вниз, вначале пишем ссылку с текстом Preferences
//*[@id =’action_list_curent’]//span[@class=’next_action_name’][following-sibling::*/a[contains(@href,’contexts’) and text() =’Offline’]] — Найти все задачи в списке current с контекстом Offline
ссылки:
w3.org/TR/selectors/
w3schools.com/css/css_examples.asp