Легкое веб-тестирование с Python, Pytest и Selenium WebDriver, часть 5: создание теста Page Object Selenium при помощи Python |
05.08.2020 00:00 |
Автор: Энди Найт (Andy Knight) Тест-функция, которую мы написали в прошлый раз, была неплоха, но ее можно улучшить, переработав при помощи паттерна Page Object. Проблемы тестирования с WebDriver Для справки, вот нынешняя версия нашей тест-функции:
Все вызовы WebDriver делаются напрямую внутри тест-функции. Без комментариев этот код будет тяжело читать и понять. Вызовы WebDriver используют контр-интуитивные локаторы с низкоуровневыми командами взаимодействия. Тут отсутствует намерение – это тест поиска DuckDuckGo, а не набор кликов и ввода букв. К тому же локаторы веб-элементов могут дублироваться – например, локатор поисковой строки. Паттерн Page Object, также известный как "модель Page Object" – это паттерн проектирования, который извлекает взаимодействия веб-страницы с целью улучшить их читабельность и повторное использование. Страницы представлены как классы с атрибутами локаторов и методами взаимодействия. Вместо сырых вызовов WebDriver тесты вызывают методы объекта страницы. Паттерн Page Object, в целом, наиболее распространенный паттерн Web UI-автоматизации. Существует множество способов внедрения этого паттерна, но по большей части они очень похожи. Реструктуризация для Page ObjectНаш тест взаимодействует с двумя страницами – страницей поиска DuckDuckGo и страницей результатов. Давайте напишем класс page object для каждой из них. Создайте новую директорию pages/ в корневом каталоге проекта, и добавьте пустой файл __init__.py, чтобы сделать ее пакетом. В этом пакете вам нужно создать два файла - search.py и result.py. Директория вашего проекта должна выглядеть так:
Размещение относящихся к тесту модулей вне директории tests/ может показаться странным, однако помните, что pytest рекомендует не делать пакетом модуль тестов. Создание отдельного пакета для Page Object позволяет тест-модулям легко их импортировать. Это также усиливает разделение тест-кейсов и веб-взаимодействий. Такой компоновки директорий достаточно для нашего проекта, однако другие (особенно крупные) проекты могут потребовать другого размещения. Пожалуйста, ознакомьтесь с руководством pytest по хорошим интеграционным практикам для получения более подробной информации. Страница поискаСтраница поиска довольно проста. Наш тест взаимодействует с ней двумя способами – загружает и вводит поисковый запрос. Единственный локатор – строка поиска. Добавьте код в файл pages/search.py:
Несмотря на небольшой размер, этот класс – хороший пример того, как должен выглядеть Page Object. Его имя, DuckDuckGoSearchPage, уникально и внятно определяет страницу. В нем есть атрибуты локаторов (SEARCH_INPUT), инициализатор (__init__) и методы взаимодействия (load и search). Рассмотрим каждую часть отдельно. Атрибуты локаторов Единственный локатор в этом классе – это SEARCH_INPUT для поиска поля ввода поискового запроса. Это атрибут класса ("статический"), потому что значение должно быть одинаковым для всех страниц поиска. Он также написан как кортеж, потому что все локаторы состоят из двух частей: типа (вроде By.ID или By.XPATH) и запроса. Этот локатор находит элемент строки поиска по имени. Создание локаторов как кортежей атрибута класса делает их читабельными и доступными. Локаторы также всегда должны иметь интуитивно понятные имена. Инициализатор Всем Page Object необходима ссылка на копию WebDriver. Обычно она внедряется через конструктор, а здесь – передается в метод __init__ как параметр браузера, а затем хранится как атрибут self.browser. Инъекция зависимости позволяет Page Object полиморфически использовать любой тип WebDriver – неважно, ChromeDriver, IEDriver, или нечто иное. Она также позволяет тест-фреймворку контролировать настройку и очистку. Методы взаимодействия Методы взаимодействия должны иметь интуитивно понятные имена. Метод load переводит браузер на страницу поиска. Заметьте, что URL – это атрибут класса. Метод search вводит поисковый запрос в поле ввода, но теперь фраза параметризована – можно использовать любую фразу. Метод search также находит целевой элемент новаторским путем. Вместо использования метода find_element_by_name он пользуется более общим методом find_element, принимающим два аргумента – тип локатора и запрос. Эти аргументы соответствуют нашему кортежу SEARCH_INPUT. Оператор * расширяет self.SEARCH_INPUT до позиционных параметров для вызова метода. Здорово! Лично я люблю этот паттерн за то, что кортежи локаторов можно менять, и это не затронет код взаимодействия. Рефакторинг поискаДавайте переработаем шаги тест-кейса, включив наш новый Page Object поиска. Замените эти строки:
На новые вызовы page object:
Намного лучше! Это куда больше похоже на шаги тест-кейса, а не на программные вызовы. Страница результатовТеперь давайте напишем Page Object страницы результатов. Добавьте код в pages/result.py:
DuckDuckGoResultPage чуть-чуть сложнее DuckDuckGoSearchPage. Локатор LINK_DIVS следует паттерну кортежа, но локатор PHRASE_RESULTS ему не следует. Вместо этого он использует метод класса, возвращающий кортеж, и в результате поисковый запрос в его XPath можно параметризировать. Инициализатор тот же самый. Три метода взаимодействия находят элементы и возвращают значения. Заметьте, что методы взаимодействия не задают правил, а просто возвращают состояния. Правила – проблема тест-кейса, а не Page Object. Разные тесты могут использовать вызовы одних и тех же page object для проверки различных типов правил. Настало время для дополнительной доработки. Замените эти строки тест-функции:
На эти:
Вау! И снова вышло гораздо чище. Перезапуск тестаtests/test_web.py теперь должен выглядеть так:
Перезапустите тест, чтобы убедиться, что он работает:
Супер! Все работает. Больше тестов!Две последние части руководства прояснили тонкости того, что изначально казалось простеньким Web UI-тестом. Мы не только автоматизировали взаимодействия для поиска DuckDuckGo – мы сделали это, используя лучшие практики и паттерны дизайна! Теперь то, чему мы научились, можно применять в новых тестах. Вот какие тесты еще можно написать:
В следующий раз мы научимся конфигурировать выбор браузера и ожидания вне кода автоматизации, используя config-файлы. |