HTML5 Zone is brought to you in partnership with:

Leigh has been in the technology industry for over 15 years, writing marketing and technical documentation for Sun Microsystems, Wells Fargo, and more. She currently works at New Relic as a Marketing Manager. Leigh is a DZone MVB and is not an employee of DZone and has posted 106 posts at DZone. You can read more from them at their website. View Full User Profile

Simpler UI Testing with CasperJS

06.05.2013
| 7493 views |
  • submit to reddit
This post comes from Matthew Setter, a professional technical writer and passionate web application developer. He’s also the founder of Malt Blue, the community for PHP web application development professionals and PHP Cloud Development Casts – learn Cloud Development through the lens of PHP. You can connect with him on TwitterFacebookLinkedIn or Google+ anytime.

Summary
Whether you develop small applications or large ones, it’s safe to say that user testing is an important aspect of the development process.

But how do you test reliably and continuously, especially when you don’t have people available to test your application every time it changes? Naturally, software fits this bill.

Over the years, developers have created a plethora of applications and technologies to fill this need. Selenium, which works very well, is a prime example. But we’re always looking for newer, simpler (and sometimes ‘cooler’) ways to do what we do. So recently I’ve started to learn a promising new technology: PhantomJS.

What is PhantomJS?
PhantomJS is a headless WebKit with a JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selectors, JSON, Canvas and SVG.

PhantomJS was created by Ariya Hidayat. It’s a pretty impressive technology on its own, but while reading through the site and all the accompanying APIs, I came across CasperJS.

What is CasperJS?
Casper is a navigation scripting and testing utility for PhantomJS, written in JavaScript. It allows us to test websites a lot like PHPUnit or JUnit allow us to test our code.

It really looks like a great tool – one that’s very simple to run through JavaScript for a wide variety of my testing needs and one that can be very readily integrated with my current development workflow. It looks like a pretty good combination of backend and frontend.

In this article, I’ll go through the basics of using CasperJS. I’ll impersonate a typical user interrogating a page of the New Relic site, specifically the ‘Real User Monitoring‘ section.

I chose this page because it has loads of aspects that are common to a standard site or application such as images, forms, buttons, links and text. It’s content-rich and has a lot for us to work with and test. Now, I’m sure that the good folks at New Relic already do this, but I’m also pretty sure they won’t mind if I set up a simple set of tests and assertions with the site.

Features
CasperJS has a range of features. In this article, I’ll focus on the Tester API. It has a range of functions and assertions that you’d expect from a good testing API, including:

* assertTextExists
* assertTitle
* assertHttpStatus
* assertDoesntExist
* assertUrlMatch

I’ll use these assertions to show how CasperJS works. It also contains a set of other features, but I’m not going to focus on them in depth – only enough to make a working example. However, I encourage you to check out the excellent online API documentation. It’s a pleasure to read because it’s so clear and concise.

Requirements
If you haven’t already, install PhantomJS or Casper before we continue on. Now, let’s dive right in.

Getting Started
When I first loaded the ‘Real User Monitoring’ page on the New Relic site, it looked like the image below (it’s changed since the time of this writing). I’ve highlighted the starting point of the signup form.

New-Relic-partial-form_

If you click any element of this form, then the rest of it gets revealed so that it looks like the image below.

nerd-in-tshirt-form

The tests will focus on the form, but we’ll also look at the title tag, the image on the left side of the form and the text above it.

The Code
First, to save time, I initialized two variables: one to store the URL and one for the site name, because I’ll use them in the test’s ‘success’ messages.

var url = 'http://newrelic.com/product/real-user-monitoring';
var siteName = 'NewRelic';

Casper comes with four built-in logging levels:

* debug
* info
* warning
* error

By default, CasperJS filters out any logging under the level of ‘error’. So if you start logging and don’t see anything at first, this might be why. To ensure that the logging output displays, I’ve set it to ‘debug.’ I also disabled the ‘verbose’ option. If it’s enabled, we’ll see information on just about everything, which can be rather distracting.

var casper = require('casper').create({
    verbose: false,
    logLevel: 'debug'
});

