Using Javascript Promises to avoid race conditions between asynchronous Javascript modules and Selenium

To homepage

While working on liwwa’s UI Selenium tests I sometimes faced problems with the selenium tests starting to execute before the javascript is fully loaded. Since I’m using RequireJS, there is no guarantee that the javascript modules that are being asynchronously loaded will be loaded when the window load event is fired.

Conceptually speaking, this problem can be resolved by requiring selenium to wait until all requirejs modules are loaded before executing UI actions. The problem, however, is that requirejs doesn’t provide an easy way to detect when any and all require() callbacks have executed. To be sure, I’m not talking about the case where you only have one require() call that you care about. In that case, it is pretty easy to detect when modules are loaded:

require(['module1', 'module2', 'module3'], function () {
  // ...
  // append an element with a unique ID to the document and
  // have selenium wait on that element to be added to the DOM
});

But when you have multiple pages with their own require() calls, or even some pages with multiple require() calls, it becomes tedious and error-prone to add a DOM element inside the callback of every single require() call so that we selenium can observe when requirejs modules are loaded.

My solution in this case is to have the following snippet at the top of every page (which is easy to do, since I use a templating engine on the backend, and all my pages inherit from one base template):

<!-- Set up RequireJS -->
<script type="text/javascript" src="/static/js/require-config.js"></script>
<script type="text/javascript" src="/static/js/lib/require.js"></script>
<!-- Load RSVP.js for promise support -->
<script type="text/javascript" src="/static/js/lib/rsvp.js"></script>

<script type="text/javascript">
  var require_promises = [];
  // replace the require call with another that creates a promise
  // and appends the promise to a promise list.
  window.require = (function () {
    var original_require = window.require;
    return function (_list, _callback) {
      var promise = new RSVP.Promise(function(resolve, reject) {
        original_require.call(null, _list, function () {
          _callback.apply(null, arguments);
          // when the callback is executed resolve the promise
          resolve();
        });
      });
      require_promises.push(promise);
    };
  })();

  // element that will be looked for by selenium to know when the
  // page is ready for testing
  var div = document.createElement('div');
  div.id = "__selenium_page_loaded";

  window.onload = function () {
    // check that all promises are done
    RSVP.Promise.all(require_promises).then( function () {
      document.getElementsByTagName("body")[0].appendChild(div);
    });
  };

  // when changing the page (e.g. a link is clicked) remove the 
  // selenium element so that it wouldn't find it until the new
  // page is completely loaded
  window.onunload = function () {
    if (div.parentNode !== null &&
        typeof(div.parentNode.removeChild) !== 'undefined') {
      div.parentNode.removeChild(div);
    }
  };
</script>

In short, we’re replacing the window.require function so that it always creates a promise and appends it to a list. When each modified require function finishes loading its relevant modules, it executes the attached callback then proceeds to resolve the promise. When page DOM’s ready event is fired, we do a Promise.all call so that we wait on the list of promises accumulated by all the require calls on the page, once that list is resolved, we’re ready to add the selenium specific div.

With this solution, preventing race conditions between selenium tests and requireJS asynchronous module loads becomes a matter of having selenium simply wait on the DOM element created by the script above. This had a great effect on increasing the stability of my selenium tests, and that, as my favorite Physics professor used to say, is “suuuuuuuuuuuppper!”.