Как устроен Selenium: Эпизоды 3 — 5

Автор оригинала: David Burns
  • Перевод
Салют, хабровчане. Сегодня мы продолжаем серию переводов, приуроченных к запуску курса «Java QA Engineer».





Эпизод 3 — isDisplayed


В результате диалога, произошедшего в последние выходные января 2020 года, который был посвящен одной из проблем в Selenium, где кто-то сказал мне «почему ты просто не сделаешь так…» в ответ на объяснение проблемы, я решил написать серию статей, объясняющих команды в Selenium WebDriver и почему мы в итоге пришли к дизайну, который имеем сегодня.

Я буду повторять это в каждом эпизоде этой серии — много, иногда до безумия много размышлений и продумывания было вложено в работу каждой мельчайшей детали Selenium.

В этом эпизоде ​​мы рассмотрим значительную часть механизмов, заложенных в isDisplayed(). Эти же механизмы используются в командах взаимодействия, поэтому будет полезно понять, как они работают, и как другие команды могут их использовать. С технической точки зрения это моя любимая команда (из-за того как она работает).

Почему нас может волновать, “отображено” ли что-либо?


Итак, давайте начнем с понимания того, почему нас в принципе может волновать отображается элемент или нет.

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

Есть два основных случая, в которых мы проверяем видимость элемента: когда мы вызываем element.is_displayed() для просмотра элемента и когда мы делаем взаимодействия.
ПРИМЕЧАНИЕ

Когда дело доходит до взаимодействий, Selenium имеет два типа команд. Методы из WebElement работают в духе «сделай то, что я подразумеваю»: selenium пытается сделать то, что по сути должно произойти, когда вы type (набираете текст) или click (кликаете). Методы, связанные с Actions, представляют стиль «сделай, как я сказал» — эти команды будут делать именно то, что вы им сказали, не пытаясь интерпретировать то, что вы на самом деле пытаетесь сделать.

Как это работает?


Прежде всего, мы должны понимать, что element.is_displayed() должен работать без необходимости прокрутки страницы. Это важно, так как мы не хотим перемещать страницу без необходимости.

Здесь нам нужно взглянуть на сам элемент. Мы смотрим на CSS элемента.

Во-первых, давайте посмотрим, является ли элемент частью дерева доступности (accessibility tree). Мы не смотрим в само дерево доступности, мы просто проверяем наличие некоторых сценариев и их влияние на расположение элементов на странице. Мы не смотрим в дерево доступности, поскольку это может быть чрезвычайно трудоемкой операцией для браузера.

Простой сценарий, который сюда подойдет — когда у элемента есть display: none, как показано ниже.

#idOfElement {
    display: none;
}

Это приведет к тому, что ничего не будет добавлено дерево доступа и не повлияет на расположение элементов.

Далее нам нужно посмотреть, сможем ли мы совершить прокрутку до элемента, если это необходимо. Мы просто проверим наличие такой возможности, поскольку мы не хотим скролить на самом деле. Это важно для элементов, которые были перемещены в другое место на странице. Это означает, что мы не всегда можем рассчитывать на просмотр дерева DOM. Я обсуждал это в предыдущем посте в 2013 году.

Далее нам нужно сделать несколько проверок для таких элементов, как <input>, <option>, <optgroup> и <map>. Элементы <option> и <optgroup> являются «скрытыми», пока вы на них не кликнули, мы предполагаем, что они видимы… ну, в основном.

Затем мы переходим к проверке размера элемента. Для элементов с размером 0 мы говорим, что они не видны. Мы также проверяем их opacity. Если opacity равна 0, мы не считаем элемент видимым.

После того, как мы выполнили эти тесты, нам нужно рекурсивно пройти DOM и повторить все тесты, пока мы не достигнем documentElement.

Зачем рекурсия?


К сожалению, из-за CSS мы не всегда знаем, на каком уровне DOM. Нам нужно проверить каждый узел на обратном пути. Это означает, что isDisplayed может быть немного медленным, но он дает нам хорошую оценку видимости элемента.

