When I started to study testing automation, I could not understand - “what is Page Object and how to implement it in Python + pytest?”. Studying the Internet, I found an implementation in other languages and frameworks: educational articles that were incomprehensible to me. Therefore, I decided to write this analysis. The idea is to show the implementation in Python + pytest and explain it in an accessible language.
What is Page Object
This is a popular pattern that is the de facto standard in web product testing automation. The basic idea is to separate test logic from implementation.
Each project web page can be described as a class object. User interaction is described in class methods, and only business logic remains in the tests. This approach helps to avoid problems with tests when changing the layout of a web application. You need to correct only the class that describes the page.
Page Object defines the parts:
- Base Page \ Base Class - Implements the necessary methods for working with webdriver.
- Page Object \ Page Class - Implements methods for working with elements on web pages.
- Tests - Implements the tests described by the business logic of the test case.
Schema of the Page Object pattern.
To clearly explain the topic, we implement an automated test.
The theoretical part of the implementation
Steps :
- The user opens a browser;
- The user enters https://ya.ru/ in the address bar;
- The user enters the word “Hello” into the search bar;
- The user clicks the “Find” button.
Expected Result :
The user is redirected to the search. Search results have sub-items (video, pictures, etc.).
Check : on the search page there is a navigation bar and elements of “picture” and “video”.
The practical part of the implementation
To understand the article, you need to know the basic constructions of Python, OOP, understand the principles and functions of Selenium.
We will use the libraries: selenium and pytest. You can install them through the pip package manager.
pip install selenium pip install pytest
Also do not forget to download the driver for the browser. This article uses chrome webdriver. You can download it here . To work with it, put the file in the root directory of the project.
Create fixture
First you need to implement initialization for WebDriver. We will describe it in fixtures. Fixtures in pytest are functions that have their own periodicity of execution.
This is an alternative replacement for the SetUp and TearDown methods in unittest. Using fixtures, you can prepare the initial state of the system for testing.
In pytest there is a reserved name for the fixture file - conftest.py .
We create the conftest.py file and implement the function with the name - browser.
We mark it with the @ pytest.fixture decorator and pass the scope parameter with a session value. This means that this fixture function will be executed only 1 time per test session.
import pytest from selenium import webdriver @pytest.fixture(scope="session") def browser(): driver = webdriver.Chrome(executable_path="./chromedriver") yield driver driver.quit()
Next, we describe the part that will be executed before the tests. It initializes the webdriver with an indication of where the chromedriver is located. Next, we use the yield construct, which divides the function into parts - before the tests and after the tests.
In the “after tests” part, we call the quit function, which ends the session and kills the webdriver instance.
Base page
Create the BaseApp.py file. In the BasePage class, we define the basic methods for working with 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)
In the BasePage class, create a constructor that accepts a driver - an instance of webdriver. Specify base_url, which will be used to open the page.
Next, we create the methods find_element (searches for one element and returns it) and find_elements (searches for the set and returns as a list).
This is a wrapper over WebdriverWait, which is responsible for explicit expectations in Selenium.
In the function, we determine the time, which by default is 10 seconds. This is the time to search for the item. Go_to_site method - Calls the get function from WebDriver. The method allows you to go to the indicated page. We pass base_url to it.
Page object
Our web page class is implemented in the YandexPages.py file.
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
We create class YandexSeacrhLocators. It will only be for storing locators.
In the class, we describe the locators:
LOCATOR_YANDEX_SEARCH_FIELD - search string locator
LOCATOR_YANDEX_SEARCH_BUTTON - locator of the “Find” button
LOCATOR_YANDEX_NAVIGATION_BAR - navigation bar locator (Pictures, Videos, etc.)
We create the class SearchHelper, inherit from BasePage.
We implement auxiliary methods for working with search:
enter_word - searches for an element of the search string, clicks and enters the desired word into the search;
click_on_the_search_button - Searches for an element of a search button and clicks on it;
check_navigation_bar - Searches for navigation items and gets the text attribute. Creates a list and filters by condition. If the string length is greater than zero, it adds an item to the list. For example, redefine the default time by setting it to 2 seconds.
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
We create a test function test_yandex_seacrh, which will accept the browser fixture. Next, the first line creates the page object - yandex_main_page. From the object, we call methods for interacting with page elements. The function describes the upper level logic of user actions.
Let's transfer everything that we implemented to the scheme, similar to the Page Object scheme. Rename the blocks under the name of the files from the article.
As you can see, we were able to put the pattern into practice.
I’ll leave a link to the finished repository . Thanks for reading!