Testing focus in JavaScript

 Date: January 29, 2016

This blog post is an overview of testing focus behavior in web browser.

During the work on Azure Portal I spent quite a bit of time on ensuring rich keyboard support. This requires appropriate focus management. When user press some keyboard shortcut, the focus should move to appropriate element. There is a good article on MDN about Keyboard-navigable JavaScript widgets. Focus behavior should be also tested very succinctly. As it is very easy to change (break) with even the smallest change in the HTML or JavaScript.

Standard test goes as follows:

  • Arrange: open some page
  • Act: execute some keyboard shortcut that should open particular page and/or set focus on particular element
  • Assert: check if expected element is focused

How to check if element is focused

The simplest way is to use jQuery:

expect($(element).is(':focus')).equals(true);

However this may not always work. Especially if you run your unit tests in parallel, because $element.is(':focus') will not work when window does not have focus.

The better (right) way is to use document.activeElement:

expect(document.activeElement).equals(element);

This will work even when browser window is not focused.

Testing async actions

Sometimes, keyboard invoke asynchronous actions that eventually will change focus. This can cause:

  • false negative tests: assertion was executed before focus was eventually set
  • false positives: finally focused element, got focus after assertion was executed

The simplest recipe for the first problem is to delay assertion:

setTimeout(() => {
    expect(document.activeElement).equals(element);
    done();
}, 100);

The problem with this approach is choosing appropriate delay for assertion. Therefore it is better to avoid raw setTimeout, and use polling approach that I described in my post setTimeout considered harmful:

poll(
  () => document.activeElement === element, 
  () => {
        assert(true);
        start();
  },
  () => {
        assert(false);
        start();
  }
);

The polling function can be also used for the second solution (by changing assertion order in callback functions). However, for the false positives problem, simple setTimeout is good enough, because we do not have a choice other than wait some particular period of time to execute assertion.

Invoking keyboard actions

There are 3 types of events that we can use to simulate keyboard action:

  • keydown
  • keyup
  • keypress

The safest bet is to use keydown. Why? An example might be using special keys. While in case of keypress - various browsers handle it differently (e.g., by not triggering the event), keydown is pretty consistent. I recommend you to check this article if you are interested in details. It was not updated for some time, but it will give you an idea.

In order to detect keyboard actions you may handle events on capture or bubbling phase. You should choose model depending on your needs, but in general bubbling phase is optimal solution. Additionally it is supported by jQuery, with browser inconsistencies taken care of for you.

To invoke keydown event in your tests, you need to set focus on the element first:

$(element)
  .focus()
  .trigger("keydown", { which: keyCode });

You can find JavaScript key codes here.

Summary

Use document.activeElement to check which element is focused.
Use polling approach for testing async actions.
Use keydown event, and bubble phase to invoke/handle keyboard actions.

 Tags:  programming

Previous
⏪ Do not take this personal

Next
NDC London 2016 - Azure Portal and recommended talks ⏩