Automated Browser Testing With The WebDriver API

About The Author

Jason is a professional cat wrangler working on the web platform engineering team with Microsoft. He hails from Melbourne, Australia and now calls Seattle home. … More about Jason ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

Manually clicking through different browsers as they run your development code, either locally or remotely, is a quick way to validate that code. However, it’s not a solution for testing the full breadth of your site’s code base on the assortment of browsers and device types available to your customers. That’s where automated testing really comes into its own. In this article, Jason McConnell provides an overview of the concepts, technologies and coding techniques involved with running test scripts against browsers automatically using WebDriverJS on Windows 10 and Microsoft Edge.

This article provides an overview of the concepts, technologies and coding techniques involved with running test scripts against browsers automatically using WebDriverJS on Windows 10 and Microsoft Edge.

Manually clicking through different browsers as they run your development code, either locally or remotely, is a quick way to validate that code. It allows you to visually inspect that things are as you intended them to be from a layout and functionality point of view. However, it’s not a solution for testing the full breadth of your site’s code base on the assortment of browsers and device types available to your customers. That’s where automated testing really comes into its own.

Spearheaded by the Selenium project, automated web testing is a suite of tools to author, manage and run test against browsers across platforms.

WebDriverJS API

The WebDriver API is a standard that abstracts out the device/browser specific bindings from the developer so that test scripts written in your language of choice can be written once and run on many different browsers via WebDriver. Some browsers have built-in WebDriver capabilities, others require you to download a binary for your browser/OS combination.

webdriverjs api

Driving The Browser Through WebDriver APIs

The WebDriver spec at the W3C documents the APIs available to developers to programmatically control the browser. This diagram shows a sample page with some of the general WebDriver collections and APIs that can be used to get and set browser properties.

webdriverjs api

Authoring Tests

You have a choice of languages based on the supported language bindings for WebDriver. The core languages supported by the main Selenium/WebDriverJS project include:

  • C#
  • Java
  • JavaScript (via Node)
  • Python
  • Ruby

Tests can vary from checking layout of the page, values returned from server-side calls, expected behavior of user interaction to workflow verification like ensuring a shopping cart workflow works as expected.

For illustrative purposes, let’s assume we’re testing the TODOMVC application, a demo application that's implemented in several different model-view-control JavaScript frameworks. This simple application provides UI to enter To-Do items, edit, delete and mark items as complete.

We will then be able to demonstrate running the tests for the React example against the Backbone.js and Vue.js examples by simply changing the URL.

For this demonstration, we’re going to write tests in JavaScript running in node to:

  1. Add three to-do items and verifying what we typed in was created into a to-do item.
  2. Modify that item by double clicking, sending backspace keyboard commands and adding more text.
  3. Delete that item using by using the mouse APIs.
  4. Check an item off the list as completed.

Setup Your Basic Automation Test Environment

Let's begin by getting our Windows 10 machine setup to run WebDriver using JavaScript. Calls to WebDriver from node are going to be almost always asynchronous. To make the code easier to read we have used ES2016's async/await over Promises or callbacks.

You’ll need install node.js newer than v7.6 or use Babel to cross compile to have support for the async/await feature. Also, we use Visual Studio Code for editing and debugging node.

WebDriverJS For Microsoft Edge

Each browser will have a binary file that you will need locally to interact with the browser itself. That binary is called by your code through the Selenium WebDriver APIs. You can find the latest downloads and documentation for the Microsoft Edge WebDriver here.

Note that the version of Edge you want to run the tests against must be tested with the matching version of MicrosoftWebDriver.exe. We’re going to be using the stable version of Edge (16.16299) with the corresponding MicrosoftWebDriver.exe version 5.16299.

Place the MicrosoftWebDriver.exe in your path or in the same folder that your test script will run. Running this executable will start a console window showing you the URL and port number that WebDriverJS will be expecting to handle requests to be sent.

WebDriverJS For Other Browsers

You can easily tell WebDriverJS to run tests in a different browser by setting a configuration variable and having the appropriate binary driver for the respective browser installed. You can find them here:

