Take a Drive with WebdriverIO and Mocha

My first interest in automated testing came in the form of unit tests. With that, I learned about assertions, test suites, promises, mocks and a plethora of other terminology. This knowledge helped when setting up WebdriverIO, a functional test utility (which I really love by the way), with Mocha, a JavaScript test framework.

But for those starting out fresh with functional testing (and WebdriverIO specifically), the architecture of assertions and tests suites may be unfamiliar territory.

To hopefully help with this, I’ve thought up a simple analogy that will cover the following terms and how they all work together:

  • Selenium
  • Browsers & Drivers
  • WebdriverIO
  • Mocha
  • Assertions
  • before/beforeEach/afterEach/after
  • Promises

Starting Off

The analogy is a simple one: Imagine you want to automate your commute to work and the WebdriverIO ecosystem is how you’ll manage it. There are a few things to consider:

  • The car to drive
  • How to drive the car
  • The directions to take
  • The speed limits to follow
  • How to take turns safely
  • Handling traffic delays

The Car to Drive - Browsers & Drivers

Before we can get anywhere, we need to have a car to drive (or bike, for those feeling more environmentally friendly). In our analogy, the car is the Browser. We can choose any model we prefer: Chrome, IE, Safari, Firefox, etc.

WebdriverIO sets the browser in the initial configuration. You can even choose to drive multiple cars to work via Multiremote.

Drivers are the specific browser integrations for Selenium (think of them like the keys to the car you want to drive). You’ll need to have these installed for you browser choice before going anywhere. You can do this manually, but it's much easier to use a package like the selenium-standalone module, which will automatically set it up for you.

How to drive the car - Selenium

A car is useless without someone (or something) to drive it. Just like browsers, cars can be manually operated via human command, but what’s the fun in that?

Selenium is the robot we’ve bought to control the car’s inputs (steering wheel, gas, brake) and read the car’s output (speedometer, fuel meter). It’s a friendly robot, unrelated to the more sinister kind.

