JavaScript Unit Test Gotcha: Element Focus

The task seems simple. Programmatically focus an input element and test whether the element received focus. Using jQuery, both tasks are straight forward:

// focus the element
// check the element for focus

Everything is right with the world, your tests pass - until you notice tests dealing with element focus are intermittently failing. Every time the tests fail, you tab to the tests, watch the tests run without failure and move on.

What's happening here?

According to the W3C spec, an element can be focused if four criteria are met:

  • The element's tabindex focus flag is set.
  • The element is either being rendered or is a descendant of a canvas element that represents embedded content.
  • The element is not inert
  • The element is not disabled

What does this mean in human understandable terms?

An element must have a tabindex set, whether explicit in the HTML or implicit by the browser. Browsers set an implicit tabindex on several elements:

  • input elements whose type is not hidden
  • textarea elements
  • select elements
  • button elements
  • a elements with an href
  • link elements with an href
  • elements with a draggable attribute set
  • elements with contenteditable=true

The element has to be rendered and visible - An element that is not part of the document cannot be focused, nor can elements with display: none or visibility: hidden styling rules.
The element must not be inert - text nodes are considered inert.
The element must not be disabled - an element with the disabled attribute is disabled.

A deep dive into jQuery

Dig deeply into the internals of jQuery, and you'll see that focusing an element ends with a call to the element's native .focus().

You'll also see some cross-browser hackery is required. For example, older IE's throw an exception if the element being focused is hidden.

// If jQuery ignored browsers that did not support document.querySelector, 
// its focus call could be boiled down to:
try {
  document.querySelector( element ).focus();
} catch(e) {}

How jQuery checks whether an element has focus is more interesting.

"focus": function( elem ) {
    return elem === document.activeElement 
            && (!document.hasFocus || document.hasFocus()) 
            && !!(elem.type || elem.href || ~elem.tabIndex);

There are a couple of interesting things here.

According to MDN, document.activeElement is "the element that will get keystroke events if the user types any."

document.hasFocus is a function I never knew about until this week. MDN states, document.hasFocus "returns a Boolean value indicating whether the document or any element inside the document has focus."

MDN adds a caveat:

When viewing a document, an element with focus is always the active element in the document, but an active element does not necessarily have focus. For example, an active element within a (popup) window that is not the foreground has no focus.

AHA #1! MDN states the document must be in the foreground for an element to have focus. It took me a while to realize that opening my browser's dev tools, tabbing to Hacker News, or focusing my vim window were all causing my tests to fail.

AHA #2! Even if a browser were capable of programmatic focus on a background window/tab/document, jQuery explicitly checks whether the document itself has focus. If not, the call to .is(':focus') automatically returns false.

A simple workaround - skip the test if the document is not in focus

I have started to wrap tests that check for focus with a function that only runs the test if programmatic focus is possible.

describe('render', function () {
  it('focuses the [autofocus] element', function () {
     // test is only run if document is in focus.
     requiresFocus(function () {

The requiresFocus function is pretty simple.

function requiresFocus(test, cannotTest) {
  if (document.hasFocus && document.hasFocus()) {
  else {
    var message = 'Cannot check for focus - document does not have focus.\n' +
                  'Try focusing the test document instead of another window\n' +
                  'or dev tools.';

    if (cannotTest) {

Of course, this isn't great because it masks potential test failures. To ensure focus really works, you are going to have to skip reading Hacker News while your tests run.