Разве мы не можем просто взять спросить браузер, видим ли элемент?


К сожалению, нет. Браузеры, выбирая, что следует рендерить, создают списки отображения, а затем отправляют их в менеджер окон (window manager), чтобы он выполнил всю тяжелую работу. В прошлом браузеры никогда не беспокоились о том, насколько эффективно генерируется список отображения. Менеджер окон попытается сделать список отображения более эффективным, а затем отобразить его. Замечательно!

Теперь, касательно списков отображения — они всего лишь говорят оконному менеджеру, что визуализировать в области просмотра. Область просмотра (viewport) — это собственно видимая область страницы в браузере. Мы хотим, чтобы isDisplayed() сообщал нам, будет ли отображаться элемент.

Мы также можем столкнуться с частично скрытыми элементами. Как мы узнаем, является ли элемент действительно видимым или видна только его небольшая часть? Здесь мы могли бы использовать некоторые веб-API, такие как document.elementFromPoint(x, y);. Опять же, это скажет нам только об элементах, которые находятся в области просмотра, и даже тогда есть случаи, когда один элемент может накрывать другой, но клики проходят по «скрытому элементу».

Об этом я рассказываю подробнее в своем выступлении на Selenium Conf London 2016. Вы можете посмотреть это видео, если вам интересно.

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

Для дальнейшего чтения


Обычно я бы поместил здесь ссылку на спецификацию WebDriver для этой команды, но вы можете прочитать приложение, которое специфицирует драйверам, какую конечную точку добавлять.

Вы также можете спуститься в кроличью нору и посмотреть код, который выполняет всю эту работу.

Эпизод 4 — Поиск элементов


В этом эпизоде ​​мы рассмотрим, как работает findElement. В первую очередь нам нужно уметь находить элементы, прежде чем мы сможем взаимодействовать с ними.

Найти элемент легко, верно?


Ну да, если бы люди разрабатывали свои приложения с оглядкой в первую очередь на тестируемость, отодвинув все остальное на второй план. К сожалению, мы живем в мире, где большую часть времени люди думают о качестве, а о тестировании — как о чем-то второстепенном.

Во всяком случае… давайте посмотрим, как мы можем их искать.

Типы поиска

Эта часть достаточно проста, Selenium предлагает те же методы поиска, которые вы можете найти при взаимодействии с DOM посредством JavaScript. Если мы можем искать элементы с помощью document.querySelectorAll(aQuery); тогда они будут доступны для поиска через find_element или find_elements. Они доступны из объекта WebDriver или WebElement. Если вы делаете element.find_element(...), это эквивалентно element.querySelectorAll(aQuery), который устанавливает начальную точку или корень для поиска по этому элементу.

Ниже приведен пример использования find_element:

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Firefox()
driver.get("https://www.theautomatedtester.co.uk")
driver.find_element(By.ID, "someID")
driver.find_element(By.CSS_SELECTOR, "#someID")
driver.find_elements(By.CSS_SELECTOR, "#someID")
driver.find_element(By.NAME, "someName")
driver.find_elements(By.NAME, "someName")
driver.find_element(By.CLASS_NAME, "someName")
driver.find_elements(By.CLASS_NAME, "someName")
driver.find_element(By.TAG_NAME, "someName")
driver.find_elements(By.TAG_NAME, "someName")

Мы также можем искать элементы на странице по XPATH.

Мы можем сделать следующее

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Firefox()
driver.get("https://www.theautomatedtester.co.uk")
driver.find_element(By.XPATH, "//li")

Мы также можем искать ссылки на основе видимого текста. Видимый текст использует алгоритм isDisplayed, чтобы понять, что пользователи могут и не могут прочитать. Selenium соберет все теги на странице, получит видимый текст, а затем найдет первый элемент, содержащий этот текст.

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Firefox()
driver.get("https://www.theautomatedtester.co.uk")
driver.find_element(By.LINK_TEXT, "some text")
driver.find_element(By.PARTIAL_LINK_TEXT, "text")

