4 reasons why you should consider using WebdriverIO for testing

For the past year I've been digging in to the WebdriverIO test framework. I'm really enjoying my time with it and feel like it definitely deserves more attention in the Front-end landscape.

Here's why:

It's Front-end Friendly

Unlike most browser automation frameworks out there, WebdriverIO is written entirely in JavaScript. Even the Selenium installation is done through an NPM module*.

I always thought browser automation meant figuring out how to get some complex Java app running, which just never sounded appealing to me. There's also the Selenium IDE, but writing tests through page recordings reminded me too much of WYSIWYG web editors like Microsoft FrontPage.

Instead, WebdriverIO lets me write in a language I'm familiar with, and integrates with the same testing tools that I use for unit tests (Mocha and Chai). The mental load to switch from writing the actual functionality in JavaScript to writing the test code in JavaScript is minimal, and I love that factor.

The other great thing, and this is more to credit Selenium than WebdriverIO, is that I can use advanced CSS selectors to find elements. xPath scares me for no good reason. Something about slashes instead of spaces just chills my bones. But I don't have to learn xPath. Using WebdriverIO, I simply pass in my familiar CSS selector and it knows exactly what I'm talking about.

I believe Front-end developers should write tests for their code (both unit and functional). WebdriverIO makes it incredibly easy to do.

It Has the Power of Selenium

Because WebdriverIO uses Selenium under the hood, I'm able to run my tests in all sorts of browsers. I always felt held back when writing tests in PhantomJS, knowing that it could never validate functionality in popular but buggy browsers like IE.

Selenium, on the other hand, is an incredibly robust platform and an industry leader for running browser automation. WebdriverIO stands on the shoulders of giants by piggybacking on top of Selenium. All the great things about Selenium are available, without the overhead of writing Java based tests.

There's another functional/visual regression testing tool out there that I like called Gemini, which also hooks in to Selenium. They've got great docs and some really neat features to their tool. Yet they don't expose the Selenium API the same way WebdriverIO does.

Gemini (last time I checked) seems to hide the fact that it's using Selenium to do its work. You can't ever get to the root browser object, only the Gemini object provided to you. WebdriverIO on the other hand lays it all out there for you to see. In the words of Tim Taylor on Home Improvement, "More Power!".

It Strives for Simplicity

The commands you use in your WebdriverIO tests are concise and common sense. What I mean is that WebdriverIO doesn't make you write code to connect two parts together that obviously are meant for each other.

For example, if I want to click a button via a normal Selenium script, I have to use two commands. One to get the element and another to click it. Why? It's obvious that if I want to click something, I'm going to need to identify it.

WebdriverIO simplifies the 'click' command by accepting the element selector right in to the command, then converts that in to the two Selenium actions needed. That means instead of writing this:

driver.findElement(webdriver.By.id('btnG')).click();  

I can just write this:

client.click('#btnG')  

It's just so much less mind-numbing repetition when writing tests...

Speaking of simple, I love how WebdriverIO integrates in to Selenium. Instead of creating their own Selenium integration, they use the common REST API that Selenium 2.0 provides and make plain old Request calls to it.

Here's an example from core WebdriverIO code of the elementActive protocol:

let elementActive = function () {  
    return this.requestHandler.create({
        path: '/session/:sessionId/element/active',
        method: 'POST'
    })
}

That's it. That's basically the entirety of the file/function. Understanding what's going on here is pretty simple. We're sending a POST request to the "element active" endpoint and returning the response. I'm not still learning Node.js, so it's refreshing to see software where I have some idea what's going on.

Most of the library is made up of these small commands living in their own separate small file. This means that updates are easier, and integration into cloud Selenium services like Sauce Labs or BrowserStack are incredibly simple.

Too many tools out there try and re-invent the wheel, just for the sake of it. I'm glad WebdriverIO keeps it simple, in turn helping me easily understand what's going on behind the scenes.

It's Easily Extendable

As someone who has spent a considerable portion of his career working for large organizations, it's important to me that the tools I'm using be easily extendable. I'm going to have custom needs and want to write my own abstractions, in order to reduce the burden on the developers I'm supporting.

WebdriverIO does a great job at this in two ways:

Custom Commands

There are a ton of commands available by default via WebdriverIO, but a lot of times you want to write a custom command just for your application.

WebdriverIO makes it easy to add new commands. Just call the "addCommand" function, and pass in your custom steps. Here's an example from their docs:

browser.addCommand("getUrlAndTitle", function (customVar) {  
    return {
        url: this.getUrl(),
        title: this.getTitle(),
        customVar: customVar
    };
});

Now, any time I want both the URL and Title for my test, I've got a single command available to get that data.

Page Objects

With the recent 4.x release of WebdriverIO, they introduced a new pattern for writing Page Objects. For those unfamiliar with the term, Page Objects are a way of representing interactions with a page or component.

Rather than repeating the same selector across your entire test suite for a common page element, you can write a Page Object to reference that component. Then in your tests, you just ask the Page Object for what you need and it handles it for you.

This means tests are both more maintainable and easier to read.

They're more maintainable because updating selectors and actions occur in a single file. When a simple HTML change to the login page breaks half your tests, you don't have to find every reference to input#username in your code. You only have to update the Login Page Object and you're ready to go again.

They're easier to read because tests become less about the specific implementation of a page and more about what the page does. For example, say we need to log in to our website for most of our tests. Without page objects, all our tests would begin with:

browser  
    .url("login-page")
    .setValue("#username", "testuser")
    .setValue("#password", "hunter2")
    .click("#login-btn")

With page objects, that can become as simple as:

LoginPage.open();  
LoginPage.login('testuser', 'hunter2');  

No reference to specific selectors. No knowledge of URLs. Just self-documenting steps that read out more like instructions than code.

Now, Page Objects aren't a new idea that WebdriverIO introduced. But they way they've set it up to use plain JavaScript objects is brilliant. There is no external library or custom domain language to understand. It's just JavaScript and a little bit of prototypical inheritance.

In Closing

I don't consider myself a tester. I'm far too clumsy to be put in charge of ensuring a bug-free launch. Yet I can't help but love what WebdriverIO provides me, and I'm really a fan of what they're doing.

If you're a front-end dev, or just familiar with JavaScript, check out WebdriverIO and see if you find it as awesome as I do.

Also, jump on over the to learn.webdriver.io page and scroll down to the bottom to find a free five-minute introduction video on the tool. If you like what you see, I go in to full detail in the course, providing you with everything you need to know about WebdriverIO.

* The Selenium install still requires Java, but overall it's a breeze to set up via NPM.