ES2015 Page Classes

~ Alister Scott ~ Leave a comment

As mentioned yesterday, I am updating my WebDriverJs and Mocha demo to use new ES2015 features.

ES2015 supports classes more elegantly than using prototype based ones in older versions of JavaScript.

For example, this old class:

var webdriver = require('selenium-webdriver');
var until = webdriver.until;
var config = require('config');

RalphSaysPage = function RalphSaysPage(driver, visit) {
	this.driver = driver;
	this.url = config.get('url');
	this.explicitWaitMS = config.get('explicitWaitMS');
	this.quoteSelector = webdriver.By.id('quote');
	if (visit === true) {
		this.driver.get(this.url);
	}
	this.driver.wait(until.elementLocated(this.quoteSelector), this.explicitWaitMS);
};

RalphSaysPage.prototype.quoteContainerPresent = function() {
	var d = webdriver.promise.defer();
	this.driver.isElementPresent(this.quoteSelector).then(function(present) {
		d.fulfill(present);
	});
	return d.promise;
};

RalphSaysPage.prototype.quoteTextDisplayed = function() {
	var d = webdriver.promise.defer();
	this.driver.findElement(this.quoteSelector).getText().then(function(text) {
		d.fulfill(text);
	});
	return d.promise;
};

module.exports = RalphSaysPage;

can be better written as a ES2015 class:

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

export default class RalphSaysPage {
	constructor( driver, visit = false ) {
		this.driver = driver;
		this.url = config.get('ralphURL');
		this.explicitWaitMS = config.get('explicitWaitMS');
		this.quoteSelector = webdriver.By.id('quote');

		if (visit) this.driver.get(this.url);

		this.driver.wait(webdriver.until.elementLocated(this.quoteSelector), this.explicitWaitMS);
	}
	quoteContainerPresent() {
		return this.driver.isElementPresent(this.quoteSelector);
	}
	quoteTextDisplayed() {
		return this.driver.findElement(this.quoteSelector).getText();
	}
}

You can see I have also simplified the functions quoteContainerPresent() and quoteTextDisplayed() to directly return the webDriver promise instead of creating our own which is unnecessary.

When I introduce another page class:

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

const by = webdriver.By;
const until = webdriver.until;

export default class WebDriverJsDemoPage {
	constructor( driver, visit = false ) {
		this.driver = driver;
		this.url = config.get('demoURL');
		this.explicitWaitMS = config.get('explicitWaitMS');
		this.expectedElementSelector = by.id('elementappearsparent');

		if (visit) this.driver.get(this.url);

		this.driver.wait(webdriver.until.elementLocated(this.expectedElementSelector), this.explicitWaitMS);
	}
	waitForChildElementToAppear() {
		return this.driver.wait(until.elementLocated(by.id('elementappearschild')), this.explicitWaitMS, 'Could not locate the child element within the time specified');
	}
	childElementPresent() {
		return this.driver.isElementPresent(by.id('elementappearschild'));
	}
}

you can see I have duplicated some common functionality such as the navigation and page waiting across these two classes. This is where we can an ES2015 parent (or base) page class to inherit from.

Our base class might look something like:

export default class BasePage {
	constructor( driver, expectedElementSelector, visit = false, url = null ) {
		this.explicitWaitMS = config.get('explicitWaitMS');
		this.driver = driver;
		this.expectedElementSelector = expectedElementSelector;
		this.url = url;

		if (visit) this.driver.get(this.url);

		this.driver.wait(webdriver.until.elementLocated(this.expectedElementSelector), this.explicitWaitMS);
	}
}

which means our page classes are much nicer now:

export default class RalphSaysPage extends BasePage {
	constructor( driver, visit = false ) {
		const quoteSelector = webdriver.By.id('quote');
		super(driver, quoteSelector, visit, config.get('ralphURL'));
		this.quoteSelector = quoteSelector;
	}
	quoteContainerPresent() {
		return this.driver.isElementPresent(this.quoteSelector);
	}
	quoteTextDisplayed() {
		return this.driver.findElement(this.quoteSelector).getText();
	}
}

export default class WebDriverJsDemoPage extends BasePage {
	constructor( driver, visit = false ) {
		super(driver, by.id('elementappearsparent'), visit, config.get('demoURL'));
	}
	waitForChildElementToAppear() {
		return this.driver.wait(until.elementLocated(by.id('elementappearschild')), this.explicitWaitMS, 'Could not locate the child element within the time specified');
	}
	childElementPresent() {
		return this.driver.isElementPresent(by.id('elementappearschild'));
	}
}

This gives us the ability to add any common functionality across pages (such as checking the page title) quickly and easily without duplication.

 

 

 

WebDriverJs & Mocha in ES2015

~ Alister Scott ~ Leave a comment

A friend of mine, Mark Ryall, recently created a fork of my WebDriverJs and Mocha example project and updated it to use ES2015. I’ve made some further changes and merged these in, and would like to share these.

Background

JavaScript is an implementation of the ECMAScript scripting language standard.

The latest version of ECMAScript, known as ES2015, ES6, ES6 Harmony, ECMAScript 2015, or ECMAScript 6, has some neat features which are handy to use for our WebDriverJs & Mocha tests I have previously written about.

It seems that there will be yearly releases of the ECMAScript standard from 2015 onwards, and the most common way to refer to these will be as ES2015, ES2016 etc.

