AMA: R.Y.O. Page Objects 2.0

Michael Karlovich asks…

Do you have any updated thoughts on rolling your own page objects with Watir? The original post is almost 4 years old but is still the basis (loosely) of every page object framework I’ve built since then.

My response…

Wow, I can’t believe that post is almost four years old. I have also have used this for the basis of every page object framework I have built since then.

I recently had a look at our JavaScript (ES2015) code of page objects and despite ES2015 not having meta-programming support like ruby, our classes are remarkably similar to what I was proposing ~4 years ago.

I believe this is because some patterns are classic and therefore almost timeless, they can be applied over and over again to different contexts. There’s a huge amount of negativity towards best practices of late, but I could seriously say that page objects are a best practise for test automation of ui systems, which isn’t saying they will be exactly the same in every context, but there’s a common best-practice pattern there which you most likely should be using.

Page objects, as a pattern, typically:

  • Inherit from a base page object/container which stores common actions like:
    • instantiating the object looking for a known element that defines that page’s existence
    • optionally allow a ‘visit’ to the page during instantiation using some defined URL/path
    • provides actions and properties common to all pages, for example: waiting for the page, checking the page is displayed, getting the title/url of the page, and checking cookies and local storage items for that page;
  • Define actions as methods which are ways of interacting with that page (such as logging in);
  • Do not expose internals about the page outside the page – for example they typically don’t expose elements or element selectors which should only be used within actions/methods for that page which are exposed; and
  • Can also be modelled as components for user interfaces that are built using components to give greater reusability of the same components across different pages.

The biggest benefit I have found from using page objects as a pattern is having more deterministic end-to-end tests since instantiating a page I know I am on that page, so my tests will fail more reliably with a better understanding of what went wrong.

Are there any other pattern attributes you would consider vital for page objects?

AMA: Page Object Best Practices

Anonymous asks..

What do you think is best practice for storing element locations? I’ve heard about having an “Element map”, a single place where all element IDs or methods for location are stored. Is that a good idea? Should each Page Object have it’s own element map?

My response…

First let me preface my answer with a quote:

“best practices are useful reference points, but they must come with a warning label: the more you rely on external intelligence, the less you will value an internal idea. And this is the age of the idea”

~  Gyan Nagpal, Talent Economics

I tend to store the element locators, or selectors, in the page objects themselves. Typically if I am going to use an element locator more than once within a page object I will store this as a property of the page object, otherwise I will just use it within a method.

So I would typically do this in ES6:

import { By } from 'selenium-webdriver';
import config from 'config';

import BaseContainer from '../base-container.js';
import * as driverHelper from '../driver-helper.js';

export default class LoginPage extends BaseContainer {
	constructor( driver, visit ) {
		const loginURL = `${config.get( 'authURL' )}?redirect_to=${config.get( 'baseURL' )}`;
		super( driver, By.css( '#loginform' ), visit, loginURL );
	}

	login( username, password ) {
		driverHelper.setWhenSettable( this.driver, By.css( '#user_login' ), username );
		driverHelper.setWhenSettable( this.driver, By.css( '#user_pass' ), password, { secureValue: true } );
		return driverHelper.clickWhenClickable( this.driver, By.css( '#wp-submit' ) );
	}
}

over

import { By } from 'selenium-webdriver';
import config from 'config';

import BaseContainer from '../base-container.js';
import * as driverHelper from '../driver-helper.js';

export default class LoginPage extends BaseContainer {
	constructor( driver, visit ) {
		const loginURL = `${config.get( 'authURL' )}?redirect_to=${config.get( 'baseURL' )}`;
		super( driver, By.css( '#loginform' ), visit, loginURL );
		this.userNameSelector = By.css( '#user_login' );
		this.passwordSelector = By.css( '#user_pass' );
		this.submitSelector = By.css( '#wp-submit' );
	}

	login( username, password ) {
		driverHelper.setWhenSettable( this.driver, this.userNameSelector, username );
		driverHelper.setWhenSettable( this.driver, this.passwordSelector, password, { secureValue: true } );
		return driverHelper.clickWhenClickable( this.driver, this.submitSelector );
	}
}

Although I would use the second style (properties) if I had a second method that used the same element locators.

Anonymous also asks..

When creating a new instance of a Page Object, should there be any validation that you’re a) on the correct page and/or b) the correct elements exist? From what I understand, best practice is to keep assertions in the actual tests. Should an error be thrown if I create a LoginPage object but the login page isn’t displayed?

I like to do b) as I find it keeps tests very deterministic meaning if you know at all times you’re on the correct page, then it’s easier to work out when things go wrong. This also works well for single page applications since the DOM ready state doesn’t necessarily mean the page is loaded, so passing in an element you expect you can poll for its existence.

Other ways to check you’re on the right page would be to check the browser title and/or URL but I have found these aren’t as reliable for example the title might be translated and change depending on your locale, and the URL might be dynamic or not change for each dynamic page update.

In the above examples, the base container that all pages/components extend requires an element locator which it uses to check that page exists within a given explicit wait time. It also optionally takes visit and url parameters which allows you to navigate to the page in the browser before checking that the element exists. This is only used for specific pages like login.