Badger Academy Week 8 - Frontend testing using WebdriverIO, Stubby and CucumberJS

Over the past few weeks on Badger Time, we've had a steady workflow for the API where we followed TDD principles of writing feature tests first and code later. It wasn't an issue to set that up in Rails as the various gems already out there for Ruby (RSpec/FactoryGirl specifically) made it a breeze.

The frontend was a different beast altogether and required quite a lot more thought which we finally decided to give over the past week.

The problems and their eventual solutions.

There were several problems which we struggled to solve initially. Firstly, we had to run a GhostDriver instance which would allow our testing suite to communicate with PhantomJS. We'd also have to run a Node server simultaneously which would serve the app in a test environment to the PhantomJS browser.

Doing this was a bit tricky; Gulp's asynchronous nature meant that running those background processes from within Gulp was a no-go. Depending on how quickly or how slowly it launched, some tests would pass or fail as the server might not be up before the tests ran.

It was probably more effort than it was worth to find a workaround for it so we simply added the processes as a part of the container's boot sequence. As our containers were based on Phusion BaseImage it was a case of adding simple init scripts to BaseImage's custom init process.

start-stop-daemon --start --background --quiet --exec /bin/bash -- -c "/usr/bin/phantomjs --webdriver=8080 --remote-debugger-port=8081 --ignore-ssl-errors=true > /tmp/phantom.log"start-stop-daemon --start --background --quiet --exec /bin/bash -- -c "node /data/server.js"

That was one catch out of the way. The next issue we faced was actually running the tests. Previously we took advantage of gulp-run to pipe our compiled spec files (we wrote the tests in LiveScript!) to the CucumberJS executable.

This was a bit overkill and we ended up just using Node's script system to run the compile task then run the CucumberJS task on the appropriate files. As a side-effect, we got really nice formatting on the tests so we could see exactly what went wrong (if it failed).

Screen Shot 2014-10-20 at 11.16.47

Nice!

We had these tests running with the API endpoint set as a local Stubby mock API. Stubby's Node implementation gave us a programmatic API which meant we could start, stop and modify the API as our tests were running.

This allowed us to feed data using Gherkin (Cucumber language) data tables to a function which would simply modify an endpoint with the supplied data. It removed our dependency on the real API to have the frontend tests working, which reduced our CircleCI build times from a staggering 15-20 minutes down to 2-3.

A look at WebdriverIO

Selenium WebDriver is somewhat of an elephant in the office at Red Badger. We all dislike it - even you Pete, you just don't know it yet - but we put up with it. The API is just a bit rubbish and documentation is quite difficult to find. As somebody working out its usage from scratch, I can say my options were quite limited; spend hours sifting through Java documentation and hope it works the same in the JavaScript implementation or go through endless amounts of user issues trying to find a solution which matches my own problem.

That's where WebdriverIO helped tremendously. It's a JavaScript wrapper to Selenium's confusing API and offers quite a few helpful additions of its own. Just having documentation - however incomplete it might be - was a godsend. At least the functions which weren't documented have a link to their source so we can see what's going on and extrapolate from that.

How LiveScript facilitates the callback-based nature of CucumberJS

If you're familiar with the term 'callback hell' then you know how asynchronous code can be a real pain deal with, as you end up with nested logic inside nested logic inside a browser action, all ending with a callback to the top level to pass (or fail) the test. Take this simple example of a browser action which would type a phrase into an input on the screen. In JavaScript, we can immediately see why it quickly grows into something that isn't nice to deal with.

We take advantage of LiveScript's unnested callbacks to offer code which is functionally the same as the example above, but reads and writes like synchronous code (much easier to handle).

Writing our tests is inherently easy due to the way Cucumber works and in most cases we don't even need to write any code for new features as we recycle logic from the more generic step definitions. 

We're excited to finally be able to adhere to BDD principles on our frontend. After all, the whole premise of Badger Academy isn't to ship a finished product, but to bring our code quality and knowledge to a higher level.