The Page Object Model – Testing Axial-Style Part 2

pom

In Part 1 we learned how to use Selenium IDE to automate the testing of common actions within the browser. In Part 2 we’ll take a programmatic approach to browser automation using the Selenium Webdriver API and the Page Object Model.



Webdriver Example


Integrating Selenium Webdriver API calls into your unit tests is easy. If we want to use Webdriver to automate a user searching “Axial Network” on Google, all we have to do is…
from selenium import webdriver

driver = webdriver.Firefox() # Open a Firefox browser
driver.get('http://www.google.com') # Go to page
driver.find_element_by_id('gbqfq').send_keys('Axial Network') # Type search term
driver.find_element_by_id('gbqfb').click() # Click search button

While this example uses Python to perform actions in Firefox, its also possible to use Java, C# or Ruby to perform actions in Chrome, Safari or IE. Selenium IDE test cases can be converted to Webdriver code by going to File > Export Test Case As… > Python 2 (WebDriver). The example here shows the result of converting our test case from Part 1, which tests weather.com’s ability to get the weather in New York.

Introducing the POM


How can we piece together our test cases in a way that produces a flexible and maintainable testing suite? What do we mean by “flexible and maintainable”?


1. One that requires minimal changes when application code changes


2. One that is reusable, in that testing logic can be shared across multiple test cases


3. One that can be run on multiple browsers


This is where the Page Object Model (POM) comes in. The POM is an approach that focuses on modeling all the attributes and actions that comprise each page, then writing test cases that perform those actions and verify results. This approach makes a lot of sense given we are trying to simulate user actions, which can be described in the form of “go to that page and do this, go to this page and do that, and so on”.


As an example lets model a login page…

class LoginPage(Page):

  """
  Model the login page.
  """
  url = '/login/'

  # Locators
  _email_loc = (By.ID, 'id_email')
  _password_loc = (By.ID, 'id_password')
  _submit_loc = (By.CSS_SELECTOR, '#sign_in_bttn')

  # Actions
  def open(self):
      self._open(self.url)

  def type_email(self, email):
      self.find_element(*self._email_loc).send_keys(email)

  def type_password(self, password):
      self.find_element(*self._password_loc).send_keys(password)

  def submit(self):
      self.find_element(*self._submit_loc).click()


The full code, including the base Page class, can be found here. For each page we identify three important things:


1. The url of the page


2. The page locators (selectors of all  HTML elements that enable user interaction)


3. All the different actions that can be performed on the page


Next we write some testing logic that uses this model to ensure users can login. The testing logic here is the assumption that if a user (with valid credentials) submits the form and does not get past the login page then login is broken. Failure to do so could be the result of a front-end issue, a backend issue, a session service issue, etc. The beauty here is that such logic is completely separated from page structure.
  driver = webdriver.Firefox()

login_page = LoginPage(driver)
  login_page.open()
  login_page.type_email('scuba.steve@axial.net')
  login_page.type_password('wouldntyouliketoknow')
  login_page.submit()
  assert not login_page.on_page(), "Couldn't get past the login page"

Now that we’ve tested our login page, lets see how using the POM allows us to reuse this functionality in other test cases. A test case that visits a page and downloads a file on Axial could look something like…
  driver = webdriver.Firefox()

login_page = LoginPage(driver)
  login_page.open()
  login_page.type_email('scuba.steve@axial.net')
  login_page.type_password('wouldntyouliketoknow')
  login_page.submit()
  assert not login_page.on_page(), "Couldn't get past the login page"

download_page = DownloadFilePage(driver)
download_page.open()
download_page.download()


Next Steps


Should each test case explicitly instantiate, open and submit the login page? No. There must be a way for test cases to implicitly include common testing logic. In Part 3 we will see how pytest, our favorite Python unit testing framework, can be used to automatically log users in before each test case is executed.