Selenium WebDriverJS For JavaScript

To interact with the binary driver you just downloaded via JavaScript, you’ll need to install the Selenium WebDriver automation library for JavaScript. This can be easily installed as a node package using:

npm install selenium-webdriver

Writing Automation Code

Once your browser specific driver binary is in your system’s path or local folder, and you’ve installed Selenium WebDriver via npm, you can start automating the browser through code.

Let’s break down our example code into the various steps you’ll need.

  1. Create a local variable to load and interact with the library.
    var webdriver = require('selenium-webdriver');
  2. By default, WebDriverJS will assume you’re running locally and that the driver file exists. Later we’ll show how you can pass configuration information into the library when instantiating the browser the first time. WebDriverJS gets instantiated with a configuration variable called “capabilities” to define which browser driver you want to use.
    var capabilities = {
        'browserName': 'MicrosoftEdge'
      };
      var entrytoEdit = "Browser Stack";
    
  3. Then you create a variable and call build() with the capabilities config variable to have WebDriverJS instantiate the browser:
    var browser = new webdriver.Builder().withCapabilities(capabilities).build();
  4. Now that we can interact with the browser, we tell it to navigate to a URL using the `get` method. This method is asynchronous so we use `await` to wait until it finishes.
    // Have the browser navigate to the TODO MVC example for React
        await browser.get('https://todomvc.com/examples/react/#');
    
  5. For some browsers and systems, it’s best to give the WebDriverJS binary some time to navigate to the URL and load the page. For our example, we wait for 1 second (1000ms) using the manage function of WebDriverJS:
    //Send a wait to the browser to account for script execution running
        await browser.manage().timeouts().implicitlyWait(1000);
    
  6. You now have a programmatic hook into a running browser through the browser variable. Note the collection diagram earlier in this document that shows the WebDriver API collections. We use the Elements collection to get specific elements from the page. In this case, we’re looking for the entry box within the TODOMVC example so we can enter some TODO items. We ask WebDriverJS to look for elements that match the class rule .new-todo as we know that’s the class assigned to this field. We declare a constant as we cannot change the data that comes back — just query it. Note this will find the first element in the DOM that matches the CSS pattern which is fine in our case as we know there’s only one.
    const todoEntry = await browser.findElement(webdriver.By.css('.new-todo'));
  7. Next, we send keystrokes to the field we just got a handle to using the sendKeys function. We put the escaped enter key on its own await line to avoid race conditions. We use the for (x of y) iteration pattern as we’re dealing with promises. toDoTestItems is simply an array of 3 strings, one string variable (which we’ll test against later) and 2 literals. Under the covers, WebDriverJS will send individual characters of the string one at a time, but we just pass the whole string variable to sendKeys:

    var toDoTestItems = [entrytoEdit, "Test Value1", "Test Value2"];
        //Send keystrokes to the field we just found with the test strings and then send the Enter special character
        for (const item of toDoTestItems) {
          await todoEntry.sendKeys(item);
          await todoEntry.sendKeys("\n");
        }
    

At this point, let’s run the script with node and see if we see a browser that navigates to the page and enters those three test TODO items. Wrap the code after the first variable declaration in an async function like this:

async function run() {

Close off the function } at the end of the code, then call that async function with:

run();

Save your JS file. Go to the node command window, navigate to the folder you saved the JS file in and run node yourfile.js

You should see a browser window appear and the text sent to the TODOMVC file be entered as new TODO entries in the application. Congratulations — you’re up and running with WebDriverJS.

Try changing the URL that WebDriverJS loads in this example to one of the other TODOMVC samples and observe that the same test code can run against different frameworks.

await browser.get('https://todomvc.com/examples/vue/');

Running Tests On BrowserStack

We’ve shown how this test runs locally on your machine. The same test can run just as easily using online test services like BrowserStack. Sign up for free access to the BrowserStack service to get access to Microsoft Edge browsers for free live and automated testing. Once signed in, go to the "Automate" section and look up your automated test account settings. You’ll need to pass these to the WebDriverJS function to sign in via code, name your test session and pass in your access token.

