Perfection Kills

by kangax

Exploring Javascript by example

← back 914 words

Cross-browser mouse events simulation

It’s been quite a while since my last post. Non-stop work on scripteka as well as few other projects is fun but takes all the free time.
I promise to write few useful tutorials in a near future, but for now, let’s talk about simulating mouse events and how this brings UI testing on a completely new level.

In case you haven’t heard about Prototype UI, now is a good time to check it out. Originally started by Sebastian Gruhier (creator of very popular Prototype Window Class) and Ruby/JS guru Samuel Lebeau, Prototype UI aims to provide a set of high quality, skinnable UI components which can ease development of complex User Interfaces in web browsers.

I have recently joined the team and am currently working on UI.ContextMenu and UI.IframeShim modules. Besides these, the library is featuring full-blown Window and WindowManager classes (completely reworked PWC), Carousel, Dock, Shadow and others.

UI.ContextMenu is nothing more than a port of Proto.Menu with few changes to take advantage of library’s core methods. Increased complexity and deep integration with other components is the reason why we do Unit Testing. Unit testing is what makes development a summer breeze – knowing that next commit into a trunk plays nice with the rest of the code sure feels good.

One of the lesser known features of scriptaculous unit testing framework is Event.simulateMouse method. The name is pretty self-descriptive – it simulates mouse event, allowing us to pass some optional parameters along – pointerX, pointerY, shiftKey, etc. Everything would be just fine if this nifty helper was a little more cross-browser. The latest revision of Scriptaculous’s unittest.js states that this feature is experimental and is Firefox-only. Bah! Shouldn’t we account for at least the most popular browser out there – IE6 and 7?

Ok, so it doesn’t work cross-browser, but do we really care? Why is it such a big deal anyway? Why simulate mouse event which triggers certain methods in our code, if we can “shorten the road” and call those methods directly? I asked myself same questions, and the answer seems to be – “Yes, we should be using it” and here’s why:

1

Mouse simulation (MS) based tests are much “closer” to the real life use cases. We sort of draw a straight line from action to the result without delving into actual implementation – “Clicking toggle button should collapse container. Clicking it again should expand it.”

2

The MS based tests can effectively catch UI related problems which would otherwise be really painful to find. As an example, let’s think of a window widget. This widget has minimize and maximize buttons. Clicking those buttons invokes appropriate actions – window changes its size and position. We decide to write assertions based on direct method invocation:



new Test.Unit.Runner({
   testMaximize: function() {

      // create window object
      var myWindow = new UI.Window().show();

      // call our method
      myWindow.maximize();

      // wait till effect is completed
      wait(1000, function() {

         // assert that window has proper dimensions
         asserEqual(800, myWindow.element.getWidth());
         assertEqual(600, myWindow.element.getHeight());
      })
   }
});

Ok, all seems fine. We run the code, window resizes, tests pass, we are happy and feeling safe. Let’s see what happens next. Few days go by and one of the users reports a problem – “clicking on maximize button does NOT maximize the window”. Feeling pretty clueless, we fire up the test suit and realize that it still passes all the assertions. Moreover, the window appears to be resizing as assertion is executed! It takes us 2 hours of tedious debugging, just to find out that the problem is not in maximize method at all. The problem is that invisible iframe, which should be positioned under the menu (to prevent IE6 <select> overlapping) somehow has z-index higher than window element and is effectively blocking any clicks on a button. No need to mention that MS based tests would make it much easier to catch the nasty bug.

I have to say that this is not a real life example – it has never happened to me when testing any of the window functionality. My point was to show one of the many UI related problems – problems which would be not so easy to find and debug. As complexity of web applications increases, the dynamic elements on a page and their behavior can quickly get out of order. Having more precise tools for the job could no doubt save a lot of time.

Turning Event.simulateMouse into a somewhat robust solution was quite trivial. The result – cross-browser mouse event simulation that works in FF2+, IE6+, Safari2+ and Opera9+. Instead of calling maximize as in previous example we could now simply do:



new Test.Unit.Runner({
   testMaximize: function() {
     ...

     var button = myWindow.getButtonElement('maximize');

     // simulate mouse click on a button
     Event.simulateMouse(button, 'click');

     // or using element's method
     button._click();
     ...
   }
});

Pretty simple, don’t you think?

Last thing to mention is that all of this is very much work in progress.
I will see what can be done to simulate drag&drop or mouseover/out functionality, so stay tuned.

The latest version of Event.simulateMouse can be found in a trunk (and unit tests for the curious ones). All you need to do is inculde event_simulate_mouse.js right after unittest.js.

Bug reports, or any comments are as always very welcome.

Start experimenting and have fun!

Did you like this? Donations are welcome

comments powered by Disqus