Good time management in CasperJS tests

Create advanced and efficient CasperJS tests for your web development projects.

(For more resources related to this topic, see here.)

When developing with JavaScript, we often need to chain two pieces of code (for instance, first we load some JSON data, and then we update the page content using that data).

But each step is generally non-blocking (meaning the rest of the code will continue to execute even if the step is not complete), and there is no way to predict when the first step will be complete.

The most solid and most common approach to solve this problem is the callback mechanism. We put the second piece of code in a function, and pass it as a parameter to the first one, so that it can call this function when it finishes.

As a result, there is no linear and predictably-ordered execution of the code, which makes testing a little bit tricky.

The following is an example.

Use case – simple place name matching with Geonames.org

<html> <head> <script type='text/javascript' src = 'http://code.jquery.com
/jquery-1.9.1.js'></script> <style> .searching { color: grey;} .success { color: green;} .noresults {color: red;} </style> </head> <body> <script> function geonamesSearch() { $('#results').html("Searching..."); $('#results').attr('class', 'searching'); var url = "http://ws.geonames.org/searchJSON"; var query = $('#searchedlocation').val(); $.getJSON(url + "?q="+ query +"&maxRows=25&featureClass=P", null, function(data) { var data = data.geonames; var names = []; if(data.length > 0) { $.each(data, function(i, val){ names.push(val.name +" ("+val.adminName1+")"); }); $('#results').html(names.join("<br/>")); $('#results').attr('class', 'success'); } else { $('#results').html("No matching place."); $('#results').attr('class', 'noresults'); } } ); } </script> <input type="text" id="searchedlocation" /> <button id="search" onclick="geonamesSearch();">Click me</button> <div id="results"></div> </body> </html>

This page contains a text input field, a button, and an empty div whose ID is 'results'. When we click on the button, the JavaScript function geonamesSearch does the following:

  • It puts the 'searching' class on the results div, and inserts the mention Searching...
  • It reads the current value of the text input
  • It calls the Geonames.org JSON web services to get the place names matching this value
  • This JSON call is performed by jQuery and we provides it with a callback function (that will be called when the Geonames web service will respond), which reads the results
  • If there is no result, it changes the results div class to 'noresults', and its text to No matching place.
  • If there are some results, it sets the class to 'success' and displays the matching place names.

We can try it with our web browser, and see it works nicely.

Testing our use case with CasperJS

Now let's test this page with the following CasperJS script which enters the value 'barcelona' and asserts we do get Barcelona (Catalonia) in the results:

casper.userAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X)'); casper.test.begin('Search a city by name', 1, function(test) { casper.start('http://localhost:8000/example3.html', function() { this.sendKeys("input#searchedlocation", "barcelona"); this.click("button#search"); }); casper.then(function() { test.assertTextExists('Barcelona (Catalonia)',
'Barcelona (Catalonia) has been found.'); }) casper.run(function() { test.done(); }); });

Note that we need to set up a regular user agent to make sure geonames.org will accept to process our request.

If we run it, we get a failure:

Why is that? It is because our this.click() triggers the geonamesSearch function and immediately after we try to assert the result content, but the Geonames web service had no enough time to respond, the content is not yet the one expected at the time the assertion is performed.

Timing is everything

The CasperJS then() blocks allow to make sure we will execute our test steps sequence in the right order, but they cannot guarantee that an asynchronous call performed in one of the block will be complete before we move to the next block.

To manage this kind of cases, CasperJS offers the ability to wait before executing the rest of our test sequence.

Here is a working test script:

casper.userAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X)'); casper.test.begin('Search a city by name', 1, function(test) { casper.start('http://localhost:8000/example3.html', function() { this.sendKeys("input#searchedlocation", "barcelona"); this.click("button#search"); }); casper.waitForSelector('div.success', function() { test.assertTextExists('Barcelona (Catalonia)',
'Barcelona (Catalonia) has been found.'); }) casper.run(function() { test.done(); }); });

We can see the tests pass successfully now:

With waitForSelector, we make sure the assertion will be performed only when the results div will have the 'success' class, and it will only happen once our JSON loading callback function has been called.

Summary

In this article, we learned how to test our use case with CasperJS and how timing is everything.

Resources for Article:


Further resources on this subject:


Books to Consider

comments powered by Disqus