Selenium + JavaScript = 😕🤔😳
by Željko Filipin
I gave a talk based on this post at Testival Meetup #27, JavaScript Zagreb Meetup #33 and Infinum JS Talks #8. It’s about Selenium packages 📦 in JavaScript.
Packages
As with all things JavaScript, there are plenty of choices. After some research, I have picked three packages:
- WebDriverJs, the official Selenium bindings, also called selenium-webdriver,
 - WebdriverIO (named WebdriverJS until 2014),
 - Nightwatch.js.
 
My initial research ended quickly for WebdriverIO and Nightwatch.js because they had Java ☕️ dependency. Some time later, after both the tools (and their documentation) and my understanding of the JavaScript ecosystem improved, I have figured out that they can be used without Java. 😓
Numbers
According to JSDOW (The JavaScript download index), WebDriverJs is far ahead in the number of downloads, but WebdriverIO is growing faster, if I have read the charts correctly.

Stack
What is needed for a test automation project that will not fail?
| Browser | Chrome, Firefox | 
| Browser driver | ChromeDriver, geckodriver | 
| Language | JavaScript/Node.js | 
| Selenium bindings | WebDriverJs, WebdriverIO, Nightwatch.js | 
| Nicer API on top of Selenium (optional) | WebdriverIO, Nightwatch.js | 
| Assertion library | Assert (ships with Node.js) | 
| Testing framework | Mocha | 
| Page object pattern implementation (optional) | WebdriverIO, Nightwatch.js | 
| Configuration | Node-config, WebdriverIO, Nightwatch.js | 
Simple code examples
The initial investigation of the tools consisted of a simple example:
- open a browser
 - go to a page
 - check that a link is present
 - close the browser
 
The code is hosted at mediawiki-selenium-nodejs repository.
WebDriverJs
Stack
| Browser | Chrome | 
| Browser driver | ChromeDriver | 
| Language | JavaScript/Node.js | 
| Selenium bindings | WebDriverJs | 
| Nicer API on top of Selenium | N/A | 
| Assertion library | Assert | 
| Testing framework | Mocha | 
| Page object pattern implementation | N/A | 
| Configuration | N/A | 
Code
webdriverjs/main_page.js
const assert = require( 'assert' ),
	{ Builder, By } = require( 'selenium-webdriver' ),
	test = require( 'selenium-webdriver/testing' );
test.describe( 'Main page', function () {
	let driver;
	test.beforeEach( function () {
		driver = new Builder()
		.forBrowser( 'chrome' )
		.build();
	} );
	test.afterEach( function () {
		driver.quit();
	} );
	test.it( 'should have "Log in" link', function () {
		driver.get( 'https://en.wikipedia.beta.wmflabs.org/wiki/Main_Page' );
		driver.findElement( By.linkText( 'Log in' ) ).isDisplayed().then( function ( displayed ) {
			assert( displayed );
		} );
	} );
} );WebdriverIO
Stack
| Browser | Chrome | 
| Browser driver | ChromeDriver | 
| Language | JavaScript/Node.js | 
| Selenium bindings | WebdriverIO | 
| Nicer API on top of Selenium | WebdriverIO | 
| Assertion library | Assert | 
| Testing framework | Mocha | 
| Page object pattern implementation | N/A | 
| Configuration | N/A | 
Code
webdriverio/stack/assert.js
const assert = require( 'assert' );
describe( 'Main page', function () {
	it( 'should have "Log in" link', function () {
		browser.url( '/Main_Page' );
		assert( browser.isVisible( 'li#pt-login a' ) );
	} );
} );Nightwatch.js
Stack
| Browser | Chrome | 
| Browser driver | ChromeDriver | 
| Language | JavaScript/Node.js | 
| Selenium bindings | Nightwatch.js | 
| Nicer API on top of Selenium | Nightwatch.js | 
| Assertion library | Nightwatch.js | 
| Testing framework | Nightwatch.js | 
| Page object pattern implementation | N/A | 
| Configuration | N/A | 
Code
nightwatch/main_page.js
module.exports = {
	'Main page': function ( client ) {
		client
		.url( 'https://en.wikipedia.beta.wmflabs.org/wiki/Main_Page' )
		.assert.elementPresent( 'li#pt-login a' )
		.end();
	}
};Realistic code examples
After the initial investigation with a simple test, all three tools looked fine to me. So far, I liked WebdriverIO API. WebDriverJs was a bit verbose for my taste. Nightwatch.js API looked strange to me.
The second round included a more realistic set of tests.
- wiki page
    
- create
 - edit
 - check history
 
 - user
    
- create
 - log in
 - change preferences
 
 
