Селекторы CSS и их применение в автоматизации тестирования Программного Обеспечения

    Всем добра!

    Данную тему мы уже раскрывали на вебинаре, который проводил наш преподаватель, но решили дополнить чуть текстом (да и многим, как оказалось, так удобнее). В общем представляем статью на тему «Селекторы CSS», которую Павел Попов прорабатывал в рамках нашего курса «Автоматизация в тестировании».

    Поехали.

    Каждый курс или статья для начинающих автоматизаторов рассказывает об удобном и универсальном средстве поиска элементов Web-страницы, как XPath. Данный вид локаторов на элемент был создан в 1999 году для указания на элементы в XML файлах. С помощью встроенных функций XPath стал очень популярным инструментом поиска элементов на Web-странице. Если HTML код вашего приложения выглядит как-то так:

    …
    <form class=“form_upload>
        <div>
            <div class=“row_element_3 row tile_fixed”>
                <div class=“button_cell wrapper_tile”>
                    <button type=“submit” class=“button_submit wrapper_button”>Нажми меня</button>
                </div>
            </div>
        </div>
    </form>
    …

    и вы не можете найти достойный XPath для кнопки “Нажми меня”, не стоит сразу бежать в сторону разработчика с просьбой о помощи. Есть отличная возможность воспользоваться CSS селектором, он будет выглядеть так:

    .button_submit

    Добро пожаловать в мир CSS.



    Принято считать, что в CSS селекторах все завязано на классы. Это не совсем так, но если Web приложение использует “оптимизатор” или “обфускатор” HTML кода, и выглядит вот так:

    
    <form class=“afhfsdh__”>
        <div>
            <div class=“hfgeyq fjjs qurlzn”>
                <div class=“fjdfmzn fjafjd”>
                    <button type=“submit” class=“ajffalf wjf_fjaap”></button>
                </div>
            </div>
        </div>
    </form>
    …

    (все названия css классов уменьшены с помощью оптимизатора)

    , то получить короткий CSS селектор не удастся — как правило, после каждого нового билда css классы меняются на новые. Но все равно, CSS селектор может оказаться проще и в этом случае: css: form button[type=‘submit’], вместо XPath: //form//button[@type=‘submit’]

    Допустим, что оптимизаторы HTML у нас не установлены и разработчики не планируют его использовать на проекте (проверьте этот факт!).

    Как вы могли уже догадаться, символ. используется вместо слова class и будет искать вхождение данного класса в любом элементе, в независимости от количества классов у этого элемента.

    Смотрите:

    
    <form class=“form_upload>
        <div>
            <div class=“row_element_3 row tile_fixed”>
                <div class=“button_cell wrapper_tile”>
                    <button type=“submit” class=“button_submit wrapper_button”></button>
                </div>
            </div>
        </div>
    </form>
    


    css для элемента button: .button_submit, при этом класс .wrapper_button указывать необязательно, но если он нужен для указания на наш класс, мы можем его добавить сразу после указания первого css класса: css: .button_submit.wrapper_button. Порядок классов роли не играет, поэтому их можно поменять местами:

     .wrapper_button.button_submit .

    Следующим незаменимым помощником в поиске HTML элементов являются Теги. Написать css селектор, указывающий на тег button очень просто, тем более, что он уже был написан в этом предложении. CSS селектор для button –

    css: button.

    И ничего больше указывать вам не требуется, если ваша цель — это привязка к тегу. Совмещая теги и классы получаем::

    button.button_submit

    и это также является css селектором к нашему элементу.

    Помимо тегов, атрибуты также помогают уникально идентифицировать элемент на странице. Часто разработчики создают дополнительные атрибуты вместо добавления новых “айдишников”, например, могут создавать дополнительные атрибуты data-id или auto-id у каждого элемента, с которым планируется дальнейшее действие. К примеру, разработчики могут добавить атрибут data-id к нашей кнопке button. Тогда к атрибутам с помощью css селектора можно обратиться через фигурные скобки: [data-id=‘submit’]. Дополнительно, вы можете не указывать значение атрибута после знака равно [data-id]. Такой селектор найдет вам все элементы, у которого существует атрибут data-id с любым значением внутри. Вы также можете указать атрибут class для поиска нашей кнопки: [class=‘button_submit’], но в CSS, как вы уже знаете, можно полениться и написать так: .button_submit. Соединять все вместе также достаточно просто:

    button[type=‘submit’].button_submit
    тег   атрибут        класс

    Но это большая удача, если нам удается находить элемент, используя селектор с указанием только одного элемента, как, например, использовать атрибут [data-id] который однозначно находит один элемент на странице. Очень часто нам приходится использовать предков элемента, чтобы найти потомка. И это в CSS тоже возможно сделать достаточно просто:

    
    <form class=“form_upload>
        <div>
            <div class=“row_element_3 row tile_fixed”>
                <div class=“button_cell wrapper_tile”>
                    <button type=“submit” class=“button_submit wrapper_button”></button>
                </div>
            </div>
        </div>
    </form>
    


    css:form > div > div > div > button.button_submit

    и знак > позволяет найти элемент исключительно у предка внутри. Но писать все элементы ненужно, так как в CSS есть возможность поиска во всех потомках, этот символ — пробел “ “. Используя данный указатель мы можем быстро найти элемент внутри формы:

    Было: css: form > div > div > div > button.button_submit
    Стало: css: form button,button_submit

    Удобно также находить следующего “родственника” через предыдущего. Дополним наш пример еще одним span:

    
    <form class=“form_upload>
        <div>
            <div class=“row_element_3 row tile_fixed”>
                <div class=“button_cell wrapper_tile”>
                    <div class=“content”></div>
                    <span data-id=“link”>Ссылка</span> <!-- элемент с атрибутом data-id -->
                    <button type=“submit” class=“button_submit wrapper_button”> <!-- искомая кнопка --></button>
                </div>
            </div>
        </div>
    </form>
    

    [data-id=‘link’] + button найдет button, у которого выше на один уровень есть родственник с атрибутом data-id=”link”. Этим указателем можно пользоваться, когда у предыдущего элемента есть id или уникальный атрибут, а у элемента, который находится следующим после нужного, таких идентификаторов нет. Итак, с помощью символа + css селектор найдет следующего родственника.

    NOTE:

    div + span[data-id=‘link’] + button

    Дополнительно вы можете собирать “паровозик” из следующих элементов с использованием указателя +, но не советую это делать из-за возможного изменения местонахождения элементов.

    Не стоит упускать вариант поиска по части атрибута. Делается это просто: button[class*=‘submit’] — из длинного названия класса button_submit мы берем только правую часть submit и добавляем к знаку = символ *. Также можно найти по слову cell из значения класса: div[class*=‘cell’].

    Есть еще одна особенность css селекторов, которая позволит найти вам все ссылки или расширения файлов, это ^= и $= , но такая задача стоит не так часто, как поиск по вхождению значения у атрибута.

    a[href^=“https:”] — найдет все ссылки, которые начинаются с https,
    a[href$=“.pdf”] — найдет все ссылки, которые заканчиваются на .pdf.

    Немного о том, как найти потомков с одним и тем же тегом у предка. Начнем, как всегда, с примера:

    
    <div class=“tiles”>
        <div class=“tile”>…</div>
        <div class=“tile”>…</div>
    </div>
    

    Как найти второй div class=“tile” у div class=“tiles”? Варианта два:

    div > div:nth-of-type(2) div > div:nth-child(2)

    Но в чем различие между этими двумя селекторами? Дополним пример:

    
    <div class=“tiles”>
    	<a class=“link”>…</a> <!—1—>
    	<div class=“tile”>…</div><!—2—>
    	<div class=“tile”>…</div><!—3—>
    </div>
    

    css 1 вариант: div > div:nth-of-type(2)
    css 2 вариант: div > div:nth-child(2)

    Теперь эти селекторы ведут на два разных элемента. Прежде чем идти далее, попробуйте догадаться, какой селектор ведет на какой элемент?

    Разгадка:

    первый селектор будет указывать на строчку номер 2, тогда как второй селектор будет указывать на строчку номер 3. nth-child ищет второй div, который является потомком родителя . Второй
    <div>
    у элемента
    <div class=“tiles”>
    это третья строка. В свою очередь nth-of-type ищет второй элемент у родителя
    <div class=“tiles”>
    , который должен являться тегом
    div
    , это строка номер два.

    Есть правило, которое позволяет упростить работу с селекторами в ситуации, когда вам нужно найти конкретный элемент: использовать nth-of-type везде, где это возможно. Если вы разобрались с примером выше, рекомендую вам всегда обращать внимание на количество одинаковых элементов у предка, используя nth-child, и тогда вам будет неважно, куда поместят ссылку
    <a>
    : наверху, между
    <div>
    или внизу блока, всегда селектор div:nth-child(2) будет все равно указывать на нужный элемент – второй элемент div внутри блока.

    Была опущена еще одна функция поиска элемента по id. Вы уже знаете, что поиск по любому из атрибутов осуществляется с использованием указания квадратных скобок [attribute=“value”] и для нашего случая мы можем найти элемент так [id=“value”]. А что если есть существует более быстрый способ поиска по id элемента?

    #value. “#” - указатель, что поиск осуществляется по id.

    Используя все приобретенные навыки попробуйте написать селектор для кнопки
    <button>


    
    Отправить
    …
    <div>
        <div class=“tile_wrapper tile content”>
            <div class=“row row_content”>
                <div class=“outline description__on”></div>
                <div class=“outline description__off button_send hover_mouse”>
                    <button class=“outline button_send”>Отправить</button>
                </div>
            </div>
        </div>
    </div>
    

    Будем рады увидеть ваши комментарии и варианты в комментариях тут или обсудить это на очередном открытом уроке, который пройдёт у нас 13-го марта.

    Спасибо!
    OTUS. Онлайн-образование
    Цифровые навыки от ведущих экспертов

    Comments 18

      +1
      Мини-оффтоп
      Т.к. в ЛС очень часто игнорят — отпишусь тут, надеюсь простите: Исправьте пожалуйста код; везде, в каждом примере, они все нерабочие. Неужели никто не проверял корректность того, что пишется? Очень сильно бросаются в глаза кривые кавычки и апострофы, что бросает тень на качество ваших курсов ;)
        –1
        Да, блин. Я конец правил, где кодовский тэг почему-то везде полез куском текста и по ходу напортачил с версиями текста. Поправлю, спасибо.
        +1
        Эмм… А что, есть люди, которые занимаются автоматизированным тестированием веб-приложений и при этом не в курсе базового синтаксиса CSS?
          +1

          Если искать элементы, как показано в статье, то мы имеем следующие проблемы:


          • Написание селектора — головоломка, решение которой требует дополнительное время. Ибо надо найти нужный элемент по косвенным признакам ("вторая с конца кнопка лежащая во второй форме на странице" или "Дедушка элемента содержащего текст 'Тестовая задача 123'").
          • Тесты получаются хрупкими. Мало мальский рефакторинг и многие тесты падают, ибо селектор то перестаёт матчиться, то матчится на что-то другое.

          В чём причина проблем: элементы на странице не имеют семантичных "якорей", для быстрого и надёжного поиска элемента.


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


          Примеры семантик и возможных идентификаторов:


          • кнопка сохранения в форме редактирования задачи — task_edit.save
          • пункт для задачи 123 в списке задач — task_list.item(123)

          Пример из реальной жизни: http://mol.js.org/ — тут для каждого элемента автоматически генерируется уникальный семантический идентификатор. Попутно этот идентификатор является и js кодом для получения доступа к соответствующему ему компоненту.


          Почему так не делают везде? Потому, что обычно используются инструменты, в которых генерация семантических идентификаторов не автоматизирована (любимые всеми html шаблоны, да), а значит требует дополнительных ресурсов на ручную их генерацию. Программисты зачастую не делают эту работу, так как не мотивированы помогать тестировщикам.

            +1

            Как мне кажется количество UI у $mol прибавилось за последние месяцы. Смотрится достойно
            Сплиттеров не будет?
            У Plota есть горизонтальный скролл?
            У Scroll не помешал бы пример с двумя зависимыми скроллами с якорями и хистори)

              +1

              Да нет, мало что поменялось за последние пол года.
              Может и будут. Сплиттеры плохо дружат с адаптивностью. Так что надо на конкретном кейсе смотреть как его лучше реализовать.
              $mol_plot только рисует, но совместить его с $mol_scroll в принципе не сложно. Тут пример совмещения его с дрегендропом: https://habrahabr.ru/post/342064/
              Зависимые скроллы — это как в merge tool? А хистори зачем для скролла?

                0

                В скролле может быть контент с якорями, чтобы контент красиво вертикально подъезжал при навигации.

            +1
            Для меня, в пользу CSS-селекторов еще есть удобство проверки селектора
            Открыл консоль браузера
            Написал $('{мой селектор}') или $$('{мой селектор}') (если нужно множество элементов) и посмотрел, что выдалось в результате
            Это очень быстро
              +1

              Точно также можно проверить xpath, $x(selector, context)

                0

                C xpath еще проще — просто открываем Chrome DevTool -> Elements и Ctrl+F

                0

                Главный вопрос, в чем посыл статьи? Чем css selector использовать лучше xpath, например? Последним то всё можно найти, в отличии от цсс селектора, хоть он и чуть более многословен.

                  +1

                  Все гораздо сложнее, когда используется обфускатор.
                  Можно ещё договориться использовать для тестирования некий кастомный атрибут, а-ля testid (react-native), и использовать его так Войти. Соотвественно, селектор для него [testid=“sign_in_button”].
                  Если в проекте используется обфускатор, то наверняка получится вырезать этот атрибут в продакшне.

                    0
                    <a testid="sign_in_button">Войти</a>
                    +1
                    Но все равно, CSS селектор может оказаться проще и в этом случае: css: form button[type=‘submit’], вместо XPath: //form//button[@type=‘submit’]

                    Не совсем понимаю чем вариант с CSS проще в примере выше, но согласен что #id или .class на глаз приятнее чем //tagname[@id=''] etc.

                    На практике — оба метода хороши, единственный минус CSS селектора с которыми я лично столкнулся: нету возможности вернуться на уровень выше (аналог с xpath: //button[@type=‘submit’]/..), так же как и указать на родительский элемент (пожалуйста поправьте, если ошибаюсь).

                      0

                      Не ошибаешься. Для этого VDOM и нужен.

                      0
                      Ох и намучались мы в свое время с этими css-селекторами в Selenium тестах — никому не советую так делать.
                      Очень скоро наши тесты начали так сильно зависеть от имен css-классов или структуры html, что любое, даже малейшее косметическое изменение UI вело к падению большого количества тестов. И время на фикс этих тестов было гораздо больше чем на создание самой фичи, а еще тесты блокировали деплой.
                      В общем для себя решили в тестах использовать только семантические идентификаторы (например data-ref=«submit_button» или data-action=«submit») а css пусть занимается только стилизацией.
                        0

                        Весь html и JS контекстнозависимый. Тестировать не учитывая возможное изменение контекста невозможно. Вы правильно решили. Микшировать css это очень круто, но бессмысленно.

                          0
                          Не нужно учить автоматизаторов писать сложные css и xpath, нужно учить разработчиков писать короткие и надёжные иденитификаторы для элементов.

                          Only users with full accounts can post comments. Log in, please.