This website uses cookies to help improve your user experience
One hot topic in today’s web test automation is regression testing. A regression is a bug introduced into a system after certain changes have been made.
These changes can include patching, new functionality development, integration of third-party components, and so on.
In agile development, regression testing is critically important to make sure your product still functions properly at the end of every iteration or sprint. Automating your regression tests means you can run them faster and more frequently, while eliminating human error during test execution.
In this article, we will explore writing regression tests with CasperJS, an easy-to-use JavaScript testing framework that leverages PhantomJS.
PhantomJS is a fast, scripted, headless WebKit-based browser — ideal for automating web page interaction. PhantomJS has excellent support for many Web standards, including DOM manipulation, CSS3 selectors, Canvas, AJAX, and more.
CasperJS is a powerful utility that runs on top of PhantomJS.
Casper provides a raft of high-level methods that greatly facilitate things like navigating web pages, clicking buttons, filling forms, and downloading resources. In addition, it includes a nice testing framework — compete with JUnit support for easy CI integration.
CasperJS has been around since 2011, so it’s a solid tool with a well-defined API and documentation that is lucid, concise, and generally a pleasure to read.
Neither PhantomJS nor Casper are Node.js modules, but they can still be conveniently installed with npm
:
npm install -g phantomjs
npm install -g casperjs
A nice way to organize your CasperJS code is to put each test suite into a separate JavaScript file. That way, each script can focus on a specific portion or functionality slice of your website, such as homepage, search, or contact form.
tester
module Casper ships with a Tester
class and a tester
module. The module provides a unit and functional testing API that is a great fit for our purposes.
An instance of the class can be accessed via the test
property of any Casper
class instance. The test
object allows us to run assertions over the current context, pass or fail tests, log errors, and more.
Your typical test suite utilizing the test
object and residing in a single *.js
file must be run with the test
subcommand like so:
casperjs test /path/to/your/test.js
By way of gentle introduction, let’s look at Oxagile’s homepage and see what kind of things we could check for possible regressions whenever we update the content or codebase.
First off, we need to make sure our homepage actually loads and returns a 200
status code. We would also do well to check the page title for important keywords that must be there.
So let’s write a minimal test suite and store it in a file called tests.homepage.js
:
casper.test.begin('Homepage Tests', 2, function suite(test) {
casper.start('http://oxagile.com', function() {
test.assertHttpStatus(200, '01 - Homepage must be up and running.');
test.assertTitleMatch(/^Custom Software Development Company/, '02 - Homepage title must contain the right keywords.');
});
casper.run(function() {
test.done();
});
});
Several things are happening here:
begin()
method starts our test suite, with 2
being the expected number of tests to run;start()
method and execute a function after the URL is loaded;run()
runs the whole test suite and executes a callback when it finishes (the done()
method simply flags the suite as complete).OK, we are ready to run our script:
casperjs test tests.homepage.js
You should get the following output in your terminal:
Now we will expand our test suite by adding more assertions. Let’s make sure that:
Adding these tests is easy with the assertExists()
, assertSelectorHasText()
, and assertElementCount()
methods:
test.assertSelectorHasText('div.phone', '+1 855 466 9244', '03 - Phone number must be correct.');
test.assertSelectorHasText('a.mail', 'contact@oxagile.com', '04 - Email must be correct.');
test.assertElementCount('div.inner-wrapper ul.prime > li', 5, '05 - Five top-level menu items must exist.');
test.assertExists('div.free-quote-btn', '06 - "Free Quote" button exists.');
test.assertElementCount('div.clients > div > img', 6, '07 - Six client logos must exist.');
test.assertSelectorHasText('div.soft', 'Custom Software Development', '08 - Custom Software Development service must exist.');
test.assertSelectorHasText('div.web', 'Web Application Development', '09 - Web Application Development service must exist.');
test.assertSelectorHasText('div.mobile', 'Mobile Application Development', '10 - Mobile Application Development service must exist.');
test.assertElementCount('div.technologies div.list img', 7, '11 - Seven tech expertise icons must exist.');
Running the above code should produce the following result:
As you can see, it took us literally a couple of seconds to ensure that our homepage displays most of the information that we care about.
Let’s go further and check if the search form works.
For filling forms, CasperJS provides a handy method aptly called fill()
. The method takes three parameters:
By way of testing, let’s search for test automation and count the results. Fill the search field and submit the form:
casper.fill('form#searchform', {
's': 'test automation'
}, true);
We now wait till the results page loads so we can run our test. We’ll also print the search results to console as an extra check:
casper.waitForUrl(/\?s=.+/, function() {
test.assertElementCount('div.search-list-item-title', 10, '12 - Ten search results must be displayed.');
console.log('\nSearch results:');
this.getElementsInfo('div.search-list-item-title').forEach(function(_item, _index) {
console.log(_index + 1 + '. "' + _item.text + '"');
});
console.log('');
});
The output should look like this:
In the above code snippet, getElementsInfo()
is a convenience method that retrieves data for all elements matching a CSS selector and returns an array that contains an object representation of elements. We use its text
key to retrieve search result titles.
Now let’s do something a bit more difficult. Each project in our portfolio has a gallery of images associated with it:
Let’s walk through all the portfolio projects and make sure that:
This script will rely on Casper’s evaluate()
method. The method allows you to evaluate an expression within the context of the current DOM.
This is an important concept to grasp when working with PhantomJS or Casper: evaluate()
acts as a bridge between the casperjs
environment and page context. Simply put, when you pass a function to evaluate()
, it will be executed as if you typed it into the browser’s console.
Using evaluate()
allows us to enter the DOM, run some JS code, and return values for further processing within the Casper environment. Which is exactly how we are going to get our gallery image sizes so we can compare them and verify the dimensions.
For this example, let’s assume you have a list of URLs stored in urls.txt
.
Obtaining URLs from a website could be easily accomplished with tools like wget
(man wget
and check out its --spider
option). We are going to load the list of URLs into an array and process them one by one.
Here’s the complete code of the portfolio test suite:
var fs = require('fs');
var aUrls = [];
stream = fs.open('urls.txt', 'r');
line = stream.readLine();
var i = 0;
while (line) {
aUrls.push(line);
line = stream.readLine();
i++;
}
casper.test.begin('Portfolio Tests', aUrls.length * 6, function suite(test) {
casper.start();
casper.then(function() {
// walk through the array:
aUrls.forEach(function(sUrl, index) {
// open URL:
casper.thenOpen(sUrl);
// wait for the URL to load:
casper.waitForUrl(sUrl, function() {
iNum = index + 1 + ' ';
this.echo("\nTesting URL " + sUrl);
test.assertElementCount('img.portfolio-main-image', 1, iNum + '[1] Page must have one main portfolio image.');
test.assertElementCount('img.portfolio-additional-image', 3, iNum + '[2] Page must have three additional portfolio images.');
var aImageData = this.evaluate(function(sUrl) {
var images = __utils__.findAll('div.gallery img');
return images.map(function(el) {
return {
"url": sUrl,
"src": el.src.replace(/^http:\/\/.+?\//g, "\/"),
"width": el.naturalWidth,
"height": el.naturalHeight
};
});
}, sUrl);
for (i = 0; i < aImageData.length; i++) {
var oImg = aImageData[i];
if (oImg.width !== 600 || oImg.height !== 480) {
test.fail(iNum + '[3] Size of ' + oImg.src + ' is incorrect: ' + oImg.width + 'x' + oImg.height + 'px');
} else {
test.pass(iNum + '[3] Size of ' + oImg.src + ' is correct.');
}
}
});
});
});
casper.run(function() {
this.echo("");
test.done();
});
});
Note the use of __utils__.findAll()
from the clientutils
module. As the name implies, it retrieves all DOM elements that match a specific CSS selector (in this case, portfolio images).
Having obtained our image list, we then use map()
to create the aImageData
array, where each item is an object containing page URL, image source, width, and height. At this point we have all the data we need to run the image dimension tests.
Run the script above and your output will look something like this (most URLs omitted for brevity):
CasperJS is a fast, versatile solution to functional web testing, including automated regression testing and identification.
The test suite for our portfolio images included 300+ tests, with the whole batch typically finishing in under 45 seconds. Assuming it takes a manual tester 30 to 45 seconds to check one URL for regressions, automation immediately provides a whopping 200-300x speedup.
CasperJS has a well-thought-out API and excellent documentation with lots of examples to build your scripts upon.
If you are familiar with (asynchronous) JavaScript, you can get up and running with Casper in next to no time.