Book Image

Instant Testing with CasperJS

By : Eric Brehault
Book Image

Instant Testing with CasperJS

By: Eric Brehault

Overview of this book

Professional web development implies systematic testing. While JavaScript unit tests will validate your JavaScript library’s quality, web functional testing is the only way to guarantee the expected behavior of your web pages. CasperJS is a fast and simple JavaScript testing API that can run on any platform, and it is currently one of the best and easiest ways to write your functional tests. Instant Testing with CasperJS will teach you how to write efficient and accurate tests for your professional web developments. This practical guide explains the various different CasperJS principles through clear and detailed examples, covering a large set of common use cases. This book will progressively cover everything you need to know from CasperJS basic principles to the most advanced testing practices. This book starts off by introducing you to the different testing assertions that you can perform with the CasperJS API. We will then move on to cover why bad timing between event triggering can ruin tests and learn strategies to avoid it. Finally, you will learn how to test efficient and complex web interactions like drag and drop, authentication, and file uploading. With Instant Testing with CasperJS, you will be able to set up an advanced and functional test suite for your web development projects quickly and efficiently.
Table of Contents (7 chapters)

Writing advanced tests (Intermediate)


This recipe will detail how to simulate rich web interactions using CasperJS, in order to achieve more complex testing.

How to do it...

The following sections cover the various steps in writing advanced tests.

Downloading files

First, let's learn how to download files. The most common way to download a file from a web page is by providing a link to this file as follows (example4.html):

<html><body>
   <h1>My example</h1>
   <a id="link-to-text" href="files/text.txt">Download a text</a>
   <a id="link-to-pdf" href="files/text.pdf">Download a PDF</a>
</body></html>

Now, let's create the following CasperJS script (example4.js):

var casper = require('casper').create();

casper.start('http://localhost:8000/example4.html', function() {
    this.click("#link-to-text");
});
casper.then(function() {
   this.echo(this.getCurrentUrl());
});

casper.thenOpen('http://localhost:8000/example4.html', function() {
   this.click("#link-to-pdf");
});

casper.then(function() {
   this.echo(this.getCurrentUrl());
});

casper.run();

It should open our page, click on the first link, log the current page URL, reopen the page, click on the second link, and log the current page URL.

To complete our test, let's create a folder named files, add two dummy files in this folder (text.txt and text.pdf), and start our SimpleHTTPServer web server.

Let's run the script:

When we clicked on the first link, we actually opened the .txt file as a page, but when we clicked on the second one, we opened the file that was kept in the original location. If we check the current folder content, we will see that nothing has been downloaded.

So the click() method will not help us in downloading any file; it will navigate to the corresponding link if PhantomJS is able to open it, or it will open the file that was kept in the original location, producing no error and no output.

The right way to download a file is by using the download() method. Let's fix our test using the following code:

var casper = require('casper').create();
casper.start('http://localhost:8000/example4.html', function() {
    this.download("http://localhost:8000/files/text.txt", "text.txt");
    this.download("http://localhost:8000/files/text.pdf", "text.pdf");
});

casper.run();

The outcome looks as follows:

Now that our files have been downloaded, let's discover how to upload files.

Uploading files

To perform a file upload during a test, we will use the fill() method. The fill() method allows us to fill in a form and optionally, to submit it. Plus, it is able to manage file inputs.

The following is an example (example5.js):

var x = require('casper').selectXPath;
casper.test.begin('Upload a file', 1, function(test) {
    casper.start('http://imagebin.org/', function() {
        this.click(x("//a[normalize-space(text())='Add your image now!']"));
    });

    casper.then(function() {
        this.fill("form[name=image_form]", {
            'nickname' : 'casper',
            'image': './test.png',
            'title': 'my test',
            'description': 'just a test',
            'disclaimer_agree': 'Y'
        }, true);
    });

    casper.then(function() {
        test.assertExists('img[alt="my test"]', "The image has been uploaded");
        this.echo("It is available here: " + this.getCurrentUrl());
    });

    casper.run(function() {
        test.done();
    });
});

You can perform the following steps with this test:

  1. Go to http://imagebin.org.

  2. Click on the Add your image now! link.

  3. Then, fill in the image submission form.

  4. Assert that we obtain a page containing our image.

  5. Display this page URL.

As we can see, the 'image' field is managed the same way as the other fields; its value is just the path to our local image.

After passing the first parameter containing the fields values, we pass true as a second parameter to fill(), so that the form is submitted.

Before running the test, we make sure that we put an image named test.png in our current folder and when we run the test, the following is what we get:

Authentication

Let's see how we can manage authentication. When we try to open a page that requires authentication, we get a 401 HTTP error (example6.js):

casper.test.begin('Log in', 1, function(test) {
    casper.start('http://www.plomino.net/zmiroot/testpage', function() {
        test.assertHttpStatus(401, 'We cannot see the page as we are not logged in.');
    });

    casper.run(function() {
        test.done();
    });
});

The outcome is as follows:

Now, let's use the setHttpAuth() method to log in properly:

casper.test.begin('Log in', 1, function(test) {
    casper.start();

    casper.setHttpAuth('demoaccount', 'demoaccount');

    casper.thenOpen('http://www.plomino.net/zmiroot/testpage', function() {
        test.assertTextExists('You are logged in.', 'Now we can see the page.');
    });
    casper.run(function() {
        test.done();
    });
});

The following is what we get:

The setHttpAuth() method can only be used for HTTP authentication. When our targeted page uses a web form authentication, we just need to fill the authentication form. The following is an example (example7.js):

casper.test.begin('Log in', 1, function(test) {
    casper.start('http://www.plomino.net/samples', function() {
        this.fill('#loginform',
            {
                '__ac_name': 'demouser',
                '__ac_password': 'demouser'
            }, true);
    });

    casper.then(function() {
        test.assertTextExists('Welcome! You are now logged in', 'We are logged in.');
    });

    casper.run(function() {
        test.done();
    });
});

The output looks as follows:

Keyboard and mouse events

Simulating keyboard and mouse events is another very common use case. Let's consider the following page (example8.html):

<html><body>
   <script>
   function updateMessage(element) {
      var message = 'You have entered ' + element.value.length + ' characters.';
      document.querySelector('#message').textContent = message;
   }
   </script>
   <h1>My example</h1>
   <form id="my-form">
      <p>Firstname: <input
         type="text"
         name="firstname"
         onkeyup="updateMessage(this);"/>
      </p>
      <div id="message"></div>
   </form>
</body></html>

If we launch our simple HTTP server, we can try opening the page at http://localhost:8000/example8.html.

When we enter a value in the input text, a message is displayed under the input text, indicating the number of characters we have entered.

To test this behavior, we can use the sendKeys() method (example8.js):

casper.test.begin('Test key inputs', 1, function(test) {
   casper.start('http://localhost:8000/example8.html', function() {
      this.sendKeys('input[name="firstname"]', 'Eric');
   });
   casper.then(function() {
      test.assertSelectorHasText('#message', "You have entered 4 characters.");
   });
   casper.run(function() {
        test.done();
    });
});

When we run the code, we will see the following result. If you enter Eric in the text input, the message will display You have entered 4 characters:

The sendKeys() method inserted the text into the text input and also triggered the onkeyup event.

The sendKeys() method can produce a key event on any element of the page (not necessarily inputs).

Let's change the page example8.html so that the header becomes editable:

<h1 contenteditable="true">My example</h1>

Now if we click on the header, we can change its text content.

Let's modify our test script:

casper.test.begin('Test key inputs', 2, function(test) {
   casper.start('http://localhost:8000/example8.html', function() {
      this.sendKeys('input[name="firstname"]', 'Eric');
   });
   casper.then(function() {
      test.assertSelectorHasText('#message', "You have entered 4 characters.");
   });
   casper.then(function() {
      this.click('h1');
      this.sendKeys('h1', 'I have changed my header');
   });
   casper.then(function() {
      test.assertSelectorHasText('h1', "I have changed my header");
   });
   casper.run(function() {
        test.done();
    });
});

Now, perform the following steps with this test:

  • Click on the header and enter a new text at its beginning

  • Assert the new header content

The output will be as follows:

Note

As we have an extra assertion in our test script now, we have changed the begin() method's second parameter from 1 to 2.

Regarding mouse events, we have already used the casper.click() method. It takes a selector as a parameter and triggers a click event on the designated element.

We use it to click on links or buttons, for instance.

We can also trigger other mouse events using the mouseEvent() method; its first parameter is the event type and the second is the targeted element selector.

It can trigger the following events:

  • mouseup

  • mousedown

  • click

  • mousemove

  • mouseover

  • mouseout

Let's create the following web page (example9.html):

<html><body>
   <h1 id="chap1">Chapter 1</h1>
   <h1 id="chap2">Chapter 2</h1>
   <h1 id="chap3">Chapter 3</h1>
   <h1 id="chap4">Chapter 4</h1>
   <div>Counter: <span id="counter"></span></div>
   <script>
var counter = 0;
function incrementCounter() {
   counter =  counter + 1;
   document.querySelector('#counter').textContent = counter;
}
for(i=0;i<document.querySelectorAll("h1").length;i++) {
   document.querySelectorAll("h1")[i].onmouseover=incrementCounter;
}
   </script>
</body></html>

It presents a list of headers and when the mouse goes over any of them, a counter is incremented.

We can test this page using mouseEvent(), as shown in the following test (example9.js):

casper.test.begin('Test mouse events', 1, function(test) {
   casper.start('http://localhost:8000/example9.html', function() {
      this.mouseEvent('mouseover', '#chap1');
      this.mouseEvent('mouseover', '#chap4');
      this.mouseEvent('mouseover', '#chap1');
      this.mouseEvent('mouseover', '#chap1');
   });
   casper.then(function() {
      test.assertSelectorHasText('#counter', "4");
   });

   casper.run(function() {
        test.done();
    });

});

The outcome is as follows:

But CasperJS also provides a specific 'mouse' module to control the mouse directly. It allows to move the mouse and to control the click (down, up, click, double-click).

It might be useful if we want to test complex mouse interaction such as drag-and-drop. The following is a web page that provides a draggable box (example10.html) using the jQueryUI draggable() method:

<html>
<head>
<style>
#box {color: white; background-color: blue; width: 100px; position: absolute; top: 0; left: 0;}
</style>
</head>
<body>
   <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
   <script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
   <script>
$(document).ready(function() {
   $('#box').draggable();
});
   </script>
   <div id="box">My box</div>
</body></html>

We can test this page using the CasperJS mouse module (example10.js):

casper.options.viewportSize = {width: 1024, height: 768};

casper.test.begin('Test drag&drop', 2, function(test) {
    casper.start('http://localhost:8000/example10.html', function() {
        test.assertEval(function() {
            var pos = $('#box').position();
            return (pos.left == 0 && pos.top == 0);
        }, "The box is at the top");
        this.mouse.down(5, 5);
        this.mouse.move(400, 200);
        this.mouse.up(400, 200);
    });
    casper.then(function() {
        test.assertEval(function() {
            var pos = $('#box').position();
            return (pos.left == 395 && pos.top == 195);
        }, "The box has been moved");
    });
    casper.run(function() {
        test.done();
    });
});

The output will be as follows:

We can see that there are a lot of interesting things in this test:

  • We set the viewport size (using the viewportSize option). In our case, it is useful because we need to make sure that we have enough room to move the box where we want.

  • We use jQuery to get the current position of the box! As jQuery is loaded in our tested page, we can use it through assertEval(), as it runs the code in the tested page.

  • We use down(), then move(), and then up() to produce a drag-and-drop move from point (5,5) to point (400,200).

How it works...

Even if they might seem quite similar, there are differences between casper.click() and casper.mouse.click().

First of all, casper.click() only accepts a selector as a parameter, while casper.mouse.click() accepts either a selector or a (x, y) position.

But more importantly, they do not work the same way; casper.click() creates an event and dispatches it to the targeted event, but casper.mouse.click() does not deal with any element and just produces a mouse action at the given position.

If casper.click() is not able to dispatch the event (because of a preventDefault() method hanging somewhere), it will default to the casper.mouse.click() method.

Note

casper.mouseEvent() works exactly as casper.click().

There's more...

Let's explore a few more details about the different features we have just used in the previous section.

Passing parameters to the download() method

The download() method might also accept two extra parameters as follows:

download(String url, String target[, String method, Object data])

So, we can produce an HTTP request using the method we want and pass the needed arguments in the data object.

setHttpAuth might have surprising timing

Consider that we come back to our test (example6.js) about setHttpAuth() and try to chain the two versions as follows:

casper.test.begin('Log in', 2, function(test) {

    casper.start('http://www.plomino.net/zmiroot/testpage', function() {
        test.assertHttpStatus(401, 'We cannot see the page as we are not logged in.');
    });

    casper.setHttpAuth('demoaccount', 'demoaccount');

    casper.thenOpen('http://www.plomino.net/zmiroot/testpage', function() {
        test.assertTextExists('You are logged in.', 'Now we can see the page.');
    });
    casper.run(function() {
        test.done();
    });
});

We would expect the tests to be successful due to the following conditions: we get a 401 error if we are anonymous and are able to view the page if we log in.

But, the following is what we obtain:

The first test fails; we do not get a 401 error, but we get a 200 status instead! This means we are actually logged in.

Why is that? It's because CasperJS chains the steps that are enclosed into then() blocks. If setHttpAuth() is not enclosed in a then() block, it will be effective right from the beginning (the start() call) to the end.

Let's enclose it in a then() block as follows:

casper.test.begin('Log in', 2, function(test) {

    casper.start('http://www.plomino.net/zmiroot/testpage', function() {
        test.assertHttpStatus(401, 'We cannot see the page as we are not logged in.');
    });

    casper.then(function() {
        this.setHttpAuth('demoaccount', 'demoaccount');
    })

    casper.thenOpen('http://www.plomino.net/zmiroot/testpage', function() {
        test.assertTextExists('You are logged in.', 'Now we can see the page.');
    });
    casper.run(function() {
        test.done();
    });
});

And now, the tests pass as follows: