Разделы портала

Онлайн-тренинги

.
Легкое веб-тестирование с Python, Pytest и Selenium WebDriver, часть 4: первый тест при помощи Selenium WebDriver, Python и Chrome
22.07.2020 00:00

Автор: Энди Найт (Andy Knight)
Оригинал статьи
Перевод: Ольга Алифанова

Теперь WebDriver готов к работе – давайте напишем наш первый web-тест! Это будет простой поиск DuckDuckGo. DuckDuckGo – это поисковик, который не отслеживает пользовательские данные. Пользователи могут вводить запросы и получать ссылки на соответствующие сайты, как и в любой другой поисковой системе.

Прежде чем писать код автоматизации, всегда стоит записать тест-процедуру обычным языком. Такое создание процедуры заставляет нас в первую и главную очередь думать о тестируемом поведении. Вот наша тест-процедура:

  1. Перейти на домашнюю страницу DuckDuckGo.
  2. Ввести поисковый запрос.
  3. Убедиться, что
    1. Результаты появились на странице результатов.
    2. Поисковый запрос появился в строке поиска.
    3. Как минимум один результат поиска содержит поисковый запрос.

Это простенький тест, однако он полностью покрывает типичное поисковое поведение.

Код

Добавьте тест-функции в tests/test_web.py:

  1. def test_basic_duckduckgo_search(browser):
  2. URL = 'https://www.duckduckgo.com'
  3. PHRASE = 'panda'
  4. browser.get(URL)
  5. search_input = browser.find_element_by_id('search_form_input_homepage')
  6. search_input.send_keys(PHRASE + Keys.RETURN)
  7. link_divs = browser.find_elements_by_css_selector('#links > div')
  8. assert len(link_divs) > 0
  9. xpath = f"//div[@id='links']//*[contains(text(), '{PHRASE}')]"
  10. results = browser.find_elements_by_xpath(xpath)
  11. assert len(results) > 0
  12. search_input = browser.find_element_by_id('search_form_input')
  13. assert search_input.get_attribute('value') == PHRASE

Функция test_basic_duckduckgo_search внедряет нашу тест-процедуру согласно паттерну "Подготовка-Действие-Проверка". Заметьте, что тест-функция объявляет параметр browser, имеющий имя, аналогичное фикстуре для настройки и очистки ChromeDriver. Pytest автоматически вызовет фикстуру и внедрит отсылку к WebDriver при каждом запуске этого теста. Затем тест-функция использует переменную browser для осуществления ряда вызовов WebDriver. Давайте посмотрим, как они работают.

Подготовка

URL = 'https://www.duckduckgo.com'

Тест объявляет URL для домашней страницы DuckDuckGo как переменную для читабельности и поддерживаемости.

PHRASE = 'panda'

Это поисковый запрос, который будет использоваться в тесте. Так как тест покрывает "базовый" поиск, запрос не особенно важен. Тесты, проверяющие более сложное поведение, должны использовать более сложные запросы. Тест снова объявляет ее в начале тест-функции для читабельности и поддерживаемости.

browser.get(URL)

Начальная точка для теста – это домашняя страница DuckDuckGo. Этот вызов открывает заданный URL в браузере. Имейте, однако, в виду – этот вызов не ждет загрузки страницы. Он просто инициирует взаимодействие загрузки.

Действие

search_input = browser.find_element_by_id('search_form_input_homepage')

Первый шаг в автоматизации веб-взаимодействий – это поиск целевого элемента (или элементов). Элементы могут как появляться на странице, так и не появляться. Автоматизация должна использовать локатор для поиска элемента, если он существует, а затем сконструировать объект, представляющий этот элемент. Существует множество типов локаторов – ID, имена классов, CSS-селекторы, XPathЛокаторы найдут все совпадающие элементы на странице – их может быть больше одного. Попробуйте использовать наиболее простой локатор, уникально идентифицирующий целевой элемент.

Для создания локаторов нужно увидеть HTML-структуру страницы. Chrome DevTools упрощает исследование разметки любых страниц. Просто кликните на странице правой кнопкой и выберите "Inspect". Вы увидите все элементы на вкладке "Elements". Для нашего теста нам нужно поле ввода поискового запроса на домашней странице DuckDuckGo. Этот элемент имеет атрибут id со значением “search_form_input_homepage”, как показано ниже:

Мы можем получить этот элемент, используя метод WebDriver find_element_by_id. Переменная search_input присваивается объекту, представляющему строку ввода запроса на странице. Помните, что так как ожидания у копии WebDriver неявные, он будет ожидать появления элемента ввода поискового запроса вплоть до десяти секунд.

search_input.send_keys(PHRASE + Keys.RETURN)

Когда элемент у нас на руках, мы можем инициировать взаимодействия с ним. Метод send_keys отправляет последовательность нажатий клавиш элементу search input – как сделал бы реальный пользователь при помощи клавиатуры. Вызов выше посылает поисковый запрос. Ключ RETURN в конце запускает поиск.

Проверка (1)

link_divs = browser.find_elements_by_css_selector('#links > div')

Страница результатов должна отображать элемент div с ID "links", у которого есть дочерний элемент div для каждой результирующей ссылки. CSS-селектор выше находит все div результирующих ссылок. Заметьте, что "элементов" тут несколько – этот вызов вернет список.

