Leveling up POM - BaseTest

With the fixtures cleaned up. It is time to update the test to use our new implementation. To do so, we'll create a BaseTest to follow the same principle as BasePage. All the Tests will inherit from that class which will make the process of setting up and closing transparent for the test.

Pre-conditions:

  1. You should have completed the series where the project has been built structured for POM.

We cleared our initial fixtures because we are going to change the approach for clarity. However, there is one fixture that we'll be including that is going to drive our tests, and this way we don't have to worry about passing the different variables to the test methods.

a. Under the tests folder, create a base_test.py file. b. Add the code below to create an empty class definition, but in the background what is going to do is run the driver initialization via the fixture we created in the previous step.

import pytest


@pytest.mark.usefixtures('init_driver')
class BaseTest:
    pass

c. Let's go to the test_web.py first and update it so that it inherits from the BaseTest that we just created and then update the test method as follows:

import pytest

from pages.search import DuckDuckGoSearchPage
from tests.base_test import BaseTest

class TestBasicSearch(BaseTest):

    def test_basic_duckduckgo_search(self):
        # Set up test case data
        PHRASE = "panda"

        # Search for the phrase
        search_page = DuckDuckGoSearchPage(self.driver)
        result_page = search_page.search(PHRASE)

        # Verify that results appear   
        assert result_page.link_div_count() > 0
        assert result_page.phrase_result_count(PHRASE) > 0
        assert result_page.search_input_value() == PHRASE

Things to note from above:

  • The most important detail,, now we have a class called TestBasicSearch which inherits from BaseTest and inside it, we have our test definition as before.
  • We removed loading the URL from the test. It's all handled in the fixture now.
  • Change the initialization of the DuckDuckGoSearchPage to use our global driver variable.
  • There's no need to initialize the DuckDuckGoResultPage because is going to be initialized in the search method on the SearchPage.

d. Now it is also required to apply some changes to the pages. On the SearchPage:

from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from pages.base_page import BasePage
from pages.result import DuckDuckGoResultPage

class DuckDuckGoSearchPage(BasePage):
    SEARCH_INPUT = (By.ID, "search_form_input_homepage")

    def __init__(self, driver):
        super().__init__(driver)

    def search(self, phrase):
        self.type_on_element(self.SEARCH_INPUT, phrase + Keys.RETURN)
        return DuckDuckGoResultPage(self.driver)
  • The constructor now receives the global variable we set up in the fixture.
  • There's no need for a method to load the URL, because is done in the fixture.
  • We added a return statement for the search method so that we have a new instance of the next page we are going to (the results page). This is according to the POM best practices.

e. Next step, we also need to update the ResultPage:

from selenium.webdriver.common.by import By
from pages.base_page import BasePage

class DuckDuckGoResultPage(BasePage):
    LINK_DIVS = (By.CSS_SELECTOR, "#links > div")
    SEARCH_INPUT = (By.ID, "search_form_input")

    @classmethod
    def PHRASE_RESULTS(cls, phrase):
        xpath = f"//div[@id='links']//*[contains(text(), '{phrase}')]"
        return (By.XPATH, xpath)

    def __init__(self, driver):
        super().__init__(driver)

    def link_div_count(self):
        return len(self.get_elements(self.LINK_DIVS))

    def phrase_result_count(self, phrase):
        return len(self.get_elements(self.PHRASE_RESULTS(phrase)))

    def search_input_value(self):
        search_input = self.get_element(self.SEARCH_INPUT)
        return search_input.get_attribute("value")
  • The only change here is to update the constructor to use our global variable from the fixture.

f. The final step would be to update your BasePage to use the driver variable instead of the browser local variable.

That's it! You have successfully upgraded your POM to a more adequate architecture.