Javascript testing in parallel with WD.js and Selenium

Like bow ties, Javascript is cool. People love it so much that they're bringing it everywhere (server, desktop UI, etc). If you do Javascript development, you probably love it for its funkiness - even if your code is pretty crazy. But that's ok, because your code base is (hopefully) extensively tested... But wouldn’t you love it if everything (tests included) were in Javascript? Ok, maybe your unit tests already are (more on this later), but what about the functional ones, too? Well, good news awaits. Now you can write your tests in Javascript with the WD.js (pronounced wood) node library and run them across multiple browsers in parallel using Selenium. Read on to learn more about three different projects to help with this.

Parallel WD and Synchronous Parallel WD

One of the biggest pains of web development is the heterogeneity of the platforms on which your code will run. Even if things are getting better with more people following a standard, things are far from perfect, especially with the rise of mobile web. One of the roles of automation and testing is to make it easier to check that your code is behaving correctly in all browsers. Up until recently, you could share the test logic, but you still had to set up the different browser and you had no way to run in parallel. These two libraries, Parallel WD and Synchronous Parallel WD, are designed to take the pain away and address those problems. This is achieved by letting you write your test once, declare the browsers in which you want the test run, and then let the libraries run it in parallel - all while keeping it as close to the same vanilla browser code. This library comes in two flavors. One is a classical Javascript asynchronous callback-based library. The vanilla asynchronous test looks like this:

var webdriver = require('wd'),
assert = require('assert');

var browser = webdriver.remote();

browser.on('status', function(info){
  console.log('\x1b[36m%s\x1b[0m', info);
});

browser.on('command', function(meth, path){
  console.log(' > \x1b[33m%s\x1b[0m: %s', meth, path);
});

browser.init({
    browserName:'firefox'
    , tags : ["examples"]
    , name: "This is an example test"
}, function() {

    browser.get("http://saucelabs.com/test/guinea-pig", function() {
	browser.title(function(err, title) {
	    assert.ok(~title.indexOf('I am a page title - Sauce Labs'), 'Wrong title!');
	    browser.elementById('comments', function(err, el) {
		el.sendKeys("this is not a comment", function(err) {
		    browser.elementById('submit', function(err, el) {
			el.click(function() {
			    browser.eval("window.location.href", function(err, title) {
				assert.ok(~title.indexOf('#'), 'Wrong title!');
				browser.elementById("your_comments", function(err, el) {
				    el.textPresent("this is not a comment", function(err, present) {
					assert.ok(present, "Comments not correct");
					el.text(function(err, text) {
					    console.log(text);
					    browser.quit();
					})
				    })
				})
			    })
			})
		    })
		})
	    })
	})
    })
});

The parallelized version looks like this:

var webdriver = require('wd-parallel-async')
  , assert = require('assert');

var parallelizer = webdriver.parallelizer();

parallelizer.run([{
    browserName:'chrome',
    tags: ["examples"],
    name: "This is an example test",
},{
    browserName:'firefox',
    tags: ["examples"],
    name: "This is an example test",
}], function(browser, desired) {
    browser.on('status', function(info){
	console.log('\x1b[36m%s\x1b[0m', info);
    });

    browser.on('command', function(meth, path){
	console.log(' > \x1b[33m%s\x1b[0m: %s', meth, path);
    });

    browser.init(desired, function() {

    browser.get("http://saucelabs.com/test/guinea-pig", function() {
	browser.title(function(err, title) {
	    assert.ok(~title.indexOf('I am a page title - Sauce Labs'), 'Wrong title!');
	    browser.elementById('comments', function(err, el) {
		el.sendKeys("this is not a comment", function(err) {
		    browser.elementById('submit', function(err, el) {
			el.click(function() {
			    browser.eval("window.location.href", function(err, title) {
				assert.ok(~title.indexOf('#'), 'Wrong title!');
				browser.elementById("your_comments", function(err, el) {
				    el.textPresent("this is not a comment", function(err, present) {
					assert.ok(present, "Comments not correct");
					el.text(function(err, text) {
					    console.log(text);
					    browser.quit();
					})
				    })
				})
			    })
			})
		    })
		})
	    })
	})
    })
});

As you can see, the test logic is identical. The only difference is declaring the array of browsers. The synchronous flavor follows exactly the same principals, but is based on the wd-sync library, which lets you write your tests in a more classical, procedural way. Here is an example:

var p_webdriver = require('wd-parallel');

var username = "<USERNAME>",
accessKey = "<ACCESS KEY>";

var browsers = p_webdriver.remote(
    "ondemand.saucelabs.com",
    80,
    username,
    accessKey
);

// Desired
var p_desired = [
    {tags: ["examples"], name: "parallel test 1/4", browserName: "firefox"},
    {tags: ["examples"], name: "parallel test 2/4", browserName: "chrome"},
    {tags: ["examples"], name: "parallel test 3/4", browserName: "firefox", platform: "LINUX"},
    {tags: ["examples"], name: "parallel test 4/4", browserName: "chrome", platform: "LINUX"}
]

// Test
browsers.test = function(browser, desired) {

    console.log("server status:", browser.status());
    browser.init(desired);

    browser.get("http://google.com");
    console.log("title is "+browser.title());

    var queryField = browser.elementByName('q');
    browser.type(queryField, "Hello World");
    browser.type(queryField, "\n");

    browser.setWaitTimeout(3000);
    browser.elementByCss('#ires'); // waiting for new page to load
    console.log(browser.title());

    browser.quit();
};

// Run
browsers.run(p_desired);

WD Unit

The last library I want to discuss is wd-unit. Earlier, I mentioned Javascript unit tests. There are a lot of great unit test frameworks out there, such as Jasmine, foounit and QUnit, to name a few. Since Selenium is for web browser automation, it's better suited for functional testing rather than unit testing. So what is this wd-unit and how can it help you? Selenium drives the web browsers that Javascript unit tests are run in, so it actually makes a lot of sense to pair them together. This is what wd-unit does - automate the running of your Javascript unit tests. Here's an example. Let's say you have a some foounit test cases. You could easily write a script that launches the browser aimed at the right page. But you still have two problems: how do you know when everything has finished and how do you get the results? This is where Selenium and wd-unit come in. They will run your tests and then output the results back to the console they were launched from. Additionally, using Selenium has some added benefits. You can run everything in the cloud with a service such as Sauce Labs across multiple browsers in parallel. This is what running the Jasmine example tests (remote) look like with Sauce:

var launcher = require('wd-unit');

var
username = 'username',
accessKey = 'accessKey';

var saucelabs = {
    host: "ondemand.saucelabs.com",
    port: 80,
    username: username,
    accessKey: accessKey
}

launcher.run({
    runner: 'jasmine',
    addr: 'pivotal.github.com',
    port: 80,
    page: 'jasmine',
    desired: [
	{browserName: 'firefox', name: "wd-unit jasmine 1/4"},
	{browserName: 'chrome', name: "wd-unit jasmine 2/4"},
	{browserName: 'firefox', platform: "LINUX", name: "wd-unit jasmine 3/4"},
	{browserName: 'chrome', platform: "LINUX", name: "wd-unit jasmine 4/4"}
    ],
    wd_args: saucelabs
});

This code, which runs on the Pivotal example page from the Sauce cloud, outputs the results to your console after running in parallel across Firefox and Chrome on Linux and Windows. You can run it across even more browsers in parallel by adding additional desired capabilities to the tests. To contribute to these projects, visit wd.js on Github. Happy testing!

Written by

Mathieu Sabourin

Topics

Programming languagesUnit Testing