JavaScript with Selenium WebDriver and Mocha

I talked previously about using Selenium WebDriver in the context of JavaScript. There I used Jasmine as a test runner for Selenium. Here I want to expand on this by using the arguably more popular Mocha as well as get into more depth on automation construction.

One thing you’ll note if you research automation with JavaScript, particularly in the context of using Selenium, is that there is a lot of fragmentation in the JavaScript test solution space right now. You’ll hear of tools like NightwatchJS, DalekJS, Intern, and Testium. But you’ll also come across Cabbie, Moonraker, Nemo, and Leadfoot. To top it off, if you look for “WebDriver Selenium” you’ll probably come across mention of “WebDriverJS”. There are two aspects to this, however. The official JavaScript-Selenium bindings are called WebDriverJS but there’s also a chance you’ll come across WebDriverIO, which was also called WebDriverJS at one point.

There are plenty of other tools (whether library, engine, framework, or runner) you’ll come across like WD.js, selenium-node-webdriver, webdriver-sync, and so on and you’d be forgiven for thinking that the JavaScript solution space is in too much flux right now to be useful. One thing I’ll note is that many of these solutions rely on what are called chain-based APIs. I won’t explain too much about that in this post but I will have a follow on post that goes into some detail. My reason for bringing it up here is because I — and many other automation solution providers — feel that this type of API is ultimately harmful to automation. When you rule out such approaches, your tool options actually become a little more clear.

To following along, you might want to follow my Setting Up WebDev instructions. Basically, you just need Node.js and npm. An install of Node should get you npm automatically. I’ve covered learning Node.js in a few posts.

Creating the Project

Let’s first create a simple project for ourselves. I do everything pretty much from the command line.

$ mkdir test-project
$ cd test-project
$ touch spec.js

If you’re on Windows and not using a Bash-type shell (either via Git, GNU Tools, or Cygwin) just create a file called spec.js.

Now I’ll use npm to create the initial package.json file and populate a node_modules directory with the relevant packages. If all of this means nothing to you, you can just use the commands I provided, or read some of my previous tutorials on writing test solutions with JavaScript.

$ npm init
$ npm install --save selenium-webdriver
$ npm install --save mocha
$ npm install -g mocha

As you can see, I’m using the Selenium WebDriver package (which is called WebDriverJS) and Mocha. I’m also installing Mocha globally to make the ‘mocha’ test command work from anywhere, although you could alter your package.json to make this unnecessary.

Setting Up the Automated Check

Let’s put the following in the spec.js file:

I’m using my own Decohere app here, which is currently a work in progress. Feel free to change up the examples with a web site of your own choosing.

Let’s run the script:

$ mocha spec.js

This won’t work. You are most likely going to get an error that looks something like this:

1) calculating weights calculates weights:
     AssertionError: Weight entry not possible
      at Context. (spec.js:13:12)

So what’s going on here? Well, you’re running smack into one of those big differences in JavaScript from just about every other language you used Selenium in. WebDriverJS is asynchronous. What this means is that it won’t automatically wait for one command to run before running another. So, in this case, it doesn’t wait for a browser to be available and ready before doing attempting any actions with that browser.

This is something you definitely have to get used to if you are going to work in JavaScript and I’ve found it is difficult for automation writers who are used to working with a synchronous API like you would find with Java, C#, Ruby, or Python. In the JavaScript context, two particular mechanisms are used to deal with asynchronous behavior: callbacks and promises.

If you have any experience at all with JavaScript, the term “callback” may scare you a bit because you’ll know that callbacks can be hard to maintain and are often not readable once your code gets to any sort of reasonable size. Promises are a little better as a general rule. Do know, however, that the basic operation of both is the same: the idea is to chain operations together so that operations can be fired off in a certain order.

WebDriverJS uses promises to allow code to run in a particular order and a promise manager to provide the scheduling and execution of all promises automatically. The promise manager is provided as a test wrapper for Mocha and you can use selenium-webdriver/testing for this. To do this, you have to replace Mocha’s describe and it methods with WebDriverJS’s promise manager version, as such:

