Testing HTML forms is a pain! Tools can help

nolan

Posted by nolan

django

Confession time. As a software engineer, I hate forms.

It's true and it's a shame. Forms are fundamentally what make a web application interactive. They are often the sole means of communication between user and administrator, and in my opinion, nearly all great web apps acheive their greatness in large part through intuitive design of the user's experience using...forms.

However fundamental, modern forms are markedly different than their forebears. They are often enhanced with client-side logic with the aim of transforming the process of data collection into a conversation between software and human. What could formerly have been an overwhelming sea of input fields followed by a button stamped with a word of frightening obeisance and finality, "Submit", becomes a multi-step dialogue wherein requests for related data are grouped together, page-by-page, with validation checks and often times actual data submission, performed invisibly via AJAX requests, without a jarring page refresh.

This is all wonderful of course. Indeed, it is user-focused design. It is not developer-focused design. When building such a form, rather than whipping up a line containing post parameters to feed into curl, or continually resubmitting a POST request in the browser to see what comes back, I find myself instead having to trudge through the impossibly tedious job of typing valid words and numbers one at a time into forms.

Jobs like this are notoriously prone to half-assery. Often times, the data in my developing application look like this:

Name: asdf
Email: a@a.com
Password: asdfasdf

Yeah, that will pass the validator check. But it sure looks dumb and is really not indicative of the real-world, user-submitted data that my application will (hopefully) deal in someday. Suppose a certain user with the email apu.nahasapeemapetilon@kwikemart.com comes along and jumps over the right wall of my fancy email input field. I or a teammate probably would've caught that at some point in the QA process, but there's no way it's going to happen in development with me pounding away at a keyboard for minutes on end every time I want to simply confirm that data capture works.

Happily, I've overcome this by using (and then authoring) tools that take on the burden of filling out forms for me. For a while, I was using an extension for Chrome by Hussein Shabbir. It provides a convenient button at the top of a browser window that, when clicked, dutifully seeks out all form inputs on the current page, and drops dummy data into them. The killer feature of this extension, and the one that ultimately inspired me to lay down some code of my own, is the ability to populate fields with a special subset of potential dummy data based on regular expression matches on the field's underlying html.

