Nowadays, test automation tools provide an alternative to manual testing methods, as they support the fast and frequent execution of tests. In addition, test results can be automatically compared to the results of an expected behavior and differences can be reported. To get started with test automation, an effort needs to be invested. The reaped benefits then pay off in the future by increased stability. CasperJS is a useful tool for automating and testing web applications, being very easy and fast to install.
Today, time to market is crucial and errors and bugs are not easily tolerated. Therefore, it is important to deliver high-quality products. As quality assurance is often not the prime focus, because of various constraints such as time, cost, resources, QA is only partially covered. The result is reflected in a negative user experience.
Manual testing has reliability issuess, as it is tedious and slow. As an example, by the time a bug is discovered, important details to reproduce it may have already been forgotten. Moreover, manual test results should be well documented by the tester in order to serve as reference to others. For these reasons, testing is a matter of discipline where automation can really help.
In comparison to manual testing, test automation fosters the following improvements to a software development process:
Improved software quality: consistent and repeatable automated tests make it easier to deliver high-quality software.
Improved documentation: automated test tools report objective outcomes, including comprehensive pass/fail results.
CasperJS has its focus with web application and is well suited for automating the interaction with a web application. It is an open source navigation scripting and testing utility written in JavaScript, designed to use various render engines. At the time of writing, the following two are being supported:
WebKit (the engine of Apple Safari browser), via PhantomJS
CasperJS is not limited to those two, additional render engines can be supported by writing an appropriate plug-in. Running a test with PhantomJS is the default option, but this can be changed with just a command-line option. You can run CasperJS scripts from the command line and, thus, they can be easily integrated in continuous integration processes.
This article presents how to install CasperJS, its main features and some basic examples. The focus of the article is not to present all features of CasperJS, but to provide an overview of CasperJS features and when to use them.
CasperJS can be installed on Mac OS X, Windows and most Linux distributions.
The PhantomJS binary can be downloaded as an archive and extracted to any location. If using Windows, the installation location (appended with "/bin" directory) should be added to the PATH environment variable.
To test that PhantomJS is ready to use, open a command-line and enter:
phantomjs -v
This should output the version number. In case of a "file not found" or "unknown command" message, check the PATH environment variable.
The CasperJS binary can be downloaded as archive and extracted to any folder. Depending on the operating system, the installation can be done using Homebrew, a popular package manager for Mac OSX, or from npm (NodeJS package manager), or using git (from source). If you use Microsoft Windows, you have to append the local path to the bin directory to the PATH environment variable.
To test that CasperJS is ready to use, open a command line and enter:
casperjs --version
This should output the version number. In case of a "file not found" or "unknown command" message, check the PATH environment variable.
Scripting and invoking CasperJS
CasperJS test cases are created by using JavaScript. The easiest way to access a casper instance is to use the create() method provided by the casper module:
var casper = require('casper').create();
An alternative method to obtain a casper instance is by retrieving the main function and instantiate it directly:
var casper = new require('casper').Casper();
Both the create() function and the Casper constructor take a single options argument which is a standard JavaScript object. An example with comments follows:
var casper = require('casper').create({
// log messages will be printed out to the console
verbose : true,
// only "debug" level messages will be logged
logLevel : 'debug',
viewportSize: {
// override default browser windows size
width: 1024,
height: 768
},
pageSettings: {
//The WebPage instance used by Casper will use these //settings
"loadImages" : true,
"loadPlugins" : true,
"webSecurityEnabled" : false,
"ignoreSslErrors" : true
}
});
The parameters and switches are self-explanatory and, therefore, easy to handle and change by non-programmers.
CasperJS provides two main modules, the casper module and the tester module. The casper module focuses on simplifying the browser interaction. The tester module focuses on high-level for doing various common tasks such as:
Defining and ordering browsing navigation steps
Filling and submitting forms
Clicking and following links
Capturing screenshots of a page or only parts of it
Testing remote DOM
Logging events
Downloading resources, including binary ones
Writing functional test suites, saving results as JUnit XML
A CasperJS script is organized as a series of steps. If the then()
method is called, the passed function is put into a queue. When all navigation steps are defined and the run()
method is called, all queued functions are executed sequentially. CasperJS uses flags to detect whether the following step has to wait for the predecessor to complete or not.
Using the API provided by CasperJS, the script developer can define navigation scenarios and, therefore, interact with a web application just like a regular user. Tests and navigation scenarios can be scripted and executed repeatedly, simulating "real world" use cases.
The scraping of a web page, which is loading all resources of a URL, provides a rather simple example. The small script below could be saved in a file named firstexample.js. In this example, the links from the CasperJS official web page, http://casperjs.org/, are scraped and printed to the console.
var casper = require('casper').create();
var links;
function getLinks() {
var links = document.querySelectorAll(
'ul.navigation li a');
return Array.prototype.map.call(links,
function (e) {
return e.getAttribute('href')
});
}
casper.start('http://casperjs.org/');
casper.then(function () {
links = this.evaluate(getLinks);
});
casper.run(function () {
for(var i in links) {
console.log(links[i]);
}
casper.done();
});
The script performs the following actions:
Line no. | Description |
---|---|
1 | This line creates a new casper instance. |
The casper module is loaded and the | |
create() method will return an instance | |
of the Casper class. | |
-------------- | ------------------------------------------ |
3-8 | The instructions scrape the links from |
the page. In line 4 the DOM of the | |
loaded page is queried, and all | |
references and list elements are | |
retrieved. In line 5 an array is created | |
by invoking a function on each element | |
of the retrieved references. In line 6, | |
the anonymous function (invoked on a | |
link item) returns the content of the | |
href attribute. | |
-------------- | ------------------------------------------ |
9 | The start() method will start Casper and |
will load the CasperJS home page. | |
-------------- | ------------------------------------------ |
10-12 | The then() method is the standard way to |
add a new navigation step to the stack. | |
It has only one parameter, a simple | |
function. In this case, the function | |
will evaluate the links. The evaluate() | |
method will execute the getLinks() | |
function. | |
-------------- | ------------------------------------------ |
13-18 | In line 13, the run() method is called, |
which executes the whole stack of | |
commands. It has one parameter, a | |
callback function which is executed when | |
the whole stack of previous steps is | |
done. In this case, the callback | |
function outputs the result links, line | |
14-15. | |
-------------- | ------------------------------------------ |
To run this script, you should type the following command:
casperjs firstexample.js
The following script is more complex, representing a navigation scenario for a user who wants to calculate a loan condition using a specialized web application for this:
"use strict";
var fs = require('fs');
var utils = require('utils');
var webserver = require('webserver');
var testclientURL = 'http://www.exampledomain.com/testclient.htm';
// Create CasperJS instance
var casper = require('casper').create({
verbose : true,
logLevel : 'debug',
viewportSize: {
width: 1024,
height: 768
},
pageSettings: {
"loadImages" : true,
"loadPlugins" : true,
"webSecurityEnabled" : false,
"ignoreSslErrors" : true
}
});
// Read file path
var filePath = casper.cli.get(0);
// Read request data
var requestHeader = fs.read('request-header.txt');
var requestFooter = fs.read('request-footer.txt');
// Define global variables
var request;
var productNumber = 0; // -1 means no product selection, otherwise it's the position in the list counting from 0
// Register error handler
casper.on('complete.error', function(err) {
this.die("Complete callback has failed: " + err);
});
casper.on('error', function(msg, err) {
this.die(msg + " -> " + err);
});
/*
* Initializes the test procedure by login a "login" page, altering the request form
* and sending the data to the server to perform the login
* @param casper The casper instance to add steps to
* @param url The URL to load the login page fromCharCode
* @param request The complete request to send to MARZIPAN
*/
function startTestcase(casper, url, request) {
// Create directory to log screenshots to
fs.makeDirectory('log');
// Loading first form
casper.thenOpen(url);
casper.then(function _saveScreen01() {
this.capture('log/screen01_Test-Client-Form.png');
});
// Updating form input with loaded request
casper.waitForSelector('body > form:nth-child(3)',
function success() {
console.log('[INFO] Filling out login form ...');
this.fill('body > form:nth-child(3)', {
'CALLINTERFACE' : request
}, true);
},
function fail() {
console.log('[ERROR] Login Form not found');
}
);
}
/*
* Method selects a product form the list of available products.
* @param casper The CasperJS instance to add steps to
* @param productNumber The number of the product list to select
*/
function selectProduct(casper, productNumber) {
var productSelection = '#SUBMIT__produktListe_' + productNumber +
'_formName_' + productNumber + '__common_ladeProduktFuerImport';
casper.wait(4000);
// Select product
casper.waitForSelector(productSelection,
function success() {
console.log('[INFO] Selecting product...');
this.capture('log/screen02_Produktauswahl.png');
this.click(productSelection);
},
function fail() {
this.capture('log/screen02_Produktauswahl_err.png');
console.log('[ERROR] Product not found');
}
);
}
/*
* This method fills a form of data to manually enter data
* @param casper The CasperJS instance to add steps to
*/
function fillForm(casper) {
// Fill the form
casper.waitForSelector('#content-area > form:nth-child(1)',
function success() {
console.log('[INFO] Filling out form...');
this.capture('log/screen03_Eingabemaske.png');
this.sendKeys('input[name="zinsbindungslaufzeit"]', '10');
this.sendKeys('input[name="auszahlungsdatum"]', '17.08.2016');
this.sendKeys('input[name="rate"]', '900');
this.sendKeys('input[name="effektivzins"]', '2');
this.capture('log/screen04_EingabemaskeAusgefuellt.png');
},
function fail() {
this.capture('log/screen03_Eingabemaske_err.png');
console.log('[ERROR] Form not found');
}
);
}
/*
* Presses the "berechen Nominalzins" Button
* @param casper The CasperJS instance to add test steps to
*/
function pressBerechneNominalzinsAnnuitaetendarlehen(casper) {
// Compute nominal interest rate
casper.thenClick('input[
name="SUBMIT_/aktiv/berechneNominalzinsAnnuitaetendarlehen"]');
casper.waitForSelector(
'#SUBMIT__aktiv_berechneNominalzinsAnnuitaetendarlehen',
function success() {
this.wait(7000, function _saveScreen05() {
this.capture('log/screen05_NominalzinsBerechnet.png');
});
},
function fail() {
this.capture('log/screen05_NominalzinsBerechnet_err.png');
console.log('[ERROR] Failed to calculate nominal interest rate');
}
);
}
/*
* Presses the "Ruecksenden" Button
* @param casper The CasperJS instance to add test steps to
*/
function pressSubmitRuecksendenGeschaeft(casper) {
// Select Rücksprung
casper.thenClick('input[
name="SUBMIT_/aktiv/ruecksendenGeschaeft"]', function _saveScreen06()
{
this.capture('log/screen06_Ruecksprung.png');
this.wait(7000, function() {
this.capture('log/screen07_Ergebnis-HTML.png');
});
});
}
// Start CasperJS engine
casper.start();
try {
console.log('[INFO] Considering >> ' + filePath);
// Check whether it's a regular file
if(fs.isFile(filePath)) {
console.log('[INFO] Processing ' + filePath);
console.log('[INFO] Loading request file >> ' + filePath);
request = fs.read(filePath);
// The complete request to send is header + content + footer
var completeRequest = requestHeader + request + requestFooter;
startTestcase(casper, testclientURL, completeRequest);
if(productNumber >= 0 ) {
selectProduct(casper, productNumber);
} else {
console.log('[INFO] Skipping product selection');
}
fillForm(casper);
pressBerechneNominalzinsAnnuitaetendarlehen(casper);
pressSubmitRuecksendenGeschaeft(casper);
console.info('[INFO] Testcase finished');
} else {
console.log('[INFO] Ignoring '+ filePath+' as it is no regular file');
}
} catch(err) {
console.log('[ERROR] ' + err);
}
// Execute the chain of steps and exit on success
casper.run(function () {
this.exit();
});
The first step required to compute the loan's conditions is the login. This is done by altering the request form and by sending data to the server to perform the login (sees line 170). The complete request to be sent to the server contains three parts: the request header, the content and the request footer. All these parts are read from files (see lines 24, 25 and 165). The request content file path is given as a command line parameter and is accessed in line 22. After a successful login, a product is selected (see line 172). Then, a form is filled with some constraints as the payout date, annuity installment, effective interest rate (see lines 105, 107, 108). In the next step, the nominal interest rate is calculated (see line 177). In the final step, the "Rücksprung" button is clicked (see line 178), and the navigation scenario is finished.
To track the fulfillment of the navigation steps, the screen capture mechanism is used, for example in lines 52, 82, 87.
Assuming the script is saved in a file named navigationScript.js, the following command starts its execution:
casperjs navigationScript.js C:/requestContent.txt
CasperJS provides a helpful range of functions and assertions. Usually, the base components of a CasperJS test file are:
Test suite declaration: contains a description, number of tests and the suite code itself
Functional steps: the code inside the suite - actions on the website and assertions
Below you can see a simple test script:
casper.test.begin('Testing Google', 1, function(test){
casper.start('http://google.com');
casper.then(function(){
test.assertTitle('Google',
'Google has correct title');
});
casper.run(function(){
test.done();
})
});
Test scripts are a little bit different than the scraping ones, although they share most of the API. One difference is that test scripts don't require creating a casper instance. They require to be run with the 'test' keyword in the command line. The casperjs test subcommand configures a test environment for you, so a preconfigured casper object will be available in your test script.
Assuming that the above script was saved in a file named firsttest.js, you have to type the following command to run it:
casperjs test firsttest.js
In line 1, the access to an instance of the Tester class, provided by CasperJS, is made by default through the test property of any Casper class instance. Then, the begin method is called, passing in three arguments: description, the number of tests expected to run and the callback function. In this case, a suite with 1 test will be started. The callback function, the third parameter, will get the current Tester instance as its first argument.
In line 2, by using the start() method, Google web page is loaded.
In line 4, one of the asserts, assertTitle, is used to check that the title equals the expected one. First parameter is the expected value and the second one is optional and is the message to be shown after the assert is performed.
The done()
method must be called in order to terminate the suite. This call was placed in the callback function of the run()
method, see line 7. That means that it will be executed after all the previous steps are done.
CasperJS is a useful tool for automating and testing web applications, being very easy and fast to install. It provides high-level methods that facilitate things like clicking buttons, filling forms, capturing screenshots of a page or only parts of it, downloading resources, scraping Web contents. It has a well-defined API and a documentation that is very helpful, being straightforward and concise.
If you want to automate the interaction with a web page or test it, CasperJS is definitely a good choice.
by Ovidiu Mățan
by Ovidiu Mățan
by Mihai Varga