Test Driven Form Development with Cypress

Jack Cross
Engineer at The Palmer Group

Developing forms can be tedious. Form state is lost on page refresh and recreating that state usually involves simply entering each value again. This is doable when there are only one or two inputs, but for larger forms this quickly becomes a time consuming and annoying process.

At The Palmer Group, we recently added a set of forms to a client’s site that included a total of 39 input fields. Rather than repeatedly filling each form out by hand, we decided to take a test driven development approach.

Which tool to use?

The tools we had in mind to help us develop these forms were Cypress and Puppeteer. They both offer major improvements in reliability and developer experience over browser automation tools we had used in the past, as well as the ability to inspect the browser while your tests are running using cypress open or puppeteer.launch({ devtools: true }).

However, Cypress also allows you to time travel through your tests and inspect DOM snapshots before and after each command is run. We decided to use Cypress because of these features and found them to be a huge help while developing.

The Workflow

Now that we had decided on Cypress, the next step was to start writing our tests. The following is an example of a workflow for a basic signup form that we had to develop.

First, we need to write a test that navigates to the application, fills out and submits the form, and checks that a success message is displayed.

describe('Signup', () => {
  it('can sign up', () => {
    // navigate to our application
    // fill out each field
    // submit the form
    // check successfully submitted

In this test we are using getByTestId from Kent Dodd’s cypress-testing-library to select DOM elements by their data-testid attribute.

Running our test for the first time fails because we haven’t actually written any UI yet!

Failing tests

Let’s fix that by writing our signup form. This is an example implementation with first name, last name, email, and password fields that will simply show a success message on submit.

class Signup extends React.Component {
  state = {
    success: false,
  onSubmit = event => {
    this.setState({ success: true });
  render() {
    const { success } = this.state;
    if (success) {
      return <h1 data-testid="success">Success!</h1>;
    return (
        <form onSubmit={this.onSubmit}>
          <label htmlFor="firstName">First Name</label>
          <label htmlFor="lastName">Last Name</label>
          <label htmlFor="email">Email</label>
          <label htmlFor="password">Password</label>
          <input type="password" id="password" data-testid="password" />
          <button type="submit" data-testid="submit">

After saving our form component, our Cypress tests re-run and now they pass 😎.

Passing tests

But perhaps that button would look better if it were tomato colored. Let's update the styles.

<button type="submit" data-testid="submit" style={{ bakcground: 'tomato' }}>

After saving our changes our Cypress tests automatically re-run, but something looks off. Using Cypress' time travel feature to view a previous state of the DOM we can see that for some reason our background style was not added to the button.

Incorrect button color

Ah! It looks like we accidentally spelled it bakcground, so let’s fix that.

<button type="submit" data-testid="submit" style={{ background: 'tomato' }}>

When our tests run again, we see that the button color is now tomato.

Correct button color

🍅 👌


Although this example workflow is a bit contrived, we use this method of form development for all the forms we create at The Palmer Group. It helps to speed up development, catch more regressions, and keep us sane.

For more details about writing Cypress tests, check out their getting started guide.

Jack Cross
Engineer at The Palmer Group
Copyright © 2018 The Palmer Group. All Rights Reserved.