Sunday, December 18, 2011

Somewhat Pretty Printing with PhantomJS, Jasmine and Backbone.js

‹prev | My Chain | next›

I now have the jasmine specs covering my Backbone.js application running under PhantomJS. Running, but the failure leaves something to be desired. The exit code is a success and the failure message simply parrots the entire suite with no indication of which specs failed.

I know that there are terminal reporters, a la the node-jasmine. They do not seem to be a quick drop-in replacement in my little PhantomJS setup. Besides, I hope that I can hack together a little something that will be good enough™.

I still have my intentionally failing spec in place:


Back in the run.html.erb page runner for the jasmine gem, I make the jasmineEnv variable global so that I might access it from within PhantomJS:
require(['Calendar', 'backbone'], function(Calendar, Backbone){
  window.Cal = Calendar;
  window.Backbone = Backbone;

  window.jasmineEnv = jasmine.getEnv();
  jasmineEnv.updateInterval = 1000;

  var reporter = new jasmine.TrivialReporter();

  jasmineEnv.addReporter(reporter);

  jasmineEnv.specFilter = function(spec) {
    return reporter.specFilter(spec);
  };

  jasmineEnv.execute();
});
Then, in the PhantomJS contributed run-jasmine.js, I log the number of failed specs found in the current spec runner:
  waitFor(
    function(){ /* finished at to display */ }, 
    function(){
      page.evaluate(function(){
        console.log('Failed: ' + jasmineEnv.currentRunner().results().failedCount);
        // ...
      });
  });
(I uncover that chain by fiddling with things in Chrome's Javascript console)

With that logger in place, when I run my intentionally failing specs, I see:
# lots of normal application console.log output ...

[object Arguments]
'waitFor()' finished in 1164ms.
Failed: 1

# The text for all specs in the suite...
Looking more closely at the PhantomJS run-jasmine.js script, I notice that it is quite shallow:
  waitFor(
    function(){ /* finished at to display */ }, 
    function(){
      page.evaluate(function(){
        console.log('Failed: ' + jasmineEnv.currentRunner().results().failedCount);
        list = document.body.querySelectorAll('div.jasmine_reporter > div.suite.failed');
        for (i = 0; i < list.length; ++i) { /* ... */ }
      });
  });
By "shallow", I mean that the script is simply grabbing the top-level failed spec and then logging the inner text of each immediate-child:
           list = document.body.querySelectorAll('div.jasmine_reporter > div.suite.failed');
           for (i = 0; i < list.length; ++i) {
               el = list[i];
               desc = el.querySelectorAll('.description');
               console.log('');
               for (j = 0; j < desc.length; ++j) {
                   console.log(desc[j].innerText);
               }
           }
As can be seen from the screen shot of my failing spec, I am nest things quite a bit. What this translates into is a bunch of nested spec groups, each of which have some failing and some passing specs:


To paraphrase the immortal philospher of movies, I know this... it's recursion! So I create a log_failure recursive function to log the specs properly:
  waitFor(
    function(){ /* finished at to display */ }, 
    function(){
      page.evaluate(function(){
        console.log('Failed: ' + jasmineEnv.currentRunner().results().failedCount);

        function has_message(el) { /* children className == spec failed */ }
        function failed_children(el) { /* all children w/ className == 'suite failed' */ }

        function log_failure(failure_el, indent) {
          if (typeof(indent) == 'undefined') indent = '';

          console.log(indent + failure_el.querySelector('.description').innerText);

          if (has_message(failure_el)) {
            console.log(indent + '  ' + failure_el.querySelector('.messages > .fail').innerText);
          }
          else {
            failed_children(failure_el).forEach(function (failed_child) {
              log_failure(failed_child, indent + '  ');
            });
          }

          log_failure(document.body.querySelectorAll('div.jasmine_reporter > div.suite.failed')[0]);
        }
      })
    });
That is kinda ugly because I had to work with HTML Collections instead of arrays. Still, it seems to work because the output produces:
'waitFor()' finished in 1117ms.
Failed: 1
Calendar
  the initial view
    the page title
      Expected '<h1>Funky Calendar<span> (2011-12) </span></h1>' to have text 'Remember, Remember the Fifth of Novemeber'.
I think I can live with that.



Day #238

No comments:

Post a Comment