You can find other configuration options on the Casper API page. The comment function, as shown below, enables us to log the output – in this case to the console. I’ve used a simple message here just to say that testing has begun.

casper.test.comment('Starting Testing');

The function ‘casper.start’ begins to run the tests.

casper.start(url, function() {
    this.test.assert(
        this.getCurrentUrl() === url, 'url is the one expected'
    );
 
    this.test.assertHttpStatus(200, siteName + ' is up');

Here’s what I did: I loaded the URL, which was specified in ‘url’, and asserted that we could load the desired address. After successfully loading the page, it’s time to start calling some assertions to make sure the content that loads on the page is what we expect.

In this case, I made a quick check of the returned status code, which should be 200, and I also got 200. Let’s do another assertion:

this.test.assertTitle(
    'Real User Monitoring, End User Experience Monitoring : New Relic',
    siteName + ' has the correct title'
);

The above test is pretty simple. It asserts that the title of the loaded page is what it should be and then outputs a message with that information.

In the code below, I check first to ensure that a form with the ID of ‘nr-signup-form’ exists. It’s a bit pointless checking for form elements if the form doesn’t exist, right?

this.test.assertExists(
    'form[id="nr-signup-form"]',
    siteName + ' has a form with name "nr-signup-form"'
);

Now before I go any further, I should mention that when I assert whether elements do or don’t exist on a loaded page, I can use two different approaches:

* XPath
* CSS Selectors

If you’d like more information on XPath, check out some of the links in the ‘Further Reading’ section, or read this post that I wrote on it. CSS selectors should be pretty self-explanatory. But just in case, I’ve included links in the ‘Further Reading’ section for that as well.

Now let’s use assertions to search for the input fields.

this.test.assertExists(
    {type: 'xpath', path: '//input[@id="FullName"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//input[@id="Company"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//input[@id="Email"]' },
                'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//input[@id="Password"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//input[@id="PromoCode"]' },
    'the element exists'
);

In the five assertions above, I searched for five input fields with the IDs ‘FullName’, ‘Company’, ‘Email’, ‘Password’ and ‘PromoCode’. It’s just as easy with XPath to find select elements.

In the tests below, I’ve asserted that there are four select lists with matching IDs: ‘country’, ‘group’, ‘company_size’ and ‘HostEstimate’.

this.test.assertExists(
    {type: 'xpath', path: '//select[@id="country"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//select[@id="group"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//select[@id="company_size"]' },
    'the element exists'
);
 
this.test.assertExists(
    {type: 'xpath', path: '//select[@id="HostEstimate"]' },
    'the element exists'
);

Now, these tests were pretty simple. So, let’s make them a bit harder.

this.test.assertExists(
    {
        type: 'xpath',
        path: '//button[@id="sign-up-button"
                and @class="small-button"
                and @type="submit"]'
    }, 'the element exists'
);

In the above test, I searched for the ‘Create Free Account’ button, as shown in the image below.

create-free-account-highlighted

I’ve used some conditional operators in this query. We’re looking for a button with:

* An ID of ‘sign-up-button’
* A class of ‘small-button’
* A type of ‘submit’

It may seem like I’m more focused on XPath than CasperJS, but bear with me just a bit longer for some more functions and configuration.

var x = require('casper').selectXPath;
var checkboxXpath = "//input[@type='checkbox' and
@id='checkbox1']/parent::*/a/label[
    contains(., 'Java') or
    contains(., 'Python') or
    contains(., 'Ruby') or
    contains(., 'PHP') or
    contains(., '.NET') or
    contains(., 'Node.js')
]";
this.test.assertExists(x(checkboxXpath), 'the element exists');

What I did in the test above was to simplify the XPath test a little by initializing ‘x’ with the ‘selectXPath’ helper. I looked for all the checkboxes on that page with the ‘Application(s)’ choice. So the XPath query looked for checkboxes with an accompanying label tag that matches the supplied application language names.

Just to show that I don’t use XPath exclusively (though it’s incredibly powerful and simple), the two tests below also use CSS selectors to check that an input field and a list item also exist.

