Вступление
Всем привет! Я Екатерина Васильева, старший инженер по автоматизации тестирования в InfoWatch, и сегодня хочу поделиться своими наработками в области автотестирования веб-сайтов. В процессе работы нужно было работать со сторонними сайтами – в нашем случае это были VK, Telegram, Yandex Messenger, Whatsapp.
Локатор - это путь к элементу на странице веб-сайта, с помощью которого автоматизированный тест сможет этот элемент найти. В большинстве случаев для автоматизации веб-сайтов используют именно локаторы.
В данной статье собрана информация, необходимая для составления устойчивых локаторов. То есть тех локаторов, которые не нужно будет часто менять. Это крайне необходимо для автоматизации, когда вполне могут поменяться локаторы из-за обновления тегов и их атрибутов программистами, например, во время рефакторинга или написания нового функционала. Статья написана максимально просто, без «воды», на основе около десятилетнего опыта в автотестировании веб-сайтов на различных проектах.
Данная статья может быть особенно интересна «новичкам» в области автотестирования веб-сайтов. Но и автотестировщики с опытом могут найти в ней интересные моменты для своей работы.
Основная теория
Локаторы составляются по тегам из дерева тегов страницы. В данной статье описано составление локаторов Xpath в Chrome.
Элемент можно найти, кликнув правой кнопкой мыши по нему, а затем выбрав - "Исследовать элемент". Или нажав F12 (или Ctrl + Shift + I), а затем найдя элемент поиском (CTRL+F) по ключевым словам. Наведя курсор на элемент в дереве тегов, этот элемент подсветится на UI странице.
Локатор должен быть уникальным. Уникальность локатора можно проверить, зайдя в Console->DevTools (открывается через F12 или Ctrl + Shift + I) и введя строку $x("locator"). При наведении курсора на локатор из списка соответствующий элемент на странице подсветится.
Бывает, программисты делают специальные ключи для автоматизации, но в условиях написания автотестов, например, для сторонних сайтов, как в моем случае VK, Telegram, Yandex Messenger, Whatsapp нет возможности попросить разработчиков об этом. Чтобы локатор был уникальным и стабильным, необходимо найти говорящий, уникальный ключ или ключи. Это может быть текст на странице или значение class, id, name и т. д.
На что обратить внимание при составлении локаторов:
Локатор должен быть не очень длинным, поэтому можно использовать часть текста или значения class и т. д., которое характеризует элемент.
Не стоит использовать локаторы с данными, которые очень вероятно могут поменяться (например, с id="r110", href="#-4563532102", class="title QljEeKI5").
Также не стоит использовать все значения, например, class и т. д. если их много. Достаточно выбрать ключевые 1-2 значения и использовать contains. Это нужно для удобства чтения локатора. Также «побочные» значения могут поменяться в короткие сроки.
Если в самом элементе нет уникальных текстов и значений, то тогда можно взять соседний элемент (выше по дереву, ниже по дереву, из соседнего узла) и через перемещения по дереву дойти до необходимого элемента.
Примеры локаторов
Примеры локаторов из популярных мессенджеров для веб:
Пример 1. Простой:
<button type="button" class="Button send main-button default secondary round click-allowed" aria-label="Send Message" title="Send Message">
...
</button>
"//button[@title='Send Message']"
Пример 2. С текстом:
<li tabindex="0" class="_aj-r_aj-q_aj-_" data-animate-dropdown-item="true" role="button" style="opacity: 1;">
<div class="_aj-z _aj-t _alxо" aria-label="Очистить чат" style>Очистить чат</div>
</li>
"//li/div[text()='Очистить чат']"
Пример 3.1. С contains:
Для данного случая в коде программы хотелось получить один локатор для похожих элементов разных версий переписки: диалога и группы. Здесь нужен contains, так как для разных чатов у текста элемента есть общая часть, но есть и отличия.
<button type="button" class="Button confirm-dialog-button def ault danger text">Delete for me and QA</button>
<button type="button" class="Button confirm-dialog-button default danger text">Delete for everyone</button>
Локатор:
"//button[contains(text(), 'Delete for')]"
Отмечу, что данный код уже устарел, так как был переделан интерфейс telegram.
Пример 3.2. С contains:
Для данного случая нужен contains, чтобы локатор не был слишком длинный, но при этом он был уникальным.
<button class="btn-icon rp btn-circle btn-send animated-button-icon record" tabindex="-1">
<div class="c-ripple"></div>
...
</button>
"//button[contains(@class, 'btn-send')]/div[@class='c-ripple']"
Пример 4.1. С переходом по дереву тегов ("/" - один уровень вниз, "//" - один или больше уровней вниз, "/.." - один уровень вверх):
Здесь нужно подняться вверх по дереву, так как для автотеста нужен кликабельный элемент (кликабельность проверяется перебором или ищется "//a"), а для самой ссылки "/a" уникальный простой локатор не сделать.
<a class="ListItem-button" role="button" href="#-4563532102" tabindex="0">
<div class="ripple-container"></div>
<div class="status status-clickable"> </div>
<div class="info">
<div class="info-row">
<div class="title Q1jEeKI5">
<h3 dir="auto" role="button" class="fullName AS54Cntu SgogACy_">*** QA Group</h3>
"//h3[text()='*** QA Group']/../../../.."
Пример 4.2. С переходом по дереву тегов:
Для данного случая уникальный определитель ищется по TEXT_MESSAGE_BEGIN = "Automation telegram web ", и затем осуществляется переход до кликабельной позиции – на два уровня ниже до "div". Стоит отметить, что используется именно TEXT_MESSAGE_BEGIN, а не полностью все отправленное сообщение:
<div class="text-content clearfix with-meta with-outgoing-icon" dir="auto">
"Automation telegram web 2024-11-06 14:17:59.713550"
<span class="MessageMeta" dir="ltr" data-ignore-on-paste="true">
<span class="message-time">...</span>
<div class="MessageOutgoingStatus">...</div>
</span>
::after
</div>
f"//div[contains(text(), '{TEXT_MESSAGE_BEGIN}')]//div", где TEXT_MESSAGE_BEGIN - переменная
Отмечу, что формат f-string доступен для Python 3.6 и выше.
Пример 4.3. Еще один пример с переходом по дереву тегов:
Для данного случая выбирается первый тег "button" на уровень ниже:
<div class="dialog-buttons-column">
<button type="button" class="Button confirm-dialog-button default danger text">Delete for me and QA</button>
<button type="button" class="Button confirm-dialog-button default danger text">Delete just for me</button>
<button type="button" class="Button confirm-dialog-button default primary text">Cancel</button>
</div>
"//div[@class='dialog-buttons-column']/button[1]"
Локаторы с индексом удобно использовать в циклах или в работе с таблицами, можно индекс сделать переменной.
Пример 5. Локатор с двумя атрибутами на одном уровне для уникальности:
<div aria-disabled="false" role="button" tabindex="0" class="_ajv6 xlylawlk x1sxyh0 xwib8y2 xurb0ha" data-tab="6" title="Meню" aria-label="Meню">
"//div[@title='Меню'][@data-tab='6']"
Пример 6. Локатор с переменной:
<div class="ui-entity-block-horizontal-layout_content">
<div class="ui-entity-block-multi-line" id="2694438472_text">
<span class="yamb-chat-list-item_author" dir="auto">You:</span>
<wbr>
"Automation yandex messenger web 2024-11-01 08:04:45.638888"
<div class="yamb-typing-overlay" aria-hidden="true"></div>
</div>
</div>
<div class="ui-entity-block-horizontal-layout_right-addon">...</div>
</div>
</div>
</div>
::after
</div>
<div class="ui-entity-block yamb-chat-list-item yamb-chat-list-item_active yamb-chat-list-item_pressable" tabindex="-1" aria-selected="true" aria-labelledby="1199377365_name 1199377365_text 1199377365_unread 1199377365_date" aria-haspopup="tr e" role="link">
::before
<div class="ui-entity-block_left-addon">...</div>
<div class="ui-entity-block_details ui-entity-block_details_start-padding_m">
<div class="yamb-chat-list-item__content">
<div class="ui-entity-block-horizontal-layout">...</div>
<div class="ui-entity-block-horizontal-layout">
<div class="ui-entity-block-horizontal-layout_content">
<div class="ui-entity-block-multi-line" id="1199377365_text">
<span class="yamb-chat-list-item_author" dir="auto">You:</span>
<wbr>
"Automation yandex messenger web 2024-11-01 10:24:44.430864"
<div class="yamb-typing-overlay" aria-hidden="true"></div>
locator="//div[text()='%s']"
Далее в коде указывается, к примеру:
browser.wait_locator(locator % message)
Где message – то, на что заменяется "%s". В нашем случае - текст сообщения.
Пример 7. Локатор со звездочкой:
Когда тег(и) локатора часто меняется, можно использовать вместо тега "*". Также можно использовать звездочку, например, в случае, когда неудобно искать тег (при быстром исчезновении элемента).
Напоминаю, в таких случаях необходимо убедиться, что это единственный элемент на странице.
"//*[text()='Очистить историю']"
Дополнение
Приведенные в данной статье в качестве примеров локаторы являются наиболее часто встречающимися. Но это далеко не все возможности Xpath. Перечислю еще некоторые возможности, которые можно самостоятельно изучить:
starts-with – когда известна первая часть текстового содержимого элемента, либо часть значения его атрибута.
last – выбор последнего элемента (указанного типа) из набора элементов.
child – выбирает элементы-потомки (но только первые дочерние) текущего узла.
preceding-sibling – выбирает все узлы, предшествующие контекстному узлу и содержащиеся в том же узле родительского элемента последовательно в обратном порядке.
Заключение
Все приведенные локаторы взяты из реализации автотестов для веб-мессенджеров (Telegram, VK, Yandex Messenger, Whatsapp). Для составления локаторов использовался Xpath.
В статье отражены различные способы задания локаторов, которые наиболее часто используются в автотестах. Используя эти способы, можно составить локаторы практически для любого современного сайта.
Комментарии (6)
saratoga8
20.02.2025 18:57не рассчитывайте на поиск локаторов по тексту - UI/UX очень часто и легко меняет текст элементов. Например:
определили локатор "//*[text()='Очистить историю']"
через неделю, дизайнеры UI/UX решили, что текст должен быть 'очистить историю'
и тест упал из-за одной буквы
breakingtesting
20.02.2025 18:57"//h3[text()='*** QA Group']/../../../.."
Жестко завязан на структуру, поэтому более хрупок, чем:
//a[descendant::h3[text()=‘*** QA Group’]]
Можно почитать подробнее про XPath Axes
saratoga8
спасибо за статью!
самый устойчивый локатор - data-testid
его добавляют разработчики для тестовых целей. он может быть добавлен по просьбе разработчиков автоматического тестирования
в остальных случаях, нет никаких устойчивых локаторов. фронт-энд разработчики изменять код и не спросят авто-тестировщиков. тесты попадали - проблема тестировщиков, а не фронт-энда
исходя из собственного опыта, могу сказать - все решает взаимодействие разработчик-тестировщик. если этого нет - головная боль, нервы, обиды