Selenium + Python + POM + Business Layer: The final abstraction

In our previous set of tutorials, we have created a basic framework that will keep us covered for the most part. However, there is always room to improve. One of the disadvantages of how the framework is built is that the test class is doing more than it should. As stated in the naming convention, the test class should only execute a test instead of running preconditions to get where the assertion really needs to go.

The thing is, the page object should not be responsible for that either, as its responsibility is to manage the attributes and methods for itself. So, this leads us to the business layer solution, which is a man-in-the-middle that will be in charge of resolving this situation. Let's update our project.

Step-by-step guide:

  1. Create a python package to hold the business layer classes -- Right-click on the project -> New -> Python Package and called it "businessLayer" image.png
  2. Create a new class for the search business layer implementation -- Right-click on the business layer package -> New -> Python File and call it "search_bl" image.png
  3. In this class, we want to add all of those methods that are currently in the test class and are calling the page object: the page load, the actual search execution and the corresponding validations.
from pages.search import SearchPage

class SearchBL:

    result_page = None

    def __init__(self, browser):
        self.browser = browser

    def search_for(self, phrase):
        search_page = SearchPage(self.browser)
        search_page.load()
        self.result_page = search_page.search(phrase)

    def result_found(self):
        return self.result_page.search_input_value()

    def page_title(self):
        return self.result_page.title()

    def list_of_results(self, phrase):
        titles = self.result_page.result_link_titles()
        matches = [t for t in titles if phrase.lower() in t.lower()]
        return len(matches)

Finally, let's update the test class to make use of the business layer

import pytest

from business_layer.search_bl import SearchBL


@pytest.mark.parametrize('phrase', ['panda', 'python', 'lda'])
def test_basic_search(browser, phrase):
    search = SearchBL(browser)
    search.search_for(phrase)

    assert phrase == search.result_found()
    assert search.list_of_results(phrase) > 0

Note how much the test has changed in terms of number of lines, complexity and responsibilities. The test only performs the search and validate the results. There's nothing else it should do.

Perhaps this example falls short in terms of how much advantage we can take on it, however on more extensive scenarios it can really be useful. Also, not all test classes would require a business logic layer, it could be more overhead than necessary, but that's only something you'll find out with time.