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.

Removing local page references from Cucumber steps

I’ve been giving some thought about the maintainability of having local page object model references in Cucumber steps. To explain what I mean, here’s some code of mine from an Etsy page model step:

When /^an item is added to the cart$/ do
  @advanced_search_page = EtsyAdvancedSearchPage.new @browser, true
  @search_results_page = @advanced_search_page.search_for 'hat'
  @etsy_item_page = @search_results_page.click_first_result
  @item_title = @etsy_item_page.item_title
  @etsy_cart_page = @etsy_item_page.click_add_to_cart
end

Here I am using instance variables in Cucumber steps to store references to pages that I am interacting with. As you can see, it’s quite easy to lose track of which page is which.

If I want to use one of these pages in another step, I have to refer to the same variable name. The key to this concept is having pages return pages, so you don’t have to initialize pages in your steps (except for the first visit, which normally happens only once per scenario).

A colleague and good friend of mine has been showing me some alternatives. One alternative to this is to actually dynamically refer to pages only when you need them. This means you don’t need to return a page from a page, as they are always initialized when needed. The above method would look like:

When /^an item is added to the cart$/ do
  visit EtsyAdvancedSearchPage do |page| page.search_for 'hat' end
  on EtsySearchResultsPage do |page| page.click_first_result end
  on EtsyItemPage do |page|
    @item_title = page.item_title
    page.click_add_to_cart
  end
end

This introduces two new methods at the Cucumber step level that need to be created: on, and visit. The on method simply creates a new page object so you can call methods from it. As each page initialize checks expected title and expected element, it will raise an error automatically if either are incorrect. The visit method is an extension of on, which actually initalizes the page and visits it.

These are defined as a module in our Cucumber env.rb file, and then mixed into the Cucumber World, so that steps automatically have access to these.

module Browser
  BROWSER = Watir::Browser.new ENV['WEB_DRIVER'] ||:firefox

  def visit page_class, &block
    on page_class, true, &block
  end

  def on page_class, visit=false, &block
    page = page_class.new BROWSER, visit
    block.call page if block
    page
  end
end

World Browser

Summary

By introducing the on and visit methods, it means that we no longer need to have instance variables for page classes in our Cucumber steps. We also no longer need to worry about state transformations in pages, as these can be done in a modular way in the steps themselves. It means that when an error occurs initializing a page, it is more likely to to occur in the correct place. I find the steps more readible, as you only have to initialize the page once using the on block, and then can refer to page.

I have updated my set of Etsy Watir-WebDriver steps if you’d like to take a look.

What do you think? How will this scale?

Update: I have updated the WatirMelonCucumber project to use this style. It’s slightly different in that it supports two sites, and therefore the on method dynamically switches between these.

Cucumber Steps:

When /^I search for a? ?"([^"]*)"$/ do |term|
  on :home do |page|
    page.search_for term
  end
end

The on method:

def on page, visit=false, &block
  page_class = Object.const_get "#{@site}#{page.capitalize}Page"
  page = page_class.new BROWSER, visit
  block.call page if block
  page
end