[Developer Says] Writing functional tests with casperjs

Hello once more!

In this blogpost, I am going to talk about functional testing using the casperjs utility.

My previous article: Handling multiple sessions with Ace editor, resulted in an editor with support for some basic file operations (add/remove/rename file). In this article, I will demonstrate an example of how to test this page, with the help of casperjs. As before, you can reach the code at github.

In order to be able to test the editor page, it should be available in our computer's localhost by the use of a server. Script: server.sh can perform this task if python is installed in the system. It runs the command: python -m SimpleHTTPServer 8000 which serves files located on the same directory from which the command is invoked. After invocking the script or running the above command in a shell, the editor page should be available at: localhost:8000.

Because casperjs is based on nodejs, I created a package.json file in which a new node module is defined along with its dependencies. With nodejs installed, and by running the command: npm install in the directory in which package.json resides, npm (the package manager of nodejs) downloads and installs all the needed dependencies for the tests to run. The same file also contains ascripts section in which, the test command is defined to run the: test.sh script, which invokes the casperjs tests. If you take a peek at the script, it runs the command: casperjs test tests/which runs each test file located in tests/ directory.

With the server running and by executing the command: npm run test, we can see the following output:

$ npm run test

> [email protected] test /home/tsiknas/repos/ace-demo
> ./test.sh

Test file: /home/tsiknas/repos/ace-demo/tests/editor.js                         
# editor tests
Visiting: http://localhost:8000
PASS HTTP status code is: 200
Changing viewport to: 800x600
Creating file: alpha
PASS File: alpha appeared in filelist
PASS File alpha selected in filelist
PASS File: new-alpha renamed successfully
PASS Editor contains the entered text
Creating file: beta
PASS File: beta appeared in filelist
PASS File beta selected in filelist
PASS File: new-beta renamed successfully
PASS Editor contains the entered text
Creating file: gamma
PASS File: gamma appeared in filelist
PASS File gamma selected in filelist
PASS File: new-gamma renamed successfully
PASS Editor contains the entered text
Removing all files
Selecting file: new-alpha
Removing file: new-alpha
PASS File selected successfully
PASS File: new-alpha removed successfully
Selecting file: new-beta
Removing file: new-beta
PASS File selected successfully
PASS File: new-beta removed successfully
Selecting file: new-gamma
Removing file: new-gamma
PASS File selected successfully
PASS File: new-gamma removed successfully
PASS 19 tests executed in 3.489s, 19 passed, 0 failed, 0 dubious, 0 skipped.

The tests, located in file: tests/editor.js, visit the editor page and start adding, selecting, renaming, editing and finally removing some files. tests/ directory, also contains the file:settings.json, in which, some common settings for the tests are stored (URL of the page to visit and the browser's viewport size during the test).

If you are still with me, its time to examine the: tests/editor.js file more carefully.

At the begging of the script, settings.json is required, which converts the JSON file into a JavaScript object, from which we can access our settings.

All the tests are wrapped inside the: casper.test.begin() function, which, as you can see, has two arguments:

  • Name of the test
  • Callback function to run when the script is invoked

Inside the callback function, two main functions are invoked: casper.start() and casper.run().

  • casper.start() takes two arguments: a URL to visit and a callback function to run after opening the URL
  • casper.run() takes one argument, a callback function

Inside the callback function of casper.run(), we can see the actual testing code.

In casperjs tests, two main objects are often used: casper and test. The casper object contains functions helpful for navigation (click on elements, open URLs etc...) while the test object contains functions to make assertions. The test file we are examining contains 19 assertions which verify that the executed actions have the desired effect.

In order to test the file operations, three files are created, and for each created file, a rename and an edit on its contents is performed. Finally each file is selected and removed from the list. To avoid repeating the same code over and over, an array is populated with the filenames that will be created during the test and then we iterate in the array by using the casper.eachThen() function, to test the needed actions on each file.

In order to create a file and test its result, we use the following code:

// Create file
casper.then(function () {
    this.waitForSelector('#create-file-input', function () {
        this.sendKeys('#create-file-input', filename);
    });
});
casper.then(function () {
    this.waitForSelector('#create-file-button', function () {
        this.click('#create-file-button');
    });
});
casper.then(function () {
    test.assertExists('#file-list a[data-name="' + filename + '"]', 'File: ' + filename + ' appeared in filelist');
    var selectedFileCheck = this.evaluate(function (filename) {
        return $('#file-list a.file-selected').data('name') == filename;
    }, filename);
    test.assert(selectedFileCheck, 'File ' + filename + ' selected in filelist');
});

Each casper.then() block adds a navigation step to the stack, steps which are executed in sequence. By using: casper.waitForSelector() function we hold the navigation until the desired CSS selector is available into the page. It is always a good practice to wait for elements that you want to interact with, to become available, in order for the test to be tolerant in delays when page elements load dynamically. When the element becomes available we add the filename into the input field by using the: casper.sendKeys() function. After that, we use the: casper.click() function, which clicks on the create file button and the file is created.

In order to test the create action we just did, we perform two assetions, one which tests that the file appeared in the filelist, by the use of: test.assertExists() function. This function checks that the provided CSS selector is present in the DOM. The second test is performed a little differently. We first use the casper.evaluate() function. Inside the callback of this function, we can write any JavaScript code that will be executed in the context of the page we are currenty in. In this particular example, we check that the data-name attribute of the selected element, equals to the expected to be selected filename. By returning the result of this check outside of the casper.evaluate() function, we can later perform the assertion: test.assert() which expects its first argument to be evaluted into boolean true.

In the same manner, we test the rest of the actions.

As you can see, casperjs gives us a handful of tools which can be used to make navigation scenarios into pages and test their effects. It lies on top of: phantomjs or slimerjs which are the scriptable browsers that actually run the tests. phantomjs is webkit based while slimerjs is geko based. By using casperjs you can write tests that run in both engines.

Hope that you enjoyed the article.