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:

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.

Selenium Downloads Selenium Percentage

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.