Let’s have a look at a first example of writing and running a functional test. This is going to be a very basic hello world example, but still it gives an opportunity of looking at the bare minimum usage of WebDriverIO and a first taste of asynchronous programming with promises.
First, we have to add the webdriverio dependency to our repo with npm install -D webdriverio
. This is the client library that can connect to WebDriver protocol servers like Selenium, PhantomJS, etc.
We are going to take the hello world example from the WebdriverIO page:
var webdriverio = require('webdriverio');
var options = {
desiredCapabilities: {
browserName: 'firefox'
}
};
webdriverio
.remote(options)
.init()
.url('http://www.google.com')
.getTitle().then(function(title) {
console.log('Title was: ' + title);
})
.end();
and try to run it with mocha.
Before we start, some observations on the code:
- it defines a set of options to initialize the WebdriverIO object with. The available options are documented here. For example, you can fine-tune your timeouts for how much WebdriverIO should wait for something to load on your site before giving up.
- Then, it initializes the WebdriverIO client with the
remote
andinit
methods - It navigates to the homepage of Google with the
url
method - It requests the page's title with the
getTitle
method - It prints that title to the console
- It terminates the session with the
end
method
All these methods are documented in the API section of WebDriverIO. The coding style may look strange at first. Instead of having one function call per line, we have chained functions operating on top of the previous function’s result. The reason is that we’re dealing with an asynchronous API that uses promises. A promise is a way of returning something that will eventually return a value. Common pitfalls in writing functional tests originate by not properly chaining promises together.
So, our first attempt is to wrap this code in a unit test:
var expect = require('chai').expect;
describe('Example Functional Test', function() {
it('should work', function() {
var webdriverio = require('webdriverio');
var options = {
desiredCapabilities: {
browserName: 'firefox'
}
};
webdriverio
.remote(options)
.init()
.url('http://www.google.com')
.getTitle().then(function(title) {
expect(title).to.equal('Google');
})
.end();
});
});
In order to run this, you need to have Selenium or PhantomJS listening, otherwise the test will fail. To use phantom, run it with phantom --webdriver=4444
which is the default port for webdriver.
If you run this test, it will work and the test will pass. But, it is actually broken. If you modify the assertion and demand that the title equals to ‘Yahoo’, the test will still pass! This is a broken, evergreen test.
The reason is that WebdriverIO is based on asynchronous code and promises. The unit test executes before the asynchronous code has completed its job. We have to tell mocha about this so that it can wait properly for the asynchronous code to finish.
There are two ways of doing that:
- add a
done
parameter to the unit test and make sure that that parameter gets called before the test exits. This way is rather verbose. - have the unit test return the promise so that mocha can wait for it. This is the easy way.
Let’s have a look at these two ways separately. Note that the first way, using the done
parameter, is more verbose and I don’t recommend it, because the other way is much simpler.
First, the usage of the done
parameter in the unit test:
it('should work', function(done) {
Note that the function that runs the test has a parameter. Mocha will understand that the test will be complete only when somebody calls that done
function. We therefore have to do that at the end of the test:
getTitle().then(function(title) {
expect(title).to.equal('Yahoo');
})
.end()
.then(function() {
done();
}, function(err) {
done(err);
});
After the end
call, we have attached another promise that calls the done function. If the test succeeds, it calls the done
without arguments. If the test fails, it calls the done
function with the error that happened. This allows mocha to correctly report the test’s status.
This is a bit too verbose. A much easier way is to simply return the promise and not use the done function at all. Mocha does the rest for us:
var expect = require('chai').expect;
describe('Example Functional Test', function() {
it('should work', function() {
var webdriverio = require('webdriverio');
var options = {
desiredCapabilities: {
browserName: 'firefox'
}
};
return webdriverio
.remote(options)
.init()
.url('http://www.google.com')
.getTitle().then(function(title) {
expect(title).to.equal('Google');
})
.end();
});
});
We took out the done
parameter completely. Notice that now the promise chain is being returned from the unit test. This is all mocha needs in order to tie the loop together and make sure we don’t have an evergreen test.
In the next post we’ll add a second test and start working towards giving some structure to these tests, taking advantage of mocha’s before and after hooks.