Methods & Tools Software Development Magazine

Software Development Magazine - Project Management, Programming, Software Testing

 

CasperJS - Automated Testing of Web Applications with JavaScript

Roberta Haiura, msg systems Romania, http://www.msg-systems.ro/

CasperJS is an open source browser navigation scripting and testing tool written in JavaScript for PhantomJS and SlimerJS. With CasperJS, you can build full navigation scenarios using high-level functions and a straight forward interface to accomplish all sizes of tasks, like running full featured tests without the overhead of a full browser.

Website: http://casperjs.org/
Version discussed: CasperJS 1.1.3
License & Pricing: CasperJS is licensed under MIT License.
Support: https://groups.google.com/forum/#!forum/casperjs.

Introduction

Today, time to market is crucial and errors and bugs are not easily tolerated. It is therefore important to deliver high quality products. As quality assurance is often not in prime focus (due to various constraints, such as time, cost, resources, it’s only partially covered. A negative user experience is the consequence.

Testing manually has reliability problems, as it is tedious and slow. As an example, by the time a bug is discovered, important details to reproduce it may have been already forgotten. Also, manual test results should be well documented by the tester in order to serve as a reference to others. For these reasons, testing is a discipline where automation can really help.

Test automation tools nowadays provide an alternative to manual testing methods, as they support the execution of tests fast and frequently. 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 invested. The reaped benefits then pay off in the future by increased stability. Also, the more often a test is performed, the cheaper its automation gets.

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 a high quality software.
  • Improved documentation: automated test tools report objective outcomes, including comprehensive pass/fail results.
  • Reduced time for testing, manpower and costs: once created, automated tests can be run multiple times at no additional cost, being much faster than manual ones.

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
  • Gecko (the engine of Mozilla Firefox browser), via SlimerJS

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.

Installation

CasperJS can be installed on Mac OS X, Windows and most Linux distributions.

Step 1 – Install PhantomJS

The PhantomJS binary can be downloaded as an archive from http://phantomjs.org/download.html 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.

Step 2 - Install CasperJS

The CasperJS binary can be downloaded as an archive from http://casperjs.org/ 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). More details can be found at http://docs.casperjs.org/en/latest/installation.html. 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.

CasperJS in action

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:

1.  var casper = require('casper').create({
2.  verbose : true,  // log messages will be printed out to the console
3.  	logLevel : 'debug', // only "debug" level messages will be logged
4.  	viewportSize: { // override default browser windows size
5.  		width: 1024,
6.  		height: 768
7.  	},
8.  	pageSettings: { 
9.  //The WebPage instance used by Casper will use these settings
10. 		"loadImages" : true,
11. 		"loadPlugins" : true,
12. 		"webSecurityEnabled" : false,
13. 		"ignoreSslErrors" : true
14. 	}
15. });

The parameters and switches are mostly self-explanatory and therefore easy to handle and change also by non-programmers.

CasperJS functionality

CasperJS provides two main modules, the casper module and the tester module. The casper module focuses on the 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,
  • scraping web contents.

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.

CasperJS scripts examples

Scraping script

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.

1.  var casper = require('casper').create();
2.  var links;
3.  function getLinks() {
4.      var links = document.querySelectorAll('ul.navigation li a');
5.      return Array.prototype.map.call(links, function (e) {
6.        return e.getAttribute('href')
7.      });
8.  }
9.  casper.start('http://casperjs.org/');
10. casper.then(function () {
11.     links = this.evaluate(getLinks);
12. });
13. casper.run(function () {
14.     for(var i in links) {
15.         console.log(links[i]);
16.    }
17.    casper.done();
18. });

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 page loaded 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 loads the home page of CasperJS.

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

Navigation script

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:


1. "use strict";
2. var fs = require('fs');
3. var utils = require('utils');
4. var webserver = require('webserver');
5. var testclientURL = 'http://www.exampledomain.com/testclient.htm';
6. // Create CasperJS instance
7. var casper = require('casper').create({
8. 	verbose : true,
9. 	logLevel : 'debug',
10. 	viewportSize: {
11. 		width: 1024,
12. 		height: 768
13. 	},
14. 	pageSettings: {
15. 		"loadImages" : true,
16. 		"loadPlugins" : true,
17. 		"webSecurityEnabled" : false,
18. 		"ignoreSslErrors" : true
19. 	}
20. });
21. // Read file path
22. var filePath = casper.cli.get(0);
23. // Read request data
24. var requestHeader = fs.read('request-header.txt');
25. var requestFooter = fs.read('request-footer.txt');
26. // Define global variables
27. var request;
28. var productNumber = 0; // -1 means no product selection, 
    otherwise it's the position in the list counting from 0
29. // Register error handler
30. casper.on('complete.error', function(err) {
31.     this.die("Complete callback has failed: " + err);
32. });
33. casper.on('error', function(msg, err) {
34. 	this.die(msg + " -> " + err);
35. });
36. /*
37.  * Initializes the test procedure by login a "login" page, altering the 
38.  * request form and sending the data to the server to perform the login
39.  * @param casper The casper instance to add steps to
40.  * @param url The URL to load the login page fromCharCode
41.  * @param request The complete request to send to MARZIPAN
42.  */
43. function startTestcase(casper, url, request) {
44. 	// Create directory to log screenshots to
45. 	fs.makeDirectory('log');
46. 	// Loading first form
47. 	casper.thenOpen(url);
48. 	casper.then(function _saveScreen01() {
49. 		this.capture('log/screen01_Test-Client-Form.png');
50. 	});
51. 	// Updating form input with loaded request
52. 	casper.waitForSelector('body > form:nth-child(3)',
53. 		function success() {
54. 			console.log('[INFO] Filling out login form ...');
55. 			this.fill('body > form:nth-child(3)', {
56. 				'CALLINTERFACE' : request
57. 			}, true);
58. 		},
59. 		function fail() {
60. 			console.log('[ERROR] Login Form not found');
61. 		}
62. 	);
63. }
64. /*
65.  * Method selects a product form the list of available products.
66.  * @param casper The CasperJS instance to add steps to
67.  * @param productNumber The number of the product list to select
68.  */
69. function selectProduct(casper, productNumber) {
70. 	var productSelection = '#SUBMIT__produktListe_' + productNumber + '_formName_' 
		+ productNumber + '__common_ladeProduktFuerImport';
71. 	casper.wait(4000);
72. 	// Select product
73. 	casper.waitForSelector(productSelection,
74. 			function success() {
75. 				console.log('[INFO] Selecting product...');
76. 				this.capture('log/screen02_Produktauswahl.png');
77. 				this.click(productSelection);
78. 			},
79. 			function fail() {
80. 				this.capture('log/screen02_Produktauswahl_err.png');
81. 				console.log('[ERROR] Product not found');
82. 			}
83. 	);
84. }
85. 
86. /*
87.  * This method fills a form of data to manually enter data
88.  * @param casper The CasperJS instance to add steps to
89.  */
90. function fillForm(casper) {
91. 	// Fill the form
92. 	casper.waitForSelector('#content-area > form:nth-child(1)',
93. 		function success() {
94. 			console.log('[INFO] Filling out form...');
95. 			this.capture('log/screen03_Eingabemaske.png');
96. 			this.sendKeys('input[name="zinsbindungslaufzeit"]', '10');
97. 			this.sendKeys('input[name="auszahlungsdatum"]', '17.08.2016');
98. 			this.sendKeys('input[name="rate"]', '900');
99. 			this.sendKeys('input[name="effektivzins"]', '2');
100. 			this.capture('log/screen04_EingabemaskeAusgefuellt.png');
101. 		},
102. 		function fail() {
103. 			this.capture('log/screen03_Eingabemaske_err.png');
104. 			console.log('[ERROR] Form not found');
105. 		}
106. 	);
107. }
108. /*
109.  * Presses the "berechen Nominalzins" Button
110.  * @param casper The CasperJS instance to add test steps to
111.  */
112. function pressBerechneNominalzinsAnnuitaetendarlehen(casper) {
113. 	// Compute nominal interest rate
114. 	casper.thenClick('input[name="SUBMIT_/aktiv/berechneNominalzinsAnnuitaetendarlehen"]');
115. 	casper.waitForSelector('#SUBMIT__aktiv_berechneNominalzinsAnnuitaetendarlehen', 
116. 		function success() {
117. 			this.wait(7000, function _saveScreen05() {
118. 				this.capture('log/screen05_NominalzinsBerechnet.png');
119. 			});
120. 		},
121. 		function fail() {
122. 				this.capture('log/screen05_NominalzinsBerechnet_err.png');
123. 			console.log('[ERROR] Failed to calculate nominal interest rate');
124. 		}
125. 	);
126. }
127. /*
128.  * Presses the "Ruecksenden" Button
129.  * @param casper The CasperJS instance to add test steps to
130.  */
131. function pressSubmitRuecksendenGeschaeft(casper) {
132. 	// Select Rücksprung
133. 	casper.thenClick('input[name="SUBMIT_/aktiv/ruecksendenGeschaeft"]', function _saveScreen06() {
134. 		this.capture('log/screen06_Ruecksprung.png');
135. 		this.wait(7000, function() {
136.         	this.capture('log/screen07_Ergebnis-HTML.png');
137.     	});
138. 	});
139. }
140. // Start CasperJS engine
141. casper.start();
142. try {
143. 	console.log('[INFO] Considering >> ' + filePath);
144. 	// Check whether it's a regular file
145. 	if(fs.isFile(filePath)) {
146. 		console.log('[INFO] Processing ' +  filePath);
147. 		console.log('[INFO] Loading request file >> ' + filePath);
148. 		request = fs.read(filePath);
149. 		// The complete request to send is header + content + footer
150. 		var completeRequest = requestHeader + request + requestFooter;
151. 		startTestcase(casper, testclientURL, completeRequest);
152. 		if(productNumber >= 0 ) {
153. 			selectProduct(casper, productNumber);
154. 		} else {
155. 			console.log('[INFO] Skipping product selection');
156. 		}
157. 		fillForm(casper);
158. 		pressBerechneNominalzinsAnnuitaetendarlehen(casper);
159. 		pressSubmitRuecksendenGeschaeft(casper);
160. 		console.info('[INFO] Testcase finished');
161. 	} else {
162. 		console.log('[INFO] Ignoring ' + filePath + ' as it is no regular file');
163. 	}
164. } catch(err) {
165. 	console.log('[ERROR] ' + err);
166. }
167. // Execute the chain of steps and exit on success
168. casper.run(function () {
169. 	this.exit();
170. });

The first step required to compute the loan’s conditions is the login. This is done by altering the request form and send data to the server to perform the login (see line 151). 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 148). 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 153). Then a form is filled with some constraints as the payout date, annuity installment, effective interest rate (see lines 97, 98, 99). In the next step the nominal interest rate is calculated (see line 158). In the final step the “Rücksprung” button is clicked (see line 159), and the navigation scenario is finished.

To track the fulfillment of the navigation steps the screen capture mechanism is used, for example in lines 49, 76, 80.

Assuming the script is saved in a file named navigationScript.js, the following command starts its execution:

casperjs navigationScript.js C:/requestContent.txt

Testing script

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
  • run tests: the command which runs the tests

Below you can see a simple test script:

1.  casper.test.begin('Testing Google', 1, function(test){
2.    casper.start('http://google.com');
3.    casper.then(function(){
4. 	test.assertTitle('Google', 'Google has correct title'); 
5.    });
6.    casper.run(function(){
7.  	  test.done();
8.    })
9. });

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, using 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.

Conclusion

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 as 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.


Click here to view the complete list of tools reviews

SpiraTeam Agile ALM


Agile Alliance Technical Conference

SQE Live Virtual Training