Automation /

End to End Testing

Using an application to pretend to be a user.

Previous: Unit Testing Next: Regression Testing

End-to-end testing, also known as integration testing or browser testing, is the fun process of making a browser pretend to be a person, and automatically run through a series of operations as if it were an actual user using the website. This is by far the most powerful and effective way of testing a site, and unfortunately one of the more complicated.

The main difficulty that people find with end-to-end testing is Selenium WebDriver. Selenium has a long history of automating the process of using a browser, and has evolved over the past 8 years or so to become a hugely powerful tool. It's used in a variety of applications, mainly around testing but also web scraping and automation. With that power comes complexity, and unfortunately the developers haven't really gone a long way to make it user friendly. That's not to be disrespectful. It's an amazing tool that makes the web better. It's just tricky to get to grips with.

There are a lot of ways to use Selenium. There are bindings for most languages, particularly Java, Python and JavaScript. In this book we're going to use JavaScript because it's a language that's incredibly common among web professionals, and if you don't already know it then it's something that will prove useful beyond testing in building websites. Plus it's what I prefer.

Before you write your first test it's well worth looking in to a handful of test runners and testing frameworks to find one you like. What you use has no impact on page weight or website bloat so there's no worry about making your website code worse by using a testing framework. You could write all the code yourself, but frankly it's really not worthwhile because testing frameworks are both mature and generally pretty awesome, so I won't even begin to cover how you'd test something with vanilla nodejs.

Here are a handful of the more popular test runners and frameworks;

Framework Features
Nightwatch Easy to use
Protractor AngularJS bindings
Jest Fully integrated
Karma ?
Mocha + Chai ?

Which framework you choose really depends on what you're testing and the way you prefer to write tests. For example, if you're writing an AngularJS application then Protractor is the obvious choice - it's written for Angular and let's you write tests that hook straight into Angular's directivs. That's pretty handy. Alternatively, if you're writing plain HTML and jQuery then Nightwatch is a great framework to use. And then there's Mocha+Chai, or Karma, or CucumberJS, and more besides. There's a lot of options, and each offers something slightly different.

Because there are different options a lot of the examples in this book are shown with alternative syntax for different frameworks.

When you have a framework in mind, or perhaps before if you're planning to try a couple of them, you'll need to install something to drive the browser and run the tests. Some frameworks install and manage this for you. If yours does that's great - you can skip this section.

Selenium WebDriver can be installed using NPM. This comes with a very basic setup for Selenium but no real browser drivers. Those are installed separately. For the purpose of this example let's assume that you want to test a webpage in Chrome and Firefox.

First of all, make sure you're running an up-to-date installation of nodejs and npm. nvm helps a great deal here. Once you're set with that you can install Selenium Webdriver;

npm install --save-dev selenium-webdriver

The --save-dev option adds Selenium WebDriver as a development dependency. This marks it as something that's only needed if you're doing development; it doesn't make a huge difference to what we're doing here but it keeps things tidy.

The next thing to do is to install Webdriver-manager. This is a commandline application that manages drivers for different browsers. It makes things much simpler.

npm install -g webdriver-manager

Once that's installed run;

webdriver-manager update

This command will fetch the latest drivers for the browsers on your operating system. Finally you can start the webdriver-manager with;

webdriver-manager start

Now you should be all set to run your first test.

At this point we should take a moment away from testing to see what it's like to interact with a website using Selenium and nodejs. This is a good test to make sure the things we set up are working, and it's pretty good fun too which is possibly a bit more important really. It's nice to feel like we're making progress.

A Selenium WebDriver script is essentially a standard nodejs script that uses the selenium-webdriver package that we installed to talk to the Selenium WebDriver hub that we started using webdriver-manager. There's a couple of extra things that we need from the selenium-webdriver package too - the By class that's used to find things on the page, and the Until class that enables us to wait until a promise has been resolved.

var webdriver = require('selenium-webdriver'),
    By = webdriver.By,
    until = webdriver.until;
 
var driver = new webdriver.Builder()
    .forBrowser('firefox')
    .usingServer('http://localhost:4444/wd/hub')
    .build();
 
driver.get('http://www.google.com/ncr');
driver.findElement(By.name('q')).sendKeys('webdriver');
driver.findElement(By.name('btnG')).click();
driver.wait(until.titleIs('webdriver - Google Search'), 1000);
driver.quit();

There's a lot going on in example.js that's new, but there's nothing that's especially complicated.

var webdriver = require('selenium-webdriver'),
    By = webdriver.By,
    until = webdriver.until;

The first part of the script imports the selenium-webdriver package and puts By and Until into variables to make them easier to access.

var driver = new webdriver.Builder()
    .forBrowser('firefox')
    .usingServer('http://localhost:4444/wd/hub')
    .build();

Next we create a driver for firefox using the Selenium WebDriver server on the default location that webdriver-manager gave us.

driver.get('http://www.google.com/ncr');

A selenium-webdriver driver has a method of .get() that we use to load Google's homepage.

driver.findElement(By.name('q')).sendKeys('webdriver');

When the driver has the page (eg it's completed loading) it uses the findElement method to find an element in the DOM that has a name attribute of 'q'. This is what Google names the search box on their landing page. When it's been found the driver uses it's sendKeys() method to simulate the user typing 'webdriver'.

driver.findElement(By.name('btnG')).click();

This time we look for an element in the page with a name attribute of btnG, and tell the browser that the user's mouse has performed a click on it. Behind the scenes this is actually just firing Javascript's click event.

driver.wait(until.titleIs('webdriver - Google Search'), 1000);

The driver's .wait() method waits until the promise that until() has given us is resolved (or rejected). In this case the promise will only resolve when the browser's title is a Google search for 'webdriver'. If the title was something else the until() call would fail with an error. The 1000 value tells until() to wait for a maximum of 1000 milliseconds, or 1 second, before timing out. A timeout would also result in an error.

driver.quit();

When everything is done we close the driver's connection to Selenium in order to tidy up.

When you run this script using node example.js you should see a Firefox window open, navigate to Google, and perform a search for 'webdriver'. That's seriously cool. You have a huge amount of power using this stuff.

Tests look like a nodejs script. There's always some boilerplate to set things up at the beginning, and then maybe some functions that run prior to each group and every test that do things like setting up a page or logging into a website, and functions to reset the test environment between each test. Then there's the tests themselves. Regardless of which framework you've picked things generally always look pretty much like this.

One thing you may have already realised is that the .findElement() method in a selenium-webdriver driver object is only useful if you can write a query that actually finds the element on the page. If you're not careful you might not be able to write anything that finds what you're looking for. Or you might find that you're finding too much if your findElement query matches several things on the page.

It's worthwhile spending time thinking of good quality semantic names and IDs for your elements. If your HTML is generated by code then put in the effort to get it generating good quality HTML. Make sure you're not using IDs more than once. Don't fill pages with thousands of unnamed elements. If you change what a CSS class refers to then make sure you change the classname. Putting in the time to make valid, well-named HTML will make testing it much, much easier.

If you're testing code that you didn't write then you're going to find things a lot trickier. Sometimes you just can't write a nice CSS query. Fortunately there's an alternative - XPath. Unfortunately it's really horrible to use. Still, sometimes you need it, so it's covered in the Advanced Topics section.

Previous: Unit Testing Next: Regression Testing