Then simply add those values into the capabilities variable and call the WebDriver builder again.

var capabilities = {
    'browserName': MicrosoftEdge,
    'browserstack.user': 'yourusername’,
    'browserstack.key': 'yqniJ4quDL6s2Ak2EZpe',
    'browserstack.debug': 'true',
    'build': 'Name your test'
  }

You can learn more about the capabilities variable and values BrowserStack can accept here.

Then call the builder function and pass in the BrowserStack server URL:

var browser = new webdriver.Builder().
    usingServer('https://hub-cloud.browserstack.com/wd/hub').
    withCapabilities(capabilities).
    build();

Finally, you should instruct WebDriverJS to quit the browser or else it will stay running and eventually time out. Place a call to the quit function at the end of your test file.

browser.quit();

Now when you run your JS test file using NodeJS, you’ll be sending the test instructions to a browser hosted on BrowserStack’s cloud service. You can go to the "Automate" section of BrowserStack and observe the test jobs starting and stopping. Once complete, you can browse through the WebDriver commands that were sent, see images of the browser screen at intervals during the test run and even see a video of the browser session.

A screenshot of the visual log feature of a test run on BrowserStack’s Automate service
A screenshot of the visual log feature of a test run on BrowserStack’s Automate service

Testing Values With Assertions

When testing your site, you are comparing actual results with expected results. The best way to do that is through assertions where an exception will be thrown if an assert condition is not met. In our example, we use an assertion library to express those assertions and help make the code more readable. We chose ChaiJS as its flexible enough to be used with any JavaScript library and is quite popular as of the time of writing.

You download and install Chai as a node package using npm. In code, you need to require chai:

var expect = require('chai').expect;

We decided to use the Expect interface to use natural language to chain together our assertions.

You can test for length, existence, contains a value, and many more.

expect(testElements).to.not.have.lengthOf(0);
  //make sure that we're comparing the right number of items in each array/collection
  expect(testElements.length).to.equal(toDoTestItems.length);

Should one of these assertions not be true, an assertion exception is thrown. Our sample code will stop executing when the exception is thrown as we’re not handling the exception. In practice, you’ll use a test runner with node that will handle the exceptions and report out test errors and passes.

Automating Test Passes With A Test Runner

To better handle the assertion exceptions, a test runner is paired with node to wrap code blocks containing test assertions in try/catch-style functions that map the exceptions to failed test cases.

In this example, we chose the MochaJS test framework as it pairs well with Chai and is something we use to test our production code.

To integrate the runner, there is both code added to the test script and a change in the way you run the code with node.

Adding Test Runner Code

You wrap test code into async functions with the top level function using the keyword “describe” and the subtest function using keyword “it.” The functions are marked up with a description of what the tests are looking for. This description is what will be mapped to test results.

MochaJS is installed as a node package via npm.

Here’s the top-level function in our sample using describe:

describe('Run four tests against TODOMVC sample', async () => {

Subsequently, wrap your logical tests into groups with the it keyword:

it('TEST 3: Delete a TODO item from the list by clicking the X button', async () => {

Assertions wrapped within these functions that cause an exception will be mapped back to these descriptions.

Executing The Code With NodeJS And MochaJS

Finally, you need to run your test code with node calling the MochaJS binary to handle the exceptions correctly. Mocha can be passed arguments to configure timeout values, the folder to look for that holds your test files and more. Here’s the configuration we used for Visual Studio Code to attach the debugger and use Code’s inspection and step through features:

{
            "type": "node",
            "request": "launch",
            "name": "Mocha Tests",
            "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
            "args": [
                "-u",
                "tdd",
                "--timeout",
                "999999",
                "--colors",
                "${workspaceRoot}/test/**/*.js"
            ],
            "internalConsoleOptions": "openOnSessionStart"
        }

Automated testing is a great way to ensure your site works across a variety of browsers consistently without the hassle or cost of testing manually. The tools we’ve used here are just a few of the many choices available but illustrate the common steps involved in setting up and executing automated tests for your projects.

Further Reading

Smashing Editorial (rb, ra, yk, il, mrn)