5

We had a discussion between several automation engineers on page object design, specifically about how many layers it should have and what are the responsibilities of the layers. I hope you guys could provide some insight about which way you prefer and why. Below you can find some examples we discussed in simplified Pseudo Python.

Which should I use in which circumstances, and why?

Case1:

Most popular case, based on examples found via Google/SQA exchange. Screen class has two responsibilities: element location & functional logic.

login_screen.xyz

class LoginScreen(BaseScreen):

  username_input = find_element(By.ID, 'username')
  password_input = find_element(By.ID, 'password')
  submit_button = find_element(By.ID, 'submit')

  def login(self, credentials):
    username_input.send_keys(credentials.username)
    password_input.send_keys(credentials.password)
    submit_button.click
  end

test_login.xyz

  def test_login:
    credentials = { 'username': 'asdf', 'password': 'asdf'}
    login_screen = LoginScreen(self.driver)
    login_screen.login(credentials)
  end

Case2:

Screen class has only one responsibility: element location. Interactions with elements are performed directly from the tests.

login_screen.xyz

class LoginScreen(BaseScreen):

  def login_input(self):
    self.find_element(By.ID, 'username')
  end

  def password_input(self):
    self.find_element(By.ID, 'password')
  end

  def submit_button(self):
    self.find_element(By.ID, 'submit')
  end

test_login.xyz

  def test_login:
    login_screen = LoginScreen(self.driver)
    login_screen.login_input.send_keys('asdf')
    login_screen.password_input.send_keys('asdf')
    login_screen.submit_button.click
  end

Case3:

3 layer structure where we have element definitions, interactions with elements and tests that work only with interactions.

login_screen.xyz

class LoginScreen(BaseScreen):

  def login_input(self):
    self.find_element(By.ID, 'username')
  end

  def password_input(self):
    self.find_element(By.ID, 'password')
  end

  def submit_button(self):
    self.find_element(By.ID, 'submit')
  end

login_steps.xyz

class LoginSteps(LoginScreen):

  def fill_username(self, username):
    self.login_input.send_keys(text)
  end

  def fill_password(self, password)
    self.password_input.send_keys(password)
  end 

  def click_submit(self)
    self.submit_button.click
  end

test_login.xyz

class TestLoginScreen:

  def test_login:
    login_screen = LoginSteps(self.driver)
    login_screen.fill_username('asdf')
    login_screen.fill_password('asdf')
    login_screen.click_submit
  end
  • Edited to make answerable. – Kate Paulk Sep 11 '17 at 11:40
  • 1
    Think of the dependencies. Generally speaking, page objects depend on the selenium library, and as such should do all the selenium stuff (IE. sendkeys). Test scripts depend on the test framework, and the page objects. As such those should be responsible for the asserts and test related stuff. If you mix all that crap together, you have a difficult time changing up your dependencies if you ever need to, and if you were unit testing your PO framework, that becomes really difficult as well. – mrfreester Sep 11 '17 at 19:39
5

It depends.

Case 1

Your first case is the classic object-oriented model where the page elements are the object properties, and the functional routines are the object methods.

As long as you keep the object model for you automation code SOLID and DRY, this flavor of page object pattern will work in most cases, and scale well.

Case 2

Your second case is a more hybrid flavor of object oriented with only the properties isolated. This method is simpler to build because there is less need to think through the proper responsibilities of each page object, but less scalable because of the need to repeat code where a test requires actions that have been performed as part of another test.

As an example, consider an e-commerce app. You might have test 1 cover a customer checking their order list, and test 2 cover a customer making a new order. In both tests, the customer must log on, and in this model the code to log in must appear in two places.

Case 3

This architecture is similar to Model-View-Controller architecture, where the tests approximate the view, the interactions with fields form the controller, and the field definitions/locators form the model. It's as scalable as the classic object oriented pattern, but a little more complex to build and architect. I'd probably look to using this pattern with inheritance and potentially also interfaces and library routines to work with complex applications where there are common elements on pages as well as multiple different modules.

This architecture helps to keep each code unit to a single concern, at the cost of increased complexity for someone new to the automation code.

Overall

No matter which method you choose, there will be tradeoffs. Test automation code always has to balance good coding principles with the need to keep each test as independent as possible.

2

First, a warning: methods/approaches which work fine on trivial examples like your login screen, will fail miserably in real life. In example, in real life you need to login to run tests:

  • in development environment (with different, dev-only set of users and other stripped down data),
  • in an integration testing (close to production), but with non-production passwords for production users,
  • to real production (say, to compare with integration testing, for regression testing).

Each has different configuration, URL, etc. But each is just a login: would you split it into three classes? I prefer to keep it in common class, because from URL I can infer the environment, and many other things about the expected behavior.

Added complications: you need more methods: dealing with failed login, timeout/lockout after multiple failed logins, password reset, and possibly more.

Also, assumption: you are writing integration/system tests for your pages. Complicated pages/single page application should have separate suite of unit-like tests which test functionality of one/few controls on the page in isolation.

Whole point of PageObject model for system/integration testing is to insulate tests from pages: changes in the page design should not require changes in tests. Test should request a behavior, and it is up to pageobject to implement it. So obviously, no webdriver calls in tests.

But of course, changes in page design would trigger changes in "unit-tests" for the page.

So I guess my answer is: you have two different goals, and consequently you need two different sets of tests. Likely you may have single pageobject to accommodate both test suites, just different methods for different goals.

In theory, there is no difference between the theory and the practice. I practice, there is.

2

The rule I follow: The less automation code you have the happier you will be over time. Specify in a "custom" framework over coding as if your specification is nicely done you can convert, generate, document so much easier based on your testcases. Model your testcase with steps, screens with sections of elements

I would move the whole page/object pattern to an XML file and have a generic class model that deals with the different (sub) pages. On top of the XML some simple GUI to manage the GUI hierarchy

In XML :

  1. I can have also a logical name for every element In XML
  2. I can make some references to other stuff like JIRA relations (so able to add custom tags)
  3. I can have an embedded screenshot or reference to a .jpg
  4. Extend the number of properties where I can recognize the element with (instead of findbyid fails)
  5. Generate documentation out of the XML if required

Then I can have another XML that groups (as a reusable building block) the elements back to a logical unit of work and feed that to the execution engine that then can deal before and after handleElement the pre and post conditions like exceptional popups.

More advanced are groups of groups like

Business layer:
Authenticate a user

Which is build of the 2 screen groups

Implementation ( the subgroup of authenticating a user):

  1. Fill screen 1 with user:=...., pwd:=secret, submit:=click
  2. Check screen 2 having user:=..., lastlogondate:=[today] , takesnapshot:=true
1

Tests should read like workflows. The login in example is a bit simple as it is only one workflow call.

app = new loginScreen();
page = app.login(new Users.ValidUser);
assert(app.loginFormIsHidden());
assert(page.menuIsVisible());

That the pageObject contains locators is the least if your worries, but I would keep them in the same file if possible. It is all about the interactions the page supports. Never should Selenium, Locators and or other dependencies be called from your tests. They should read like test-scripts, preferable full worksflows and not a bunch of single steps.

If you need single steps I would add extra more simpler workflows to the pageObjects and hide the steps and locators in the pageObject.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.