That’s a start but you’ll find it still doesn’t help. You’re going to get the same error. Why is that? While it’s true that the use of the promise manager helps with the ordering of execution, anything that retrieves a value from the browser (such as checking whether an element exists as I’m doing here) must be written using an actual promise. So let’s modify the test as such:

If you run that, you’ll at least see a browser open up. But you’ll get another error:

(1) calculating weights calculates weights:
     Error: timeout of 2000ms exceeded.
     Ensure the done() callback is being called in this test.

As you can see there, Mocha has a low maximum time it will allow a test to execute for before triggering an automatic fail. This low time is because Mocha is often used as a unit test runner and those tests should, of course, operate very fast. When you use Mocha as a runner for browser-based tests like this, however, is pretty low. We’re going to to set the time to a bit longer, say 15 seconds. So modify the script as such:

Once a timeout of 15 seconds (which is 15,000 milliseconds) is in place, you should find that running the script works in terms of the check passing.

Adding Another Check

Just to be sure we got the hang of it, let’s add another test:

As before, you should find that this will work in the sense that both checks pass. Incidentally, you can run just one test from the spec.js file if you want. This requires having the text of your ‘it’ blocks distinct enough that they can be discovered. For example, if I just wanted to run only the second test, I could do this:

$ mocha -g "default weight" spec.js

There is also an ‘.only()’ method that you can apply to a specific test. So to achieve the same effect as the above command line, I could have modified the second ‘it’ to read like this:

I’m not too keen on that method for the obvious reason that I have to then add and remote that ‘.only()’ method call.

Cleaning Up The Script

This is fairly bad code at this point. There is a lot of duplication. Note that the code itself has each test opening the browser, navigating to the page, and then finally performing the relevant action. Each test also quits the browser. The only thing unique to each test is the test condition. And, in fact, that should be the only unique thing to the test. So what I have to do is factor out the common material. But where? Well, lucky for us, Mocha, as with most test runners, provides hooks. We can use these hooks to create our browser, navigate to the page, and then quit the browser.

Add the following to the script:

You’ll find that this does the same thing as the previous execution but this clears up the code of the tests quite a bit. I’ve used the ‘beforeEach’ and ‘afterEach’ hooks to say what should happen before each test method is executed and after each test method is executed. Since this is before and after each, you’ll of course note that we still open a new browser for each test. Should you want to keep the same browser open you could do this:

Here I’m using ‘before’ and ‘after’ and that means the code in each block will run before and after all of the tests. So the browser will only be opened once and closed when all tests are completed.

A challenge here is that the browser now is using the same session for each test. That won’t matter for this particular test but if you have a page that is generating cookies or session information, you can ceratinly run into issues where a second test is reusing the session from the first test. One way people like to handle that is to put in place the following:

Note I’m using the ‘afterEach’ here to say that after each test is run, the cookies should be cleared. However this will do nothing for server side session cookies nor will this work on situations where a session variable is being held (such as with Rails, Django, etc). While keeping a browser open during tests can save startup and close-down time, you do have to be aware of the context of the tests that is happening with and determine if there’s any chance for leaky state (i.e., session information) to be causing problems for later tests.

Adding Page Objects

The tests are pretty nicely cleaned up at this point but we still have some duplication here in the fact that we are finding an element. Let’s use the very common page object pattern. First we’ll create a page file:

$ touch planet-page.js

Add the following to it:

Here I created a ‘view’ function. I’m returning a promise here which is needed to tell the promise manager that the actions of the function have been fulfilled. Now change the existing test script so that the ‘beforeEach’ hook uses the new page object:

Everything should work as before if you run this. You’ll note that I removed the ‘driver.get()’ call, since that’s now being handled by the page object, and instead just instantiated the page object and then called the ‘view()’ function on it.

We can do a similar thing for our actions regarding checking the existence and the text of the weight text field. Add this function to the page:

You’ll see here that I’m returning a promise. I’m doing that because the then() promise that I have in place is only fulfilled within the promise of the function that is being called: i.e., this function which will be called from my test. So I have to make sure that the promise fulfilled in this function is then returned to the test itself, which will trigger the Mocha promise manager to recognize the promise as being fulfilled within the context of the test.

That can sound confusing. This is an area where you really have to start to learn to think in terms of JavaScript promises.

Now change the first test to this:

If you run the script, everything should still work as before. Now let’s add the other component to our page object. Add this function to the page:

With this in place, you should change the second test to this:

Everything should continue to work. This is all working fine but if you’re like me, you can’t help but feel this is all really complicated looking. So let’s do a little more cleanup.

Cleaning Up the Logic

Going to our page object for a second, you’ll note that I have some duplication in terms of finding the ‘wt’ (weight text field) element. Following the standards of the page object pattern, it would be better to store this as a property of the page. So change the page object accordingly:

A relatively minor cleanup, admittedly. So let’s look at a bigger one. One thing to note about the driver methods that you call is that many of them already return a promise. This means a lot of that logic that I have in place to make sure a promise is returned to the test can be removed and I can rely on the default promise mechanism that is part of the driver library. Specifically, here’s a simplification of the page object:

That actually looks pretty nice! Or at least about as nice and clean as you’ll get with JavaScript.

Let’s also go back to the test script for a second. I instantiate a new page object in each test. But I could just as easily just use the one that I already declare in the beforeEach as such:

At this point the tests themselves look pretty clean and they reference a page object that is in turn fairly clean. The beforeEach method is a little bit of admitted eye sore. But overall this should give you a fairly good start at using Selenium WebDriver (i.e., WebDriverJS) in JavaScript and having a reasonably clean set of expressive code.

Do You Want Coffee With Your Tests?

Some automation writers coming from languages like Groovy, Ruby, or Python can’t stand all the boilerplate bits of JavaScript. Personally, I would argue that it’s not that much worse than Java. But for those who simply cannot abide by the symbols and whatnot of JavaScript, you can try your hand at writing all of the logic in CoffeeScript if you want.

$ npm install --save coffee-script

You might get a warning about coffee-script should be installed globally (with a -g). Don’t worry about that for now. With this package installed, change your spec.js file to spec.coffee. Then modify the contents as such:

You can’t just run ‘mocha spec.coffee’ at this point because you have to compile the CoffeeScript. So what you can do is this:

$ mocha spec.coffee --compilers coffee:coffee-script/register

I’m not going to go into too many details about CoffeeScript execution here. I just wanted to show you that this was possible and that if you didn’t like JavaScript’s extra details, then you could try to use CoffeeScript to get rid of those. Some people hate CoffeeScript, some love it. I’m still on the fence with it, to be honest.

What Next?

Well, for now, I think this script has been taken as far as it can go for one post. I mentioned earlier that there a lot of frameworks out there in the JavaScript world and many of them are predicated upon so-called chained APIs. In a follow on post to this one, I’ll be sure to cover some of what that means.

Share

About Jeff Nyman

Anything I put here is an approximation of the truth. You're getting a particular view of myself ... and it's the view I'm choosing to present to you. If you've never met me before in person, please realize I'm not the same in person as I am in writing. That's because I can only put part of myself down into words. If you have met me before in person then I'd ask you to consider that the view you've formed that way and the view you come to by reading what I say here may, in fact, both be true. I'd advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.
This entry was posted in Automation, JavaScript, Selenium, Test Solutions. Bookmark the permalink.

3 Responses to JavaScript with Selenium WebDriver and Mocha

  1. julg says:

    Thanks! Great post. It helped me a lot.

    In addition, selenium-webdriver contains an (experimental) assertion library, which is handy, when it comes to writing assertions against promises.

     

    var sassert = require('selenium-webdriver/testing/assert');
    
    sassert(driver.findElement(By.id("something")).getText()).equalTo('value');
    sassert(driver.isElementPresent(By.id("something"))).isTrue();

     

  2. praveen says:

    Thank you so much for the Article. The Article is simple and each concept is covered in depth.  Please keep on posting more articles in this topic.

  3. oleg says:

    Thanks a lot!

    Each step is explained in detailed way.

    Very handy. Keep writing, you’re doing great!

    Chears.

Leave a Reply

Your email address will not be published. Required fields are marked *