Basics of Headless Browser Testing with Zombie.js


Testing interaction with your site in an automated fashion can be hard, but fortunately there are tools out there to make it easier. I didn’t realize they existed until recently so I have been playing with them. One I am really liking is Zombie.js. To be honest I haven’t tested anything too difficult, but the ajaxy/dynamic content stuff done through JavaScript I have done, and I like it.
How about we do something very basic, that shows what and how to test.

Test HTML Page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>
  <head>
    <title>Hello World</title>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function(){
        $('footer').click(function(){
          $("#content").append("<div class='my-data'>hello world</div>");
        });
      });
    </script>
  </head>
  <body>
    <div id="content">
      <footer>
        <Copyright>Now Buddy Lindsey</Copyright>
      </footer>
    </div>
  </body>
</html>
This page is extremely basic. You click on the footer and it appends a div to #content. This is just enough to do javascript calls and check to make sure something worked. Again this is a very basic example. Now you need to host that file somewhere. I use servedir which you can get via npm.

What You Need to Start Testing

Zombie.js allows you to interact with a page, but by itself does not allow you to do assertions. For that we need to use a test runner of some sort. The one I have been using lately has been Mocha since this is node.js we can just install mocha and zombie with npm.

Dependencies

1
2
npm install -g mocha
npm install zombie
That is it for our dependency installs.

Testing Our Div is There

The first test we want to do is to make sure our content div is there for us to use. Mocha uses a describe block to bundle up like tests, but it uses it functions for the actual tests. So describe is there for organization. Here is our first test which checks for our div.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var assert = require('assert');
var Browser = require('zombie');
 
var browser = new Browser();
 
describe("Index Page", function(){
    it("contains #content div", function(done){
        browser.visit("http://localhost:8000/index.html", function(){
            assert.ok(browser.success);
            assert.ok(browser.query("#content"))
            done();
        });
    });
});
Let us look at the first 3 lines.
1
2
3
4
var assert = require('assert');
var Browser = require('zombie');
 
var browser = new Browser();
The normal thing we are doing is including our zombie and assertion modules so we can use them in the rest of the code. The biggest thing to note though is we are instantiating a new browser object which is our zombie module. We are going to use browser throughout all of our tests.
1
2
describe("Index Page", function(){
});
The next part we have is our describe block, and all this does is give us our logical separation of tests. You will see later when we output our results what it is there for. Just know for now describe is more of a label than anything else.
1
2
it("contains #content div", function(done){
});
Here we are calling the it function giving it a label and passing a callback function with a done parameter. You will call done like a function when all of your assertions are complete, and it will finish off your test state.
1
2
3
4
5
browser.visit("http://localhost:8000/index.html", function(){
    assert.ok(browser.success);
    assert.ok(browser.query("#content"))
    done();
});
We are going to take this whole chunk at once.
  1. We are opening the page and rendering everything out
  2. The entire response is stored in the browser
  3. Once the visit is finished we run our assertions
  4. The success is making sure that we have a 200 response
  5. The query function selects the dom elements and returns it back
  6. Finally we end out with done(), and all is well
1
 

Testing Our JavaScript

This next code snippet is very similar to what we have covered so we are only going to cover the new stuff. Here is the entire code snippet.
1
2
3
4
5
6
7
8
9
10
it("add div to page", function(){
    browser.visit("http://localhost:8000/index.html", function(){
        assert.ok(browser.success)
        var footer = browser.query('footer');
        browser.fire("click", footer, function(){
            assert.ok(browser.query(".my-data"));
            done();
        });
    });
});
The key here is the browser.fire.
1
var footer = browser.query('footer');
This line queries for our footer html dom object and returns it so we can use it on our next line.
1
2
3
4
browser.fire("click", footer, function(){
    assert.ok(browser.query(".my-data"));
    done();
});
The biggest key here is the fire functions parameters
  1. First parameter is the event to fire
  2. The second is the element to which you want to fire event on
  3. Finally the callback
Make sure to take note the second parameter is the element not just a string name of the element.
Finally, we just do an assertion of some sort to make sure things worked. In this case we added a div to the page so we need to test it exists.

Final Step

Now that we have our code we are ready to run our tests. Make sure your index.html is hosted somewhere for our headless tests to get to. Make sure you have mocha and zombie installed. Then take all the code and put it in a javascript file in your test folder and run the following command:
1
mocha -R spec
You should get the following result
1
2
3
4
5
6
Index Page
  ✓ contains #content div (307ms)
  ✓ add div to page (324ms)
 
 
✔ 2 tests complete (370 ms)

All the Test Code

Here is all the test code in its glory for you to look at.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var assert = require('assert');
var Browser = require('zombie');
 
var browser = new Browser();
 
describe("Index Page", function(){
  it("contains #content div", function(done){
    browser.visit("http://localhost:8000/index.html", function(){
      assert.ok(browser.success);
      assert.ok(browser.query("#content"))
      done();
    });
  });  
   
  it("add div to page", function(){
    browser.visit("http://localhost:8000/index.html", function(){
      assert.ok(browser.success)
      var footer = browser.query('footer');
      browser.fire("click", footer, function(){
        assert.ok(browser.query(".my-data"));
        done();
      });
    });
  });
});

Conclusion

This was a basic example. Hopefully you have gained a starting point at doing headless browser tests. This is really good for doing front end acceptance testing for your UI. UI functionality testing is usually hard to do, but this really helps us get a step closer to automating it so we know, at least, when our features break for our users.
Previous
Next Post »