assert len(link_divs) > 0

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

Проверка (2)

xpath = f"//div[@id='links']//*[contains(text(), '{PHRASE}')]"

Мы проверили, что какие-то результаты появились, но надо еще и убедиться, что результаты соответствуют нашему поисковому запросу. Для выявления ссылок, содержащих в тексте наш запрос, можно использовать XPath. XPath сложнее имен и CSS-селекторов, но и мощность у них повыше. Наш XPath ищет любой div с ID "links", а затем – его потомков, содержащих поисковый запрос.

("f" в начале строки для вас в новинку? Это f-строка, она упрощает форматирование!)

phrase_results = browser.find_elements_by_xpath(xpath)

Этот вызов находит все элементы, используя предварительно связанный XPath. Мы могли совместить эти две строки в одну, но разделение строк делает код более читабельным, и более "пайтоновским".

assert len(phrase_results) > 0

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

Проверка (3)

search_input = browser.find_element_by_id('search_form_input')

Финальная проверка убеждается, что поисковая строка все еще отображается в строке ввода. Строчка выше идентична вызову поиска элемента из стадии подготовки – она снова находит элемент строки поиска. Почему бы не воспользоваться объектом search_input снова? К сожалению, предыдущий элемент протух. Страница изменилась с домашней страницы на страницу результатов. Несмотря на то, что элементы выглядят одинаково, они не идентичны, и нам нужен новый локатор. Поэтому нам нужно получать свежий элемент.

assert search_input.get_attribute('value') == PHRASE

Текст, введенный в элемент поля ввода, доступен как его атрибут “value”. Эта строчка убеждается, что атрибут “value” равен поисковому запросу. Она проверяет, что запрос не испарился.

Проверка и запуск веб-теста

Полный код for tests/test_web.py теперь должен выглядеть так (с комментариями для ясности):

  1. """
  2. Этот модуль содержит веб-тест-кейсы для этого руководства.
  3. Тесты используют Selenium WebDriver с Chrome и ChromeDriver.
  4. Фикстуры настраивают и очищают копию ChromeDriver.
  5. """
  6. import pytest
  7. from selenium.webdriver import Chrome
  8. from selenium.webdriver.common.keys import Keys
  9. @pytest.fixture
  10. def browser():
  11. # Инициализация ChromeDriver
  12. driver = Chrome()
  13. # Неявное ожидание готовности элементов перед попыткой взаимодействия
  14. driver.implicitly_wait(10)
  15. # Возвращение объекта драйвера в конце настройки
  16. yield driver
  17. # Для очистки покиньте драйвер
  18. driver.quit()
  19. def test_basic_duckduckgo_search(browser):
  20. # Настройте данные для тест-кейса
  21. URL = 'https://www.duckduckgo.com'
  22. PHRASE = 'panda'
  23. # Перейдите на домашнюю страницу DuckDuckGo
  24. browser.get(URL)
  25. # Найдите элемент ввода поискового запроса
  26. # В модели DOM его атрибут 'id' равен 'search_form_input_homepage'
  27. search_input = browser.find_element_by_id('search_form_input_homepage')
  28. # Отправьте поисковый запрос в поле ввода и нажмите ENTER
  29. search_input.send_keys(PHRASE + Keys.RETURN)
  30. # Убедитесь, что результаты появились на странице результатов
  31. link_divs = browser.find_elements_by_css_selector('#links > div')
  32. assert len(link_divs) > 0
  33. # Убедитесь, что как минимум один результат поиска содержит поисковый запрос
  34. xpath = f"//div[@id='links']//*[contains(text(), '{PHRASE}')]"
  35. phrase_results = browser.find_elements_by_xpath(xpath)
  36. assert len(phrase_results) > 0
  37. # Убедитесь, что поисковый запрос сохранился
  38. search_input = browser.find_element_by_id('search_form_input')
  39. assert search_input.get_attribute('value') == PHRASE

Давайте запустим тест, чтобы убедиться, что он работает:

  1. $ pipenv run python -m pytest
  2. ============================= test session starts ==============================
  3. platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
  4. rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
  5. collected 9 items
  6. tests/test_math.py ........ [ 88%]
  7. tests/test_web.py . [100%]
  8. =========================== 9 passed in 6.10 seconds ===========================

При запуске веб-теста откроется Google Chrome. Вы увидите, как он автоматически введет поисковый запрос, дождется страницы результатов, а затем закроет браузер. Легко и просто!

Если тест не запустился, проверьте:

  • Есть ли на тест-машине установленный Chrome
  • Есть ли ChromeDriver в системном пути
  • Соответствует ли версия ChromeDriver версии Chrome
  • Нет ли проблем с разрешениями доступа к файлам
  • Не блокируются ли порты файерволом
  • Верен ли код теста.

Как насчет улучшений?

Поздравляю с созданием вашего первого Web UI-теста! Это, безусловно, занимает больше времени, нежели юнит-тесты.

Несмотря на то, что тест успешно запускается, мы можем сделать кое-что еще, дабы улучшить код. В следующий раз мы проведем рефакторинг этого теста, используя паттерн Page Object.

Обсудить в форуме