pythonselenium-webdriverpageobjects

How to abstract web elements and actions in page object model (Selenium+python)?


I have Options table with ON/OFF option (checkboxes, radio buttons and text fields). The table has 30 rows. I stored locators as ENUM

class OptionType(Enum):
    PAYMENT_TYPE_CREDIT_CARD = "Credit_Card"
    PAYMENT_TYPE_ECP = "ECP"
    PAYMENT_TYPE_SAFETECH = "SAFETECH"
    PAYMENT_TYPE_ON = "yPaymentType"
    PAYMENT_TYPE_OFF = "nPaymentType"
    USE_POST_MESSAGE_ON = "yPostMessage"
    USE_POST_MESSAGE_OFF = "nPostMessage"
    ORDER_ABSTRACTION_ON = "yuID"
    ORDER_ABSTRACTION_OFF = "nuID"

What is the best way to add methods to access those locators and make some actions over them? If I create the methods for each element in the same class it will be too long (more than 300 lines)

I tried to store elements directly by using XPATH and only elements take more than 300 lines. I need methods for actions as well.


Solution

  • Locators should be stored as By. That's the class designed to contain locators in Selenium, e.g.

    username_locator = (By.ID, "username")
    

    which can then be used like

    driver.find_element(username_locator).send_keys("username")
    

    For the other part, you should research the page object model. It's the generally accepted best practice for organizing your code. A page object is a class that contains all locators and methods for a given page (or portion of a page, e.g. header, footer, left or top nav, etc.) that is designed to be reusable.

    All locators for that page should be stored in the page object as By. Each action that a user can take on that page should be represented as a method in the page object. This organization structure makes maintenance much cleaner and clearer.

    A simple example

    There's a test page here that simulates a login page. Below I've created a sample page object for the login page and a sample script using the Login page page object.

    login_page.py, the page object

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.wait import WebDriverWait
    
    class LoginPage:
        '''Page object for the login page'''
        def __init__(self, driver: webdriver):
            self.driver = driver
            self.password_locator = (By.ID, 'password')
            self.username_locator = (By.ID, 'username')
            self.login_button_locator = (By.CSS_SELECTOR, "button")
    
        def login(self, username: str, password: str):
            '''Logs in with the provided credentials'''
            wait = WebDriverWait(self.driver, 10)
            wait.until(EC.visibility_of_element_located(self.username_locator)).send_keys(username)
            wait.until(EC.visibility_of_element_located(self.password_locator)).send_keys(password)
            wait.until(EC.element_to_be_clickable(self.login_button_locator)).click()
    

    pom_test.py, the script that uses the page object

    from selenium import webdriver
    
    from page_object_model.login_page import LoginPage
    
    URL = 'https://the-internet.herokuapp.com/login'
    driver = webdriver.Chrome()
    driver.maximize_window()
    driver.get(URL)
    
    USERNAME = "tomsmith"
    PASSWORD = "SuperSecretPassword!"
    
    login_page = LoginPage(driver)
    login_page.login(USERNAME, PASSWORD)
    
    driver.quit()
    

    Hopefully that helps make sense of the page object model.


    In general, don't worry about how many lines of code are in your page object. If it's a really large, complex page then you're going to have a lot of code in there. Just make sure you're only including methods and locators that correspond to only that page object. Each page should have it's own page object... keeping those carefully separated will help reduce the amount of code in each class.