Selenium + JavaScript = 馃槙馃馃槼
by 沤eljko Filipin
tags: code 路 event 路 featured 路 image 路 javascript 路 organizer 路 selenium 路 speaker 路 testing 路 testival
Estimated reading time is 11 minutes.
I gave a talk based on this post at Testival Meetup #27, JavaScript Zagreb Meetup #33 and Infinum JS Talks #8. It鈥檚 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鈥檛 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鈥檚 simplicity. I have liked WebdriverIO for it鈥檚 API and features. I have decided that spending time on investigating Nightwatch.js would not be a good investment.
Feedback
Thank you for reading. If you want to stay in touch please use the feed or send me an email.