Protractor blocking wait

I’ve been working with non-angular protractor testing lately, and have been frustrated at the lack of documentation for how to perform simple tasks. For example, blocking execution while waiting for an element to appear, or another condition. My team’s tests were relying heavily on sleep() in the absence of such knowledge.

This week, I figured out the secret to a blocking wait.

Let’s say you’ve got some page rendering going on, and you need to wait for an element to show up before continuing:

browser.wait(element(by.id('some-element')).isPresent);

That’s it! Now, and this is absolutely vital: note that we pass in the function isPresent, not the promise it returns. This is because browser.wait() may run the function *many* times, and return many promises, until one of them resolves and passes the test.

What if we have a more complex condition, such as waiting for the element to be removed? In that case, we need to return our own promise-returning function:

browser.wait(function() {
  var deferred = protractor.promise.defer();
  element(by.id('some-element')).isPresent()
  	.then(function (isPresent) {
      deferred.fulfill(!isPresent);
    });
  return deferred.promise;
});

browser.wait() will execute our outer anonymous function as many times as it needs to until the promise is fulfilled with a truthy value.

Here’s a more generic function to handle pretty much any blocking-wait-for-condition that you’ll need:

function waitForPromiseTest(promiseFn, testFn) {
  browser.wait(function () {
    var deferred = protractor.promise.defer();
    promiseFn().then(function (data) {
      deferred.fulfill(testFn(data));
    });
    return deferred.promise;
  });
}

…then use this like so:

waitForPromiseTest(element(by.id('some-element')).isPresent,
  function (isPresent) {
    return !isPresent;
  });

Not a single line of code will execute until your test function returns true.

Note about browser vs. protractor.getInstance(): they’re exactly the same object! browser is the new preferred syntax.

The future of nugget functionality

I’ve spent some of today updating my “jQuery plugin”, doWhen, with the added help of a splendid chap and coworker, name of Erin Swenson-Healy. He adapted it to be more AMD friendly, and I expounded upon that. Basically, now, using requirejs, we can simply say in a given file:

define(["doWhen"],
function(doWhen){
  doWhen(....);
});

Which means for our purposes, it’s not really a jQuery plugin. And, realistically, it’s not. It’s just a tiny nugget of functionality we want to pull in. All it uses from jQuery is a flat extend, and that is easily replaceable (as it already is in the “plain” version).

However, not everybody will consume doWhen through AMD or a similar method. And not everybody who doesn’t necessarily uses jQuery. But I need *somewhere* to attach it to, if AMD or equivalent is not defined.

So, I’m thinking of ditching the jQuery requirement entirely. The “plain” version would be the only one. If AMD or something like it is present, it would define according to that pattern. If jQuery, underscore, or another common library is present, it would attach to one or more of those for more traditional use, defaulting perhaps to the global object if none are present?

doWhen

It occurs to me that I have neglected to post about the most bloody useful utility I’ve ever written: doWhen

While the main version is a jQuery plugin, I find myself using the non-jQuery version more often, so that’s the one I’ll describe here.

What doWhen does is allow you to create a single “virtual event” in the form of a test function which, when it returns true, runs a callback function.

Let’s say we were loading a library and wanted a quick way to do something when the library is ready:

<script type="text/javascript">
var global = (function(){return this;}());
Event.doWhen(function(){
  return !!global.jQuery;
}, function(){
  alert('jQuery loaded!');
});
</script>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.min.js"></script>

The !! ensures the result is a boolean. Every 100 milliseconds (configurable), it will run the first function. The first time that function returns true, it runs the second function.

I use this extremely frequently, even in wee code snippets, thanks to the tiny footprint (about 1/3kB minified).

See the github page for options.

Open question: is Event the place to put this? Keep in mind this isn’t extending the object’s prototype, so shouldn’t cause conflicts in other code.

I am considering whether there’s a need for an ajax capable one which periodically checks a remote state.

JWPlayer and Firefox hunting

Greetings m’chums! Welcome to this week’s big adventure: trapping and shooting an old bug in a very popular tool.

In the course of my everyday tinkering, I encountered a curious flaw in JWPlayer under Firefox which I think has plagued fellow adventurers of this fine Flash movie player for years, but which none seem to have sussed the essential nature of. Until today.

It all started when I found the player was firing no events. Initial investigation showed that I was never subscribing the events, because I was waiting for the event attaching method jwAddEventListener to arrive on the scene:

var player = $('#myplayer');
$.doWhen(function(){
  return !!player.jwAddEventListener;
}, function(){
  //attach events
});