The entire code is available in the GitHub repositories. I don’t want this blog post to be too long, so I will include only the code for creating a wiki page.
WebDriverJs
The code is at mediawiki-webdriverjs repository.
Stack
| Browser | Chrome | 
| Browser driver | ChromeDriver | 
| Language | JavaScript/Node.js | 
| Selenium bindings | WebDriverJs | 
| Nicer API on top of Selenium | N/A | 
| Assertion library | Assert | 
| Testing framework | Mocha | 
| Page object pattern implementation | N/A | 
| Configuration | Node-config | 
Code
config/default.js
module.exports = {
	baseUrl: 'http://127.0.0.1:8080/w/index.php?title=',
	browser: 'chrome',
	logPath: '.',
	mochaTestOptions: {
		reporter: 'spec',
		slow: 10000,
		timeout: 20000 },
	password: 'vagrant',
	username: 'Admin'
};test/helper.js
var config = require( 'config' ),
	webdriver = require( 'selenium-webdriver' );
function browser() {
	return new webdriver.Builder()
	.forBrowser( config.get( 'browser' ) )
	.build();
}
function screenshot( driver, state, title ) {
	if ( state === 'failed' ) {
		driver.takeScreenshot().then( ( image ) => {
			let fileName = config.get( 'logPath' ) + title + '.png';
			require( 'fs' ).writeFile( fileName, image, 'base64', ( err ) => {
				if ( err ) { throw err; }
			} );
		} );
	}
}
module.exports = { browser, screenshot };test/page.js
var assert = require( 'assert' ),
	config = require( 'config' ),
	baseUrl = config.get( 'baseUrl' ),
	webdriver = require( 'selenium-webdriver' ),
	By = webdriver.By,
	test = require( 'selenium-webdriver/testing' );
test.describe( 'Page', function () {
	let content,
		driver,
		helper = require( './helper' ),
		name;
	test.beforeEach( function () {
		content = Math.random().toString();
		driver = helper.browser();
		name = Math.random().toString();
	} );
	test.afterEach( function () {
		helper.screenshot( driver, this.currentTest.state, this.currentTest.fullTitle() );
		driver.quit();
	} );
	test.it( 'should be creatable', function () {
		// create
		driver.get( baseUrl + name + '&action=edit' );
		driver.findElement( By.css( '#wpTextbox1' ) ).sendKeys( content );
		driver.findElement( By.css( '#wpSave' ) ).click();
		// check name
		driver.findElement( By.css( '#firstHeading' ) ).getText().then( function ( text ) {
			assert.equal( text, name );
		} );
		// check content
		driver.findElement( By.css( '#mw-content-text' ) ).getText().then( function ( text ) {
			assert.equal( text, content );
		} );
	} );
} );WebdriverIO
The code is at mediawiki-webdriverio repository.
Stack
| Browser | Chrome | 
| Browser driver | ChromeDriver | 
| Language | JavaScript/Node.js | 
| Selenium bindings | WebdriverIO | 
| Nicer API on top of Selenium | WebdriverIO | 
| Assertion library | Assert | 
| Testing framework | Mocha | 
| Page object pattern implementation | WebdriverIO | 
| Configuration | WebdriverIO | 
Code
wdio.conf.js
exports.config = {
	specs: [
		'./specs/**/*.js'
	],
	capabilities: [ {
		browserName: 'chrome',
	} ],
	framework: 'mocha',
	reporters: [ 'spec' ],
};specs/page.js
const assert = require( 'assert' ),
	EditPage = require( '../pageobjects/edit.page' );
describe( 'Page', function () {
	var content,
		name;
	beforeEach( function () {
		content = Math.random().toString();
		name = Math.random().toString();
	} );
	it( 'should be creatable', function () {
		// create
		EditPage.edit( name, content );
		// check
		assert.equal( EditPage.heading.getText(), name );
		assert.equal( EditPage.displayedContent.getText(), content );
	} );
} );pageobjects/page.js
class Page {
	constructor() {
		this.title = 'My Page';
	}
	open( path ) {
		browser.url( '/index.php?title=' + path );
	}
}
module.exports = Page;pageobjects/edit.page.js
'use strict';
const Page = require( './page' );
class EditPage extends Page {
	get content() { return browser.element( '#wpTextbox1' ); }
	get displayedContent() { return browser.element( '#mw-content-text' ); }
	get heading() { return browser.element( '#firstHeading' ); }
	get save() { return browser.element( '#wpSave' ); }
	open( name ) {
		super.open( name + '&action=edit' );
	}
	edit( name, content ) {
		this.open( name );
		this.content.setValue( content );
		this.save.click();
	}
}
module.exports = new EditPage();Conclusion
I have spent some time with both WebDriverJs and WebdriverIO. I have liked WebDriverJs for it’s simplicity. I have liked WebdriverIO for it’s API and features. I have decided that spending time on investigating Nightwatch.js would not be a good investment.
tags: code - event - featured - javascript - organizer - selenium - testing - speaker - testival