Wednesday, June 25, 2014

Casper, d3, jquery and clicking

I spent (wasted? invested?) an awful lot of time yesterday on trying to use CasperJS to click a link inside an SVG diagram that had been made by d3.js. I’ll start this article by showing my Foolish Mistake, but then will document what I did learn.

Foolishness

Normally, in CasperJS, we write this.click('#myButton'), where “#myButton” can be just about any CSS selector.
Aside: it is normally this.click() rather than casper.click()
because it is normally in the handler function of a casper.waitXXX()
function.
This wasn’t working for me trying to click an SVG <g> tag, that starts a search running. I was taking a screenshot 0.5s later to check it had worked, and it showed the page had not changed.
And that turned out to be foolish. The click was working, the search was working, d3 and SVG had nothing to do with anything. The problem was simply were no search results found, and so it never moved to the next page. When I changed the search string, suddenly click() was working.
So my troubleshooting turned out to be barking up the wrong tree. But, still, I did learn a few things from it.

Using evaluate() With jQuery

Instead of calling this.click() you can do something like this:
this.evaluate(function(){
  $('#myButton').click();
  };
This runs JavaScript from within the browser’s context. In this case I use jQuery. This works just as well as using Casper’s click() outside the evaluate().
Here there is no advantage. But, by being in a different scope, we have extra flexibility: we could call other functions, or add new event handlers, etc, etc.

Using evaluate() With d3

Here was another of my attempts, but this one does not work:
this.evaluate(function(){
  d3.select('#myButton').click();
  };
The reason: a d3 selection does not have a ready-made click() function.

Making events happen

this.evaluate(function(){
  var evt = document.createEvent("MouseEvents");
  evt.initMouseEvent("click", true, true, window,
    0, 0, 0, 0, 0, false, false, false, false, 0, null);
  return d3.select('#myButton').node().dispatchEvent(evt);
  };
This is how you do a click with d3 (you could use this approach with jQuery too (see a helper function), but are unlikely to ever need to). The first lines create a (simulated) mouse click. The final line sends that event to the DOM item of interest.
Aside: dispatchEvent() returns false if any of the event handlers
cancelled it, true otherwise.
I learned this here; that answer also says this should have worked:
this.evaluate(function(){
  d3.select('#myButton').on("click")()
  };
This definitely does not work for me. Why? It is actually a cheat, trying to find and call the click handler for the button. In my case the click handler was attached to the parent object (a <g>) not the object I was calling select on. (Also, because it is a cheat, this approach does not work when you’ve attached multiple handlers.)
By the way, if not using jQuery or d3, you can use querySelector() and do it this way:
this.evaluate(function(){
  var evt = document.createEvent("MouseEvents");
  evt.initMouseEvent("click", true, true, window,
    0, 0, 0, 0, 0, false, false, false, false, 0, null);
  return document.querySelector('#myButton').dispatchEvent(evt);
  };

Summary

The real lesson here, for me, was when something doesn’t work, make sure you are judging success in the right way!
Beyond that, it turns out there are a whole host of ways to click a button in a CasperJS script. Use the simplest when you can, bear the others in mind for special occasions.
Written with StackEdit.

No comments: