Searching the DOM: Decoupling Complexion from Dissection

A large part of UI testing resolves around finding things. DOM elements, to be exact.

Let’s take the example of writing an acceptance test for a user registration page containing a form that requires typing the same password twice in order to submit.

A common instinct — even among those of us who try to follow Test-Driven Development — is to implement the form first, generate an acceptance test by running ember g acceptance-test registration , look at the markup in our registration.hbs template, and then write something like this:

While a test like this might pass initially, it’s very susceptible to breaking later for reasons having nothing to do with password confirmation validation:

A designer decides to change the button text from ‘Submit’ to ‘Register’.

A CSS-savvy developer decides to remove the .username , .password , and .confirm-password classes because they’re all just .text-input s.

, , and classes because they’re all just s. The development team decides to adopt BEM for their CSS, and .form-field-errors becomes .form-field--has-errors .

becomes . The company decides to require all user passwords to have at least one special character, making the number of errors after validation equal to 2.

With these possibilities in mind, to make our selector queries more maintainable, we need an approach that combines a surgeon’s depth with a radiologist’s breadth.

We need Ember Test Selectors.

Embracing a convention commonly used on QA-heavy teams, Ember Test Selectors allows us to leverage the power of HTML attributes by attaching any attribute prefaced with data-test- to an element, and then finding it later by passing that attribute’s suffix to testSelector helper:

Less-brittle selector queries with ember-test-selectors

Most crucially, this allows us to keep our selector queries data-driven — data-test-submit-button will always be an attribute of our submit button, whether its text is “Submit” or “Register”. At the same time, we can assign values to our data-test- attributes and pass them later as a second argument to the testSelector helper. From the snippet above…

find(testSelector('form-field-error-message', 'password-confirmation'));

…allows us to precisely target an element with a data-test-form-field-error-message attribute equaling password-confirmation — without needing to know how many other errors are present or what the exact message text contains.

If you’re still on the fence about using Ember Test Selectors, consider also the fact that it offers built-in binding for data-test- attributes on components, and the ability to strip data-test- attributes from production build.

For example:

{{comments-list data-test-comments-for=post.id}}

gives us something like:

<div id="ember123" data-test-comments-for="42">

<!-- comments -->

</div>

which ships to the end user as:

<div id="ember123">

<!-- comments -->

</div>

Achieving this level of utility with a hand-rolled implementation would be a heavy maintenance burden to say the least. But with Ember Test Selectors, it’s never a concern.

If you’re interested in learning more about using Ember Test Selectors, I’d highly recommend this walk-through from EmberMap as a good compliment to the project’s own excellent documentation.

DOM API: The Web’s OG

Another coupling in the first example above that’s a bit more subtle, but still important to point out: a reliance on jQuery:

assert.equal(find('.error-text').text().trim(), 'Passwords must match');

I’ll spare my views as to whether or not jQuery itself is a bad thing, but using its selector syntax and its matched-element methods without explicitly importing it — without explicitly saying that you need it and know it will be there — is a recipe for trouble in the future. How, so? As it happens the “future”, pertaining to Ember testing, is coming sooner than you might imagine: The Grand Testing Unification RFC for Ember has gained widespread support (and traction) among the core team, and one of its many proposals is the removal of jQuery from Ember’s built-in test helpers.

Fortunately, that’s where the outstanding Ember Native DOM Helpers project steps in to help. Using its helpers (which currently map to the same acceptance test helpers we’re already used to), we can read elements using standard Element.querySelector syntax — and we can guarantee that the return values are references to real, raw, in-the-flesh elements (you know, these).

ember-test-selectors + ember-native-dom-helpers

Using the native DOM API will help standardize the syntax we use for finding and operating on elements, provide a more standard set of expectations about the behavior of fired events, and reduce the amount of cognitive overhead for developers who may not be familiar with jQuery.

One caveat, however, is that unlike Ember’s built-in acceptance test helpers, the replacements offered through ember-native-dom-helpers don’t automatically wait for the completion of any asynchronous behavior going on under the hood.

With the next pattern at our disposal, however, they won’t have to.