Say, for example, I'd like to always use a word that is a plausible first name in a field who's name attribute includes the string "firstname". The extension allows me to create a custom rule that states that when the name attribute (or class attr, id attr, the tag's label text, or a combination of any) matches "firstname", a name randomly picked from a set of first names defined by the extension (or by me if I so choose) is dropped into the field.

Shabbir's extension contains many human-specific datasets to pick from for many data types including:

  • Names
  • Email addresses
  • Telephone numbers
  • Organizations
  • and more, including a neat ability to return a string generated by a reverse regex generator.

This got me along pretty far in my workflow until some of the shortcomings started to show through. Chief among them is that there are so many more types of data I routinely would like to fill from, and I would like to be able to choose, with greater control, the criteria that determines when and how input fields are filled via that data. What I wanted was a tool that expanded on Shabbir's extensions' sensible default behavior of picking an item from data type-specific randomness, and combined it with the means of pinpointing an input tag or tags and fine-tuning that filling behavior for whatever a particular situation called for.

To address the first requirement, I searched projects with the goal of finding one that could easily be dropped into an html document or JavaScript application, and then spit out any number of random results on-demand, based on a given data type. I found Chance.js, a self-contained JavaScript library that does just that and boasts an expansive range of generators including ones for personal data (names, ages, birthdates), location data (addresses, coordinates, geohashes), credit card data (by type), and more. Chance's API is stupid-simple too. It just requires calling methods on the Chance object with an optional keyword parameter object, and snatching up the randomly returned item.

Being a web developer with experience querying the DOM on a regular basis, I pretty much had all the tools I needed to meet the second requirement. Say the form I'm testing contains two input fields of type "email". I'd like the sender field to be filled with a random, valid email address, but I need the receiver field to always be filled with my address, nolan@fusionbox.com. Using jQuery, I would select the tags with something like:

1
2
$('input[type="email"][name="sender"]');
$('input[type="email"][name="receiver"]');

and respectively fill each input with:

1
2
Chance.email()  // a random email address
"nolan@fusionbox.com"

Since querying the DOM with selectors was essential to my needs, it made sense that whatever solution I landed on would somehow use jQuery and as a bonus, could piggyback on the libary's .val() mechanism for getting/setting a value on an form field element.

I wrote a jQuery extension called jQuery Chancyforms that provides a convenience method for filling a field (or an entire form of fields) with datatype-appropriate values selected randomly from Chance.js, or additionally from datasets you provide, or data-returning functions you provide. Let me show you how it works.

At its simplest, fill every field in every form tag.

1
$('form').fill();

Every input, select and textarea tag contained in a form will be filled with random values.
For input tags of a type other than checkbox, radio, and range, a value from Chance.js will be used.

  • <input type="email"> with Chance.email()
  • <input type="tel"> with Chance.phone()
  • <input type="number"> with Chance.natural()
  • <input type="color"> with Chance.color({format: 'hex'})
  • Many more. See Docs

Radio input tag groups will have one of their tags checked at random, and Checkbox groups will check one or more tags.
Select tags will have one of their options selected, and select tags with the multiple attribute present will select one or more options.
Range input tags will have a value set within the min and max, adhering to the step attribute if it is set.

Use selectors to target subsets of form fields to fill:

1
$('input[type=text]').fill();

Now, we can build on this to create a rule to designate the dataset used to fill a specific input:

1
2
$('input[type=text].firstname').chance('first');
$('input[type=text].firstname').fill();  // sets input's value to a random first name

That call to .chance() with 'first' as the parameter tells Chancyforms that each time it performs a .fill() operation, it must use Chance.first() (the first name method) and fill the input's value with the return value.

We could also pass additional parameters used by Chance.first():

1
2
$('input[type=text].firstname').chance('first', {gender: 'female'});
$('input[type=text].firstname').fill();  // sets input's value to a random feminine first name

The actions above would only permit the input be filled with a value provided it is empty at the time the .fill() call is made. We can change that behavior by passing an options object at the first parameter to .chance():

1
2
3
$('input[type=text].firstname').chance({overwrite: true}, 'first');
$('input[type=text].firstname').fill();  // sets input's value to a random first name
$('input[type=text].firstname').fill();  // filled with a new first name

If we want this overwriting to be the default behavior for all .fill() calls, we could set that in the jQuery extension itself:

1
$.fn.chance.defaults.overwrite = true;

We could temporarily clobber the default, or set option for a selection by passing the options object to the .fill() method instead:

1
2
3
4
$('input[type=text].firstname').chance({overwrite: true}, 'first');
$('input[type=text].firstname').fill();  // filled with a new first name
$('input[type=text].firstname').fill({overwrite: false});  // will not overwrite the input's value
$('input[type=text].firstname').fill();  // back to filling with a new first name

Now lets fill with an item picked from an array of names we have specified:

1
2
$('input[type=text].firstname').chance(['Bart', 'Lisa', 'Maggie']);
$('input[type=text].firstname').fill();  // sets input's value to one of the three above

How about filling with a bit of logic we provide?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function isHomer() {
    if (user.isHomer) {
        return 'Homer';
    } else {
        return Chance.pick(['Bart', 'Lisa', 'Maggie']);
    }
}
$('input[type=text].firstname').chance(isHomer);
$('input[type=text].firstname').fill();
// sets input's value to 'Homer' or one of 'Bart', 'Lisa', or 'Maggie' based on our condition

With a rudimentary understanding of DOM selectors and the Chance.js API, jQuery Chancyforms is super-versatile. However, its existence as a JavaScript library is admittedly perplexing. It ultimately was created to facilitate application testing and QA procedures. How many developers or QA testers want to include a script tag in a document or require an extra module for that purpose?

That's why I wrapped the library up into a browser extension (currently early in development and solely for Chrome) uncreatively named Form Filler. Chancyforms will really shine when it is a click away in a browser window, ready to be used on any page.

You can install it now for use in its minimally viable state provided you can run node/npm on your system.

1
2
3
4
git clone https://github.com/nolsto/form-filler
cd form-filler
npm install
./node_modules/.bin/webpack

Then, In a Chrome window,

  1. navigate to chrome://extensions
  2. select Load unpacked extension...
  3. find and select the directory cloned from git (form-filler)

An extension button will be placed at the top of the browser. Click it to fill all form fields on any given page.

Admittedly, the browser extension as it now stands is not as powerful as the JS library that underlies it and nowhere near as customizable as Hussein Shabbir’s extension. The plan going forward is to build an options page UI that provides users the ability to create their own selector-based rules and datasets from which to fill form fields. These rules could be applicable globally, or additionally, specific to domains or pages. I envision a user would add rules for general situations, such as the above example of a first name input tag rule. Users would then also create test-specific rules for a given form in a developing application.

Sound good? If you’re like me and testing form-driven user workflows drives you nuts, and you’re tired of tediously mashing your keyboard every few minutes, stay tuned as I hope to stretch this work into an essential developer tool in the near-future.

Return to Articles & Guides