this.test.assertVisible('input#Company');
 
this.test.assertExists('li.nav-enterprise', 'the element exists');

It’s easy to search for form elements and list items, but what if your page is resource heavy, with loads of images or Flash objects? Here’s an example:

this.test.assertResourceExists(
    '/images/tshirt-dude-form.png', 'T-shirt Image exists'
);

With the test above, I’ve asserted that the image of the man in the advertised t-shirt is loaded. One final test asserts that some text exists, which in this case is the ‘Sign up for free!’ text above the shot of the man in the t-shirt:

    this.test.assertTextExists(
        'Sign up for free!', 'page body contains "Sign up for free!"'
    );
 
});

Then we close the ‘start’ function.

casper.test.comment('Ending Testing');

The tests are almost done, so I’ll output another log entry to let the user know this.

casper.run(function() {
    this.test.done(16);
    this.echo('So the whole suite ended.');
    require('utils').dump(casper.test.getFailures());
    require('utils').dump(casper.test.getPasses());
    this.exit();
});

I could write all the tests that I want, but without calling ‘run’, nothing will happen. So ‘run’ kicks off the aforementioned work. What I’ve done is check that the script has run 16 tests and then output a final line of text at the end with ‘this.echo()’.

When we run tests, it’s sometimes good to get a summary of the results at the end. With the ‘getFailures()’ and ‘getPasses()’ functions, we can see a summary of the tests that passed and failed. Following all this, I called ‘this.exit()’ to wind up. This exits CasperJS and PhantomJS, optionally specifying an exit code if desired.

Taking this one step further, this time I call ‘casperjs’ slightly differently: I pass ‘test’, as seen below, along with the ‘xunit’ switch. This exports the test results to an XUnit XML file.

$ casperjs test mytest.js --xunit=log.xml

In the next major version of CasperJS, you won’t be able to run and render tests if ‘test’ doesn’t get passed to the command. It’s important to know this ahead of time and avoid your scripts breaking unexpectedly.

Automating Testing
What if you want to automate your way through the test setup? I mean, it’s great that we can hand-sculpt some tests, but it can get tedious and rather time-intensive. Happily, there’s an extension that helps to automate your tests. I wasn’t aware of it when I started writing this series. I want to give a big thank you to @casperjs_org for making me aware of the resurectio extension for Google Chrome.

refugio-tweet-link

Clone or download a copy and install it, then reopen the same New Relic page we used earlier and click the ‘resurectio’ button in the extension button list.

refugio-screencap

On the page where you want to compose an automated test, click ‘Go’ by the page URL field. Anything you do from now on will be included as part of the generated test script.

I clicked through each of the form elements, from ‘Email’ to ‘Company Size.’  Then I clicked the extension button again to stop recording and clicked ‘Export CasperJS.’ This opens another page in the browser that displays the generated script.

Copy that into a script and run it as you would the previous script that we created, and you’ll get the output as seen in the screenshot below.

bash-php-script_

As you can see, it’s done a nice job of setting up the environment and creating all the test assertions, so all we need to do is run it. This makes it easy to get tests ready to use with CasperJS.

Further Reading

XPath
CSS Selectors
CasperJS
PhantomJS
JUnit
PHPUNit

Conclusion
I hope you’ve enjoyed this gentle introduction to CasperJS and appreciate the power and flexibility that it gives us to design thorough user-accepted test suites through PhantomJS.

We saw how — with a minimal amount of effort — we could test the content of the New Relic ‘Real User Monitoring’ page, albeit with some manual effort to determine the required XPath expressions and CSS selectors.

But for a free solution, it sure delivers a lot of capacity. I’m sure you’ll agree. So what did you think of it? Can you see yourself or your organization incorporating it as part of your deployment and/or continuous integration solution?

In the next part, I’ll branch out from testing and looking at other parts of the CasperJS, including:

* HTTP authentication
* Mouse events
* Submitting forms
* Impersonating user agents
* Making AJAX requests

Published at DZone with permission of Leigh Shevchik, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)