(doWhen is a nifty little utility I wrote that runs one function once after another function returns true)

A full investigation of the matter revealed that, in Firefox, the entire Javascript API for the embed was never being created! This was quite curious, as I’ve had many players fully functional in Firefox before.

I set about identifying what was different, and at last deduced that in all the previous cases, I had been instantiating the player after an ajax call (for content). In this case, however, the player was being created immediately after jQuery(document).ready.

Pausing briefly to brew up another cup of brown joy strong enough that the spoon stood rigid, I continued the experiment by delaying the player instantiation by two seconds with setTimeout. Lo and behold, the missing API was rumbled!

This was far from an ideal solution, of course. I wondered at what critical juncture Firefox would be in a position to correctly instantiate JWPlayer. Time to dust off an old friend: window.onload. A quick test showed that yes, indeed, once the load event had fired off, the player’s entire API reappeared like Manchurians at a near-dead pub advertising half-off pints of Brown!

Naturally, I didn’t want to slow other browsers down unnecessarily, so I knew that the final solution would have to narrow its sights on Mozilla, and only if the load had not yet fired. The way to determine the latter is document.readyState: “interactive” means the page is still in its initial rendering phase, but the DOM is available; “complete” is the word we’re looking for, the readyState when load has fired.

if ($.browser.mozilla
      && document.readyState !== 'complete') {
  $(window).bind('load', function(){
    //write your embed code here
  });
} else {
  //write your embed code here
}

There you have it! Clearly, this should not be necessary; JWPlayer should do all of this internally. For now, it’s a workaround that seems 100% successful.

And with that, another successful week tromping through the deepest woods of my trade draws to a close. I bid your weekend be filled with lakes of Earl Grey and barrels of gorgeous Somerset gold. Tata!

More on Randomness of Arrays

A few months ago, I posted about how the classic method most JS programmers would use to randomise an array completely fails. I proposed a quick solution to it, but that solution had flaws of its own.

So, for those of you who need to shuffle decks of cards, scramble virtual eggs or just want to toss a little confusion into your applications, here’s a fully working array randomiser:

Array.randomiseArray = function(arr){
  var i, v;
  for (i=0; i<arr.length; i++) {
    v = arr[i];
    arr[i] = {
      v: v,
      sort: Math.random()
    };
  }
  arr.sort(function(a, b){
      return a.sort - b.sort;
  });
  for (i=0; i<arr.length; i++) {
    arr[i] = arr[i].v;
  }
  return arr;
};

SMIRF 2.0

Late last year, I told you about the SMIRF JS pattern I’ve been working on. Since then, I’ve had quite a lot of opportunity to use it in the field and refine it.

The biggest weakness with the old SMIRF pattern was the need to pass the instance variables around all the time. It meant there was a tiny memory leak in the api, since wrapper functions needed to add the iVars argument to all calls.

