Вступление

Всем привет! Я Екатерина Васильева, старший инженер по автоматизации тестирования в 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)


  1. saratoga8
    20.02.2025 18:57

    спасибо за статью!
    самый устойчивый локатор - data-testid
    его добавляют разработчики для тестовых целей. он может быть добавлен по просьбе разработчиков автоматического тестирования
    в остальных случаях, нет никаких устойчивых локаторов. фронт-энд разработчики изменять код и не спросят авто-тестировщиков. тесты попадали - проблема тестировщиков, а не фронт-энда
    исходя из собственного опыта, могу сказать - все решает взаимодействие разработчик-тестировщик. если этого нет - головная боль, нервы, обиды


  1. saratoga8
    20.02.2025 18:57

    не рассчитывайте на поиск локаторов по тексту - UI/UX очень часто и легко меняет текст элементов. Например:
    определили локатор "//*[text()='Очистить историю']"
    через неделю, дизайнеры UI/UX решили, что текст должен быть 'очистить историю'
    и тест упал из-за одной буквы



  1. breakingtesting
    20.02.2025 18:57

    "//h3[text()='*** QA Group']/../../../.."

    Жестко завязан на структуру, поэтому более хрупок, чем:

    //a[descendant::h3[text()=‘*** QA Group’]]

    Можно почитать подробнее про XPath Axes