python

Реализация паттерна Page Object на Python + pytest

  • воскресенье, 20 октября 2019 г. в 00:32:15
https://habr.com/ru/post/472156/
  • Python
  • Тестирование веб-сервисов


Когда я начинал изучать автоматизацию тестирования, не мог понять — “что такое Page Object и как его реализовать на Python + pytest?”. Штудируя интернет, нашел реализацию на других языках и фреймворках: обучающие статьи, которые были непонятны для меня. Поэтому решил написать этот разбор. Идея — показать реализацию на Python + pytest и объяснить ее доступным языком.


Что такое Page Object


Это популярный паттерн, который является де-факто стандартом в автоматизации тестирования веб-продуктов. Основная идея состоит в том, чтобы разделить логику тестов от реализации.


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


Page Object определяет в себе части:


  • Base Page \ Base Class — Реализует в себе необходимые методы для работы с webdriver.
  • Page Object \ Page Class — Реализует методы для работы с элементами на веб-страницах.
  • Tests — Реализует тесты, описанные бизнес-логикой тест-кейса.

Схема паттерна Page Object.
image
Чтобы наглядно объяснить тему, реализуем автоматизированный тест.


Теоретическая часть реализации


Шаги:


  1. Пользователь открывает браузер;
  2. Пользователь вводит в адресную строку https://ya.ru/;
  3. Пользователь вводит слово “Hello” в строку поиска;
  4. Пользователь нажимает кнопку “Найти”.

Ожидаемый результат:
Пользователь перенаправлен на выдачу поиска. Выдача поиска имеет подпункты (видео, картинки и тд.).


Проверка: на странице поиска присутствует бар навигации и элементы “картинки” и “видео”.


Практическая часть реализации


Для понимания статьи необходимо знать базовые конструкции Python, ООП, понимать принципы и функции Selenium.


Будем использовать библиотеки: selenium и pytest. Установить их можно через пакетный менеджер pip.


pip install selenium
pip install pytest

Так же не забываем скачать драйвер для браузера. В статье используется chrome webdriver. Скачать его можно по ссылке. Для работы с ним положите файл в корневой каталог проекта.


Создаем фикстуру


Для начала необходимо реализовать инициализацию для WebDriver. Описывать её будем в фикстуре. Фикстуры в pytest — функции которые имеют свою периодичность выполнения.
Это альтернативная замена SetUp и TearDown методов в unittest. С помощью фикстуры, можно подготовить начальное состояние системы для проведения тестирования.


В pytest есть зарезервированное имя для файла с фикстурами — conftest.py.


Создаем файл conftest.py и реализуем функцию c именем — browser.


Помечаем ее декоратором @pytest.fixture и передаем параметр scope со значением session. Это означает что данная функция-фикстура будет исполнятся только 1 раз за тестовую сессию.


import pytest
from selenium import webdriver

@pytest.fixture(scope="session")
def browser():
    driver = webdriver.Chrome(executable_path="./chromedriver")
    yield driver
    driver.quit()

Далее мы описываем часть, которая будет выполнятся перед тестами. В ней происходит инициализация webdriver с указанием где располагается chromedriver. Далее используем конструкцию yield, которая разделяет функцию на часть — до тестов и после тестов.


В части “после тестов” мы вызываем функцию quit, которая завершает сессию и убивает экземпляр webdriver.


Base Page


Создаем файл BaseApp.py. В классе BasePage определяем базовые методы для работы с WebDriver.


from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class BasePage:

    def __init__(self, driver):
        self.driver = driver
        self.base_url = "https://ya.ru/"

    def find_element(self, locator,time=10):
        return WebDriverWait(self.driver,time).until(EC.presence_of_element_located(locator),
                                                      message=f"Can't find element by locator {locator}")

    def find_elements(self, locator,time=10):
        return WebDriverWait(self.driver,time).until(EC.presence_of_all_elements_located(locator),
                                                      message=f"Can't find elements by locator {locator}")

    def go_to_site(self):
        return self.driver.get(self.base_url)

В классе BasePage создаем конструктор, который принимает driver — экземпляр webdriver. Указываем base_url, который будет использоваться для открытия страницы.


Далее создаем методы find_element (ищет один элемент и возвращает его) и find_elements (ищет множество и возвращает в виде списка).


Это обертка над WebdriverWait, который отвечает за явные ожидания в Selenium.


В функции определяем время, которое по умолчанию равно 10-и секундам. Это время для поиска элемента. Метод go_to_site — вызывает функцию get из WebDriver. Метод позволяет перейти на указываемую страницу. Передаем в него base_url.


Page object


Наш класс для веб-страницы реализуется в файле YandexPages.py


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

class YandexSeacrhLocators:
    LOCATOR_YANDEX_SEARCH_FIELD = (By.ID, "text")
    LOCATOR_YANDEX_SEARCH_BUTTON = (By.CLASS_NAME, "search2__button")
    LOCATOR_YANDEX_NAVIGATION_BAR = (By.CSS_SELECTOR, ".service__name")

class SearchHelper(BasePage):

    def enter_word(self, word):
        search_field = self.find_element(YandexSeacrhLocators.LOCATOR_YANDEX_SEARCH_FIELD)
        search_field.click()
        search_field.send_keys(word)
        return search_field

    def click_on_the_search_button(self):
        return self.find_element(YandexSeacrhLocators.LOCATOR_YANDEX_SEARCH_BUTTON,time=2).click()

    def check_navigation_bar(self):
        all_list = self.find_elements(YandexSeacrhLocators.LOCATOR_YANDEX_NAVIGATION_BAR,time=2)
        nav_bar_menu = [x.text for x in all_list if len(x.text) > 0]
        return nav_bar_menu

Создаем класс YandexSeacrhLocators. Он будет только для хранения локаторов.
В классе описываем локаторы:


LOCATOR_YANDEX_SEARCH_FIELD — локатор поисковой строки
LOCATOR_YANDEX_SEARCH_BUTTON — локатор кнопки “Найти”
LOCATOR_YANDEX_NAVIGATION_BAR — локатор бара навигации (Картинки, Видео и т.д.)


Создаем класс SearchHelper, наследуемся от BasePage.


Реализуем вспомогательные методы для работы с поиском:
enter_word — ищет элемент строки поиска, кликает и вводит в поиск необходимое слово;
click_on_the_search_button — ищет элемент кнопки поиска и кликает на неё;
check_navigation_bar — ищет элементы навигации и получает атрибут text. Создает список и фильтрует по условию. Если длина строки больше нуля, то добавляет элемент к списку. Для примера, переопределим время по умолчанию установив его — 2 сек.


Tests


from YandexPages import SearchHelper

def test_yandex_search(browser):
    yandex_main_page = SearchHelper(browser)
    yandex_main_page.go_to_site()
    yandex_main_page.enter_word("Hello")
    yandex_main_page.click_on_the_search_button()
    elements = yandex_main_page.check_navigation_bar()
    assert "Картинки" and "Видео" in elements

Создаем тестовую функцию test_yandex_seacrh, которая будет принимать фикстуру browser. Далее первой строчкой создаем объект страницы — yandex_main_page. Из объекта вызываем методы взаимодействия с элементами страницы. В функции описывается верхнеуровневая логика действий пользователя.


Перенесем все что реализовали на схему, аналогично схеме Page Object. Переименуем блоки под название файлов из статьи.


image


Как видите, нам удалось реализовать паттерн на практике.


Оставлю ссылку на готовый репозиторий. Спасибо за прочтение!