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:
- Back out of driveway
- Turn left at the stop sign
- 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:
- Load the website URL
- Click on the ‘Contact’ link
- 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