Enabling ES2015 Support for our Example Tests

There is a node tool called Babel which is a JavaScript compiler that allows you to use new ECMAScript features and compile these into JavaScript. This requires two node packages which we add to our package.json file:

"babel-core": "^6.3.13",
"babel-preset-es2015": "^6.3.13"

This means we have a babel compiler and a babel library to transform ES2015.

The second thing we need to do is add a plugin to actually tell babel to transform ES2015.

We add a .babelrc file to our project with the following content:

{
"presets": ["es2015"]
}

Running our Specs using Babel

Once we’ve done this, we can use Mocha and WebDriverJs with ES2015. Instead of calling mocha specs we now need to use babel like:
mocha --compilers js:babel-core/register specs.

This isn’t as nice, so we can update our package.json file so our test command is set to the longer babel command, and we just need to call npm test to run our Mocha specs.

Updating our code to use ES2015

The great thing about ES2015 is it is backwards compatible, so we don’t need to update all our code at once, we can made gradual changes to use new features available to us.

Mark made changes to the spec and the page object to use some of the pretty ES2015 features:

Import Statements

This:

var assert = require('assert');
var webdriver = require('selenium-webdriver');
var test = require('selenium-webdriver/testing');
var config = require('config');
var RalphSaysPage = require('../lib/ralph-says-page.js');

Becomes:

import assert from 'assert';
import webdriver from 'selenium-webdriver';
import test from 'selenium-webdriver/testing';
import config from 'config';
import { ralphSays } from '../lib/pages.js';

Using let instead of var

let is block scoped so this is better to use.

This:

var driver;

Becomes:

let driver = null;

Arrow functions

The arrow functions make the clean up hooks simpler to read:

From this:

test.afterEach(function() {
  driver.manage().deleteAllCookies();
});

To this:

test.afterEach(() => driver.manage().deleteAllCookies());

Summary

Moving to use ES2015 wasn’t as daunting as I initially thought as once you add support for it using Babel, you can gradually start using the new features.

Real vs Headless Browsers for Automated Acceptance Tests

~ Alister Scott ~ 2 Comments

When I was comparing/evaluating JavaScript browser automation tools for Automattic, I noticed that a lot of the tools were headless only: either they use PhantomJS/SlimerJS or have their own headless browser engine (like Zombie.js).

I must admit I am not a fan of headless browsers for a number of reasons:
Continue reading

Comparison of JavaScript browser automation and test specification libraries

~ Alister Scott

As part of my trial for my current role at Automattic, I was tasked with implementing some e2e acceptance tests using my choice of library/framework/language.

I very much recommend writing automated acceptance tests in the same language as your app, even though I have described some benefits of using a different language, and since WordPress is moving towards JavaScript from PHP, JavaScript seems the most suitable language for Automattic.

Continue reading

Accessing Mocha Test Metadata in Hooks

~ Alister Scott ~ 1 Comment

In setting up my WebDriverJs tests to run in Mocha I wanted to add some intelligent screenshot taking ability:

  1. Only take screenshots if the test has failed
  2. Include the name of the test in the screenshot file name to easily identify which test it relates to

Both of these involve accessing some metadata about a test in Mocha. Fortunately it’s easy to do:

test.afterEach(function() {
       if (this.currentTest.state == 'failed') {
           var prefix = this.currentTest.title.replace(/[^a-z0-9]/gi, '-').toLowerCase() + '-';
           driver.takeScreenshot().then(function (data) {
               mediaHelper.writeScreenshot(data, prefix);
           });
       }
});

The this.currentTest.state gives us the status of the test (eg. ‘failed’), and this.currentTest.title gives us the title of the lowest level mocha test running (you can use this.currentTest.fullTitle() to get the entire test chain).

Highlights from CukeUp! Sydney 2015

~ Alister Scott

I spent the last two days at CukeUp! 2015 in Sydney, in the beautiful but hot Cell Block Theater.

spacer
spacer
spacer
spacer
spacer

Here’s some of my highlights.

Continue reading

The 10 Do’s, and 500* Don’ts of Automated Acceptance Testing

~ Alister Scott ~ 25 Comments

This is a talk I delivered at CukeUp! Australia on Friday 20 November in Sydney, Australia.

spacer

Continue reading

GTAC 2015 Day Two Highlights & Summary

~ Alister Scott ~ 1 Comment

Today’s conference began with some rather funny commentary shared by Yvette Nameth’s mother from yesterday’s talks. I was mentioned as the ‘flaky’ guy:

spacer

My main takeaway from the entire conference is that it seems we get way too caught up on complex solutions for our testing. We need to keep asking ourselves: “what’s the simplest thing that could possibly work?” If we have complex systems why do we need complex tests? We need to take each large complex problem we work on and break it down till we get something small and manageable and solve that problem. Rinse and repeat.

Continue reading

GTAC 2015 Day One Highlights

~ Alister Scott ~ 1 Comment

Here are my highlights from today’s GTAC conference at Google in Cambridge. I have excluded my talk with the content of my talk here.

Continue reading

Your tests aren’t flaky

~ Alister Scott ~ 10 Comments

This is a talk I delivered at the Google Test Automation Conference (GTAC) on Tuesday 10th November at Google in Cambridge, Massachusetts.

spacer

Continue reading