Transcripted Summary

When we automate web UI tests, we need a way to model interactions with the pages under test. The page object pattern is one of the simplest and most popular ways to do so.



A page object is an object representing a webpage or component. They have locators for finding elements as well as interaction methods.

Page objects make low-level Selenium WebDriver calls so that tests can make short, readable calls instead of complex ones.

Our test interacts with two pages — the search page and the result page.



When I write tests, I like to take a top down approach by naming page object methods first and then implementing them with Selenium WebDriver calls later. Like I said before, think before coding.

The search page needs two actions —loading the page and entering a search phrase on the page.

The result page doesn't make any actions, but it does return three values — the result link titles, the search input value and the page title.


We'll write page object stubs for both pages and then use them to write our test case function.

Let's look at the code.

In our project, I've created a directory called pages right here.



Notice that this directory is outside of the tests directory.

  • In theory, pages could be used by things other than tests.

  • We also need it outside because we want this pages directory to be a Python package.

  • Python packages are denoted by this __init__.py file (pronounced “dunder init”). It's just an empty file inside of a directory that says, "Hey, this is a Python package, and you can treat it as such, specifically for import statements."


Let's look at our search page.

Here, we have the stub for our search page object class.


# Example Code - pages/search.py

"""
This module contains DuckDuckGoSearchPage,
the page object for the DuckDuckGo search page.
"""

class DuckDuckGoSearchPage:

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

  def load(self):
    # TODO
    pass

  def search(self, phrase):
    # TODO
    Pass

I have some comments at the top just to denote what it is,

Then, class DuckDuckGoSearchPage.

And inside of here, I have our __init__ method, which acts like the constructor. Notice how it takes in the browser, which will be passed in from the test case.

I want to set my local self.browser variable to be whatever browser it's passed in. This is basically just an instance variable initialization.

Furthermore, I have two stubbed methods — one for loading the page and one for searching the page with a given textual phrase.

For now, I'm going to use Python's pass command to just “no-op” these methods, and I'll put a “TODO” comment so I know to come back and check them later.


Let's take a look at our result page as well.

I've put it in a separate Python module named result.py, but it looks very, very similar to the search page.


# Example Code - pages/result.py

"""
This module contains DuckDuckGoResultPage,
the page object for the DuckDuckGo search result page.
"""

class DuckDuckGoResultPage:

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

  def result_link_titles(self):
    # TODO
    return []

  def search_input_value(self):
    # TODO
    return ""

  def title(self):
    # TODO
    return ""

Comments at the top, class name, the same __init__ method for the WebDriver browser object.

The only difference here are the stubbed methods.

Notice here instead of no-op’ing, I'm returning something. That's because, as we said for our result page, the interaction methods don't necessarily do something, but rather they get something.

I don't want to fill in that quite yet because I just want to have a stub, so I'm going to put my “TODO” comments and I'm going to return appropriate values.

  • So, for result_link_titles, since this is supposed to represent a list of titles, I'll return an empty list.

  • And for the other two methods, since they're supposed to return String-based values, I'll just return empty Strings for now.


Now, that we have our page object stubs, we can actually implement the Python code in our test case.

Let's open test_search.py. Here, we can see the entire test case is implemented.


# Example Code - tests/test_search.py

"""
These tests cover DuckDuckGo searches.
"""

from pages.result import DuckDuckGoResultPage
from pages.search import DuckDuckGoSearchPage

def test_basic_duckduckgo_search(browser):

  search_page = DuckDuckGoSearchPage(browser)
  result_page = DuckDuckGoResultPage(browser)

  PHRASE = "panda"

  # Given the DuckDuckGo home page is displayed
  search_page.load()

  # When the user searches for "panda"
  search_page.search(PHRASE)

  # Then the search result title contains "panda"
  assert PHRASE in result_page.title()

  # And the search result query is "panda"
  assert PHRASE == result_page.search_input_value()

  # And the search result links pertain to "panda"
  titles = result_page.result_link_titles()
  matches = [t for t in titles if PHRASE.lower() in t.lower()]
  assert len(matches) > 0

  # TODO: Remove this exception once the test is complete
  raise Exception("Incomplete Test")

At the top, I have my comments. Then I have my import statements.

Notice how I'm referring to the pages package from pages.result and from pages.search to import the different page object classes that I need in my test case function.

We will take the browser that we get from the fixture. That's the WebDriver object as you recall, and I will use it to construct my 2 different search pages.

Then each of my steps is nothing more than page object calls.

  • Given the DuckDuckGo home page is displayed — that's search_page.load.

  • When the user searches for a phrase —that's search_page.search with the “PHRASE” that I want.

  • Then the search result title contains my phrase —assert that your “PHRASE” is in the result_page.title.

  • And the search result query is "panda" — assert that your “PHRASE” equals the result_page.search_input_value.

  • Finally, for title and result page, result_link_titles, doing a loop over everything — assert that your lowercase version of your phrase is in the lowercase version of the title.

We have completely implemented all of our steps using page object calls and maybe a few other little Python tricks.


That's pretty nice because from now on, the only things left to implement are the page object calls themselves with Selenium WebDriver.

Finally, just note that even though I've finished the code of the test case function, the test case is not fully implemented yet, so I'm going to leave in my exception for now.



Resources



© 2021 Applitools. All rights reserved. Terms and Conditions Privacy Policy GDPR