Что происходит, когда он находит элемент?


Когда мы в состоянии найти элемент, мы отслеживаем его в карте (Map). Карта будет содержать представление элемента, используя uuid. Карта позволяет нам искать элементы, когда мы возвращаем их из теста Selenium, и проверять, действительно ли эти элементы подключены к DOM. Если элемент не подключен к DOM и вы пытаетесь использовать его, вы получите StaleElementReferenceException.

а когда он не находит элемент или элементы


Есть 2 вещи, которые могут произойти.

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

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By

driver = webdriver.Firefox()
driver.get("https://www.theautomatedtester.co.uk")
try:
    driver.find_element(By.LINK_TEXT, "some text")
except NoSuchElementException:
    pass

Если вы ищете более 1 элемента, вы получите пустой список:

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By

driver = webdriver.Firefox()
driver.get("https://www.theautomatedtester.co.uk")
elements = driver.find_elements(By.CSS_SELECTOR, "myShadowElements")

assert len(elements) == 0

Скоро в Selenium: Относительные локаторы


Они позволят вам искать элементы на странице по их относительному положению к другому элементу. Например, вы можете найти элемент выше и левее от другого элемента.

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import with_tag_name

driver = webdriver.Firefox()
driver.get("https://www.theautomatedtester.co.uk")
elements = driver.find_elements(
                    with_tag_name("td").above(
                    driver.find_element(By.ID, "center"))\
                    .to_right_of(driver.find_element(By.ID, "second")))

Для дальнейшего чтения


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

Эпизод 5 — Клик


В этом эпизоде ​​мы рассмотрим, что происходит при клике. В целях простоты мы будем рассматривать только работу, выполняемую с помощью element.click().

У вас есть элемент, по которому вы хотите кликнуть, что теперь?


Итак, вы нашли элемент, и вам нужно кликнуть по нему. Selenium предпримет следующие шаги, прежде чем он отработает клик.

Проверка, находится ли еще элемент на странице

Когда мы находили элемент, браузер возвратил представление этого элемента. Все последующие вызовы этого элемента должны быть проверены на факт того, что элемент все еще находится на странице. Если это не так, вы получите исключение StaleElementReferenceException.

Проверка видимость элемента

Как только мы убедились, что элемент все еще находится на странице, нам нужно убедиться, что элемент отображается. Это важно, так как мы не ожидаем, что пользователь нажмет на элемент, который не виден. Так как пользователь никогда там не кликнет, мы должны убедиться, что Selenium тоже не будет этого делать. Если он не отображается, вы получите исключение ElementNotVisibleException.

Прокрутка к элементу

Далее, нам нужно убедиться, что мы можем совершить прокрутку к элементу. Для правильного запуска всех событий элемент нам понадобится в области просмотра. Если элемент не находится в области просмотра после попытки прокрутки к нему, браузер возвратит исключение ElementNotInteractableException.

Проверка, является ли элемент интерактивным

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

Выключение некоторых событий

Теперь мы готовы запустить некоторые события. У нас есть специальный кейс для <option>, чтобы убедиться, что это родительский элемент, который получает события.

Затем мы вычисляем центр элемента и начинаем отправлять события. Вы можете прочитать, какие события мы отправляем в спецификации WebDriver.

А как насчет навигации?

Нам нужно указать браузеру следить за навигацией, происходящей в результате клика. Если клик вызывает навигацию, мы применяем ту же логику, как если бы мы сами осуществляли навигацию.

Для дальнейшего чтения


Вы можете прочитать спецификацию WebDriver для более подробной информации.



Подробнее о курсе




Как устроен Selenium: Эпизоды 1 — 2
OTUS. Онлайн-образование
Цифровые навыки от ведущих экспертов

Комментарии 0

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое