This recipe will detail how to simulate rich web interactions using CasperJS, in order to achieve more complex testing.
The following sections cover the various steps in writing advanced tests.
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.
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:
Go to http://imagebin.org.
Click on the Add your image now! link.
Then, fill in the image submission form.
Assert that we obtain a page containing our image.
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:
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:
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()
, thenmove()
, and thenup()
to produce a drag-and-drop move from point (5,5) to point (400,200).
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.
Let's explore a few more details about the different features we have just used in the previous section.
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.
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: