Adding your own WebDriverJs helper methods

Whilst I find the WebDriver API useful, I also find it lacking in certain methods that I wish to do repeatedly throughout my tests.

For example, using the driver.findElement(selector).click() method on a WebElement is troublesome when you are automating dynamic JavaScript driven single-page application (SPA) as you’ll often come across ‘stale element’ exceptions as the DOM continually changes.

When I was writing C# code I found writing extension methods to be useful as these could be added to the IWebDriver or IWebElement interfaces rather easily and made for nicer test code.

My first attempt at solving this issue involved writing a helper method that takes the driver object and selector to click:

import webdriver from 'selenium-webdriver';
import config from 'config';

const explicitWaitMS = config.get( 'explicitWaitMS' );

export function clickWhenClickable( driver, selector, waitOverride ) {
	const timeoutWait = waitOverride ? waitOverride : explicitWaitMS;

	return driver.wait( function() {
		return driver.findElement( selector ).then( function( element ) {
			return element.click().then( function() {
				return true;
			}, function() {
				return false;
			} );
		}, function() {
			return false;
		} );
	}, timeoutWait, `Timed out waiting for element with ${selector.using} of '${selector.value}' to be clickable` );
}

To use this, all you do is import the file and you can can clickWhenClickable from your test code:

import { By as by } from 'selenium-webdriver';
import config from 'config';
import BasePage from './base-page';
import * as driverHelper from './webdriver-helper';

export default class WebDriverJsLeavePage extends BasePage {
	constructor( driver, visit = false ) {
		super( driver, by.css( '#leavepage' ), visit, `${config.get( 'demoURL' )}/leave` );
	}
	navHome() {
		driverHelper.clickWhenClickable( this.driver, by.css( '#homelink' ) );
		return this.driver.switchTo().alert().then( function( alert ) {
			return alert.accept();
		} );
	}
}

This works well, but it would be neater if it was similar to the C# extension methods which are added to the webDriver or webElement objects so you don’t have to pass the objects in.

JavaScript allows you to use the prototype property of objects to add your own methods. This means you can rewrite the helper method to add it to the WebDriver object:

import webdriver from 'selenium-webdriver';
import config from 'config';

const explicitWaitMS = config.get( 'explicitWaitMS' );

webdriver.WebDriver.prototype.clickWhenClickable = function( selector, waitOverride ) {
	const timeoutWait = waitOverride ? waitOverride : explicitWaitMS;
	const self = this;

	return self.wait( function() {
		return self.findElement( selector ).then( function( element ) {
			return element.click().then( function() {
				return true;
			}, function() {
				return false;
			} );
		}, function() {
			return false;
		} );
	}, timeoutWait, `Timed out waiting for element with ${selector.using} of '${selector.value}' to be clickable` );
};

and your page now looks like:

import { By as by } from 'selenium-webdriver';
import config from 'config';
import BasePage from './base-page';
import './webdriver-helper';

export default class WebDriverJsLeavePage extends BasePage {
	constructor( driver, visit = false ) {
		super( driver, by.css( '#leavepage' ), visit, `${config.get( 'demoURL' )}/leave` );
	}
	navHome() {
		this.driver.clickWhenClickable( by.css( '#homelink' ) );
		return this.driver.switchTo().alert().then( function( alert ) {
			return alert.accept();
		} );
	}
}

This looks a look neater to me, however, the general consensus is not to use prototype for objects you don’t own, since it can cause confusion (eg. ‘where did that method come from?’) when others are working on your code.

So now I am not sure whether to use the neater but controversial second approach or the slightly less elegant (but more obvious) first approach. Thoughts?

Author: Alister Scott

Alister is an Excellence Wrangler for Automattic.

6 thoughts on “Adding your own WebDriverJs helper methods”

  1. I’ve found myself many times walking this same road.
    To be? Or not to be?
    Should the tests be clear and easy to read?
    Or must be easy to re-develop whith simple/basic code skills?

    I cannot reaaly answer but, for me, it’s largely preferred to have simple and readable tests, with a couple of surrounding comments, so the person eventually analyzing my code (usually, it’s just myself) can envision the idea (simple phrasing, less code, clear names) and assume by the comments where the magic (Page, Helper, etc.) is done.

    But…
    I must recognize I’ve found myself reworking that same staff when my new project feels ideal for theese styling.

    Great articles.

    Eduardo.

    Like

  2. I used to love extension methods when I was developing in C#. I now mainly work with Java and I miss them.
    Not sure why Java never adopted the concept. I know Swift and objective C have similar concepts. ..

    Anyway, I’m not too much of a javascript coder but extending the prototype seems fine to me. I know javascript has the concept of Mixin, which is basically a function that mixes in members from one objects into another’s prototype, similar to what you did. Apparently there’s a neater way of doing it in ES6 according to this guy (http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/)

    Like

  3. I think you’ll be fine extending prototypes like that Alister, particularly if your team are all aware and on board with that approach.

    The practice carries more risk in front-end development space, which might be why you’ve come across the “general consensus”. You can adjust the prototypes with your own scripts in a web page easily enough, but what if there is a conflict with another script/library/browser extension that modifies the same prototype in a different way?

    I think the risk is shared scope and conflicts with third-party code. The same reason global variables are risky “in javascript” — where “in javascript” means “javascript running in the customers’ browser environments”.

    Webdriver tests run in a node environment I think? Makes all the difference! :)

    Liked by 1 person

Comments are closed.