What I’ve changed is that iVars is now accessed via a method getIVars┬áin the “constructor”. There’s a localised randomly generated private key which must be passed in to get the instance variables. That way, nothing outside could grab them and clobber them (unless you write a method to deliver them, obviously!

So, here’s the updated pattern:

var employeeFactory = (function(){
  var PRIVATE_KEY = String(Math.random()), giveRaise, fire;
  
  giveRaise = function(percent){
    var iVars = this.getIVars(PRIVATE_KEY);
    percent = percent || 0;
    console.log(iVars.salary);
    iVars.salary = iVars.salary * (1 + (percent/100));
    return iVars.salary;
  };
  fire = function(){
    var iVars = this.getIVars(PRIVATE_KEY);
    //long, drawn out legal process that takes 5000 lines
    iVars.currentlyEmployed = false;
  };
  
  return function(name, salary){
    //This is essentially a constructor function
    var iVars = {
      //Instance variables. All state info should be stored here
      name: name,
      salary: salary,
      currentlyEmployed: true
    };
    return {
      getIVars: function(key){
        if (key === PRIVATE_KEY) {
          return iVars;
        }
      },
      giveRaise: giveRaise,
      fire: fire
    };
  };
}());
var employee1 = employeeFactory('Bob', 30000);
var employee2 = employeeFactory('Mary', 21000);

An Array of Random Thoughts

Today, I was asked to supply a way (in Javascript) to create an array of all integers 0-9, but randomised, with no duplicates. Immediately, I gave this solution:

var arr = [0,1,2,3,4,5,6,7,8,9].sort(function(){
  return 0.5 - Math.random();
});

That should do it, right? Well, one of the tests that was run on the resulting “random” array was to see whether or not the first five members were consistently even or odd:

var generateRands = function(){
  return [0,1,2,3,4,5,6,7,8,9].sort(function(){
    return 0.5 - Math.random();
  });
};
var isMoreEven = function(arr){
  var i, even = 0, odd = 0;
  for (i=0; i<5; i++) {
    if (arr[i] % 2) {
      odd++;
    } else {
      even++;
    }
  }
  return even > odd;
};
var runTests = function(iterations){
  var i, even = 0, odd = 0, arr;
  for (i=0; i<iterations; i++) {
    arr = generateRands();
    if (isMoreEven(arr)) {
      even++;
    } else {
      odd++;
    }
  }
  console.log('even won', even, 'or', (even/iterations * 100) + '%',
    '; odd won', odd, 'or', (odd/iterations * 100) + '%');
};
runTests(10000);

The expected result would be that even and odd would hover around 50%, with some variance either way. Here’s what it *actually* outputs:

even won 6762 or 67.62% ; odd won 3238 or 32.379999999999995%

WTF? It hovered consistently around this ratio with every test! I thought at first that it was perhaps a pattern in the pseudo random number generator, but no matter what I did, it hovered strongly in favour of even.

Keep in mind this is the method most commonly recommended on tutorial sites around the net for randomising an array’s order!

I added a further test to this, to see how frequently each number ends up in the first five. Here’s the test:

var runTests = function(iterations){
  var i, j, arr, nums = [], order = [];
  for (i=0; i<10; i++) {
    nums.push({
      num: i,
      count: 0
    });
  }
  for (i=0; i<iterations; i++) {
    arr = generateRands();
    for (j=0; j<5; j++) {
      nums[arr[j]].count++;
    }
  }
  nums.sort(function(a, b){
    return a.count - b.count;
  }, true)
  for (i=0; i<10; i++) {
    order.push(nums[i].num + ' (' + nums[i].count + ')');
  }
  console.log(order);
};
runTests(10000);

The output was consistently something like this:

["9 (309)", "8 (598)", "7 (1180)", "6 (2256)", "5 (3963)",
  "4 (6170)", "3 (7926)", "2 (8817)", "1 (9386)", "0 (9395)"]

The number in parentheses represents the number of times in 10000 that the number to the left appeared in the top five. This “random” sort didn’t seem to take array members very far from home. One and zero are the only two that I could get to swap how frequently they appeared in the first five, for some reason.

What I finally realised was that the flaw was in my use of sort. Sort swaps adjacent items until it reaches the conclusion that the array is in the proper order. Each item is “less than” or “greater than” the other, and this is meant to be a consistent value. If you randomise the compare function’s output, then it will swap items, then swap them back, and finally give up on your inconsistency.

Even was coming up more often than odd because there are a greater number of even numbers from 0-4.

And now, what you probably visited this page for. A *working* array randomiser:

UPDATE: I have a much better method now in this post!

var randomSort = function(arr){
  var i, l, sortOrder = {};
  for (i=0, l=arr.length; i<l; i++) {
    sortOrder[arr[i]] = Math.random();
  }
  return arr.sort(function(a, b){
    return sortOrder[a] - sortOrder[b];
  });
};
var generateRands = function(){
  return randomSort([0,1,2,3,4,5,6,7,8,9]);
};
for (var i=0; i<5; i++) {
  runTests(10000);
}

This worked! Here’s the output:

["4 (4933)", "0 (4953)", "7 (4983)", "6 (4990)", "9 (5002)",
  "5 (5010)", "1 (5017)", "3 (5029)", "2 (5038)", "8 (5045)"]
["9 (4915)", "4 (4940)", "2 (4941)", "8 (4957)", "5 (4974)",
  "7 (5011)", "3 (5044)", "1 (5046)", "0 (5077)", "6 (5095)"]
["2 (4947)", "4 (4963)", "5 (4977)", "0 (4987)", "3 (4988)",
  "9 (5002)", "6 (5013)", "1 (5022)", "7 (5034)", "8 (5067)"]
["8 (4871)", "4 (4938)", "9 (4962)", "1 (4977)", "0 (4989)",
  "2 (4993)", "3 (5012)", "6 (5051)", "7 (5057)", "5 (5150)"]
["7 (4944)", "3 (4953)", "8 (4968)", "5 (4976)", "0 (4991)",
  "4 (5017)", "1 (5024)", "6 (5028)", "2 (5030)", "9 (5069)"]

The even/odd test above works with this new method, too.

I just wonder how many faulty array shuffling implementations are out there that don’t work!

Note: This randomSort is not perfect! Identical array values will end up adjacent to one another.