You do have to do some work to get it running. To boot the robot up, I recommend using the NPM selenium-standalone module to handle the install (that's my second recommendation for this hint hint). You can even borrow robots from services like Sauce Labs or BrowserStack.

The directions to take - WebdriverIO

Now that you have your robot ready to go, you need to instruct it where to drive. This is where WebdriverIO comes in.

WebdriverIO knows how to take our JavaScript instructions and pass them along to our Selenium robot. There’s a whole list of things WebdriverIO knows how to say/ask.

Writing your script is as simple as tying together a set of instructions. In driving terms, maybe you want to do the following:

  1. Back out of driveway
  2. Turn left at the stop sign
  3. Turn right at the second stop light

On and on until you get to work. For a browser, you might do the following set of tasks:

  1. Load the website URL
  2. Click on the ‘Contact’ link
  3. Enter an email address in to the right form field

WebdriverIO allows us to speak a language we’re familiar with (JavaScript), to get information to and from the robot.

Our robot is multilingual by the way. There are other tools available to interface with Selenium besides WebdriverIO. If JavaScript isn’t your thing, you can write the instructions in Ruby, Python, or even Gherkin-syntax.

The speed limits to follow - Assertions

Consider your robot friend driving you to and from work; its goal is to get you there as fast as possible. This means it may end up going faster than the prescribed speed limit, as it doesn’t really know what a speed limit is. This may work for getting you to work, but I’m certain the police would have something to say about your setup.

To help avoid breaking the law, we can teach out robot to validate its speed at speed limit signs. In testing language, this is known as “assertions”. You “assert” that one value is equal to another. In our analogy, we assert that the speedometer readout matches the numbers on the speed limit sign.

Here’s what the code could look like:

var speedLimit = 35;
expect(mySpeed).to.equal(speedLimit);

There are many assertion styles out there. There’s even an assertion library called Chai that allows you to choose a style that matches you best, while retaining the same underlying functionality.

The example above uses the “expect” style, but here are a couple others:

Should

mySpeed.should.equal(35);

Assert

assert.equal(mySpeed, 35);

The choice of assertion style up to you; it’s really a preference thing. Just tell Mocha what you want and it will understand.

How to take turns safely - Mocha & before/after hooks

We haven’t talked about how Mocha fits in. If Chai is the assertion library we use to define expectations, what does Mocha do?

A lot, actually. Assertions are essential to testing code, but there is a significant amount of set up and organization needed to ensure our test/directions don’t become bloated.

Back to our driving analogy; during the course of our commute, we’ll need to make various turns to get where we’re going. Each turn requires us to slow down, turn the wheel, then speed back up. We could code this out for every turn, but what if we can prescribe a set of steps to take before and after each redirection?

This is where Mocha can help out. Mocha is a test framework that adds some niceties around our assertions. The main feature is describe/it blocks, which help organize our test suite.

Here’s a pseudo-example of our instructions:

describe('car driving to work', function() {
	it('should turn left at 32nd', function() {
		slowDown();
		turnWheel(360deg);
		expect(direction).to.equal('north');
		speedUp();
	});
	it('should turn right at 41st', function() {
		slowDown();
		turnWheel(-360deg);
		expect(direction).to.equal('east');
		speedUp();
	});
	it('should turn left at 53rd', function() {
		slowDown();
		turnWheel(360deg);
		expect(direction).to.equal('north');
		speedUp();
	});
});

You’ll notice we call slowDown and speedUp for all of our tests. This bloats the test code and makes our tests verbose.

Mocha has another trick up its sleeve to help out. Each describe block can have a beforeEach and afterEach hook, which will be run before/after each it instruction.

Here’s the updated code example:

describe('car driving to work', function () {
	beforeEach(function() {
		slowDown();
	});

	it('should turn left at 32nd', function() {
		turnWheel(360deg);
		expect(direction).to.equal('north');
	});
	it('should turn right at 41st', function() {
		turnWheel(-360deg);
		expect(direction).to.equal('east');
	});
	it('should turn left at 53rd', function() {
		turnWheel(360deg);
		expect(direction).to.equal('north');
	});

	afterEach(function() {
		speedUp();
	});
})

Now we don’t have to repeat ourselves in each test and can keep the it blocks specific to the particular instruction.

Mocha also has before and after functions, which allow us to run a one-time set of instructions before/after the entire test suite:

describe('car driving to work', function() {
	before(function() {
		startCar();
	});

	beforeEach(function() {
		slowDown();
	});

	it('should turn left at 32nd', function() {
		turnWheel(360deg);
		expect(direction).to.equal('north');
	});

	// more tests here

	afterEach(function() {
		speedUp();
	});

	after(function() {
		turnCarOff();
	});
})

We only need to start the car at the start of our test suite. It would be needless to start/stop it after each test. You can use before, beforeEach, afterEach and after to help keep your tests clean and efficient.

Handling traffic delays - Promises

One last example to end our analogy. Even though you’ve written your directions out entirely before hand, the robot can’t just execute them all at once. We also can’t rely on timing, as the amount of time it takes to get to a street depends heavily on traffic getting in the way.

Similarly, the browser gets held up with network lag, busy servers and so on. Each action requires an undefined amount of wait time to execute.

To get around this issue, we use promises to manage the wait. With promises, we can write code sequentially, with the delay handling built in. We do need to pass the promises around though, so Mocha and WebdriverIO are in-sync.

WebdriverIO comes with built-in promises, so making use of them is as simple as returning the WebdriverIO browser object to Mocha’s it statement (in our example, car is a substitute for browser):

it('should turn left at 32nd', function () {
	car.turnWheel(360deg);
		
	expect(car.direction).to.equal('north');

	return car;
});

By passing car back to Mocha, Mocha knows to wait until car says it’s complete before moving on.

There’s a problem with the above test though. Our assertion doesn’t wait until the car has turned to check the expectation. This is due to car.turnWheel being an “asynchronous” function, as we alluded to above. We tell our Selenium robot to turn the wheel, but it moves on to the next line of code before validating the wheel turning has completed.

To wait until the steering wheel has turned, we need to take advantage of the promise-specific then function:

it('should turn left at 32nd', function () {
	car
		.turnWheel(360deg)
		.then(function(){ 
			expect(car.direction).to.equal('north');
		});		

	return car;
});

The code inside the then function will wait until turnWheel has “resolved” (i.e. completed) to run.

While useful, have .then scattered everywhere can be a mess. As an alternative, you can hook up Chai As Promised to help clean things up:

it('should turn left at 32nd', function () {
	car
		.turnWheel(360deg)
		.getDirection().should.eventually.equal('north');

	return car;
});

Finishing Up

That’s the end of the analogy. To sum up:

  • Browsers are the car to drive. They’re driven using installed Selenium browser drivers.
  • Selenium is the robot that knows how to drive the car.
  • WebdriverIO provides the directions to take to Selenium, in a language that’s easy for us to write in.
  • Assertions help validate the speed limits are followed. Chai is a common assertion library used.
  • Hooks such as before/after help reduce code duplication, making it easier to take turns safely
  • Promises help with handling delays due to congestion along the way

Enjoy this content? Take it a step further with my free course on Visual Regression Testing with WebdriverIO