Simplifying Intern tests with Command helpers

By on April 19, 2016 7:21 am

Intern Logo

Intern, via the Leadfoot WebDriver library, provides a lot of low-level control over the browsers it uses to run tests. Tests can navigate to new pages, resize the browser window, examine elements on a page, and interact with controls like inputs and buttons. Unfortunately, with all this power can come great complexity. Many testing tasks will involve a large number of low-level operations and dealing with these can be error prone and make tests difficult to follow. Command helpers to the rescue!

What’s a Command helper?

The term “Command helper” refers to a design pattern for creating reusable modules for Intern functional tests. They’re called Command helpers because they’re built around Leadfoot Command objects (e.g., this.remote in a functional test). The idea is to create functions that execute groups of low-level commands for some higher-level purpose. For example, assume a login form is used by several different applications being tested. The commands in a functional test to fill in and submit the login form, and to verify that the login was successful, might look like the following:

return this.remote
    .get('page.html')

	.findById('username')
	.type('foo')
	.end()

	.findById('password')
	.type('bar')
	.end

	.findById('submit')
	.click()
	.end()

	.findByTag('h1')
	.getVisibleText()
	.then(function (text) {
	    assert.equal(text, 'Welcome, foo!');
	})

We can dramatically simplify the above example by moving the low-level logic into Command helpers.

return this.remote
    .get('page.html')
	.then(login('foo', 'bar'))
	.then(verifyLogin('foo'));

Not only is this code easier to read, but the helper functions allow the login and verification logic to be shared with other test suites.

Writing Command helpers

So, what does a Command helper look like? In general, a Command helper is implemented as a function that returns a function that can be passed to a then call in a Leadfoot Command chain:

function helper() {
    return function () { /* helper code */ }
}

registerSuite({
    test1: function () {
        return this.remote
            .get('page.html')
            .then(helper())
    }
});

To get a better sense of how this works, consider how the first code snippet in the previous section might be transformed into the second using Command helpers. First, we can rewrite the code so that related function calls are grouped into then callbacks:

return this.remote
    .get('page.html')

	// Code to perform login action
	.then(function () {
	    return this.parent
			.findById('username')
			.type('foo')
			.end()

			.findById('password')
			.type('bar')
			.end

			.findById('submit')
			.click();
	})

	// Code to check that the login was successful
	.then(function () {
		return this.parent
			.findByTag('h1')
			.getVisibleText()
			.then(function (text) {
				assert.equal(text, 'Welcome, foo!');
			});
	})

A few details should be noted:

  • The Command chain is built from this.parent rather than a reference to this.remote. This allows the helper to execute in the context of the outer Command chain (the one that called the helper). For example, if the context of the outer Command chain was a particular <table> element on the page, that will be the context of this.parent in the Command chain.
  • While the search context for the this.parent Command chain is initially the same as the context parent chain’s context, changes to the this.parent chain’s context will not affect the parent. This means you don’t need to worry about restoring the search context (by calling end()) at the end of the then callback.
  • The Command chain must be returned by the then callback or the parent Command chain won’t wait for the actions in the then callback to complete.

Next, the code in those callbacks can be moved into separate functions. To keep the functions generic, the username and password can be passed in as configuration parameters. Through the magic of JavaScript closures, the functions that will actually be used as then callbacks (the ones returned by the helper functions) will have access to the configuration parameters when they’re called.

function login(username, password) {
	return function () {
	    return this.parent
			.findById('username')
			.type(username)
			.end()

			.findById('password')
			.type(password)
			.end

			.findById('submit')
			.click();
	}
}

function verifyLogin(username) {
	return function () {
		return this.parent
			.findByTag('h1')
			.getVisibleText()
			.then(function (text) {
				assert.equal(text, 'Welcome, ' + username + '!');
			});
	}
};

registerSuite({
    test1: function () {
        return this.remote
            .get('page.html')
            .then(login('foo', 'bar'))
            .then(verifyLogin('foo'));
    }
});

These examples show only the most basic application of Command helpers. Helpers can be created for much more complex tasks, and can even be grouped into higher-level abstractions such as Page Objects. And Dojo users can leverage our Dijit helper for testing their widgets efficiently. Command helpers are a great example of how Intern makes it easy to write compact and maintainable functional tests!

Enterprise JavaScript Support

Learning more

We cover the creation of functional tests, including writing Command helpers, in our Intern and Dojo 202 workshops, and we support developers worldwide in their efforts with JavaScript and testing. If you would like to discuss how we can help your organization improve their approach to automated testing, please contact us to start the conversation.

Comments