Do not write Storybook tests manually, do this instead!

Date

Storybook is one of the best tools for building client components. It allows us to create UI in isolation, document it, and even cover it with automated tests.

I explained why automated tests are worth pursuing in Why You MUST Have Automated Tests. In this post, we'll go deeper into how to write them efficiently with Storybook.

Let's write a test

We have a form with an email, password fields and a submit button. The user enters their credentials, submits the form and sees the confirmation message. Here is what the form looks like: Form

And this is the HTML structure of the form:

<form> <label for="email">Email</label> <input id="email" type="email" name="email" /> <label for="password">Password</label> <input id="password" type="password" name="password" /> <button type="submit">Submit</button> </form>

How to test?

Here are the steps we need to take to write a test for this story:

  • Pick an element to interact with.
  • Find a user-friendly selector for it (e.g. by aria-role, label, placeholder, text, title, test-id) according to Testing Library's guiding principles.
  • Write the interaction code (e.g. userEvent.click(selector))
  • Return to step 1 to perform the next interaction until the test is ready.

The actual test

There are four elements to interact with. Here is how we can do it:

  • Email and password:
    • Both fields use an input tag, which translates to role=textbox in Testing Library. They also have a <label>, which we will use to narrow the element selection.
    • We will click on the field to focus it and then type in the value.
  • The submit button is a button element, which translates to role=button in Testing Library. The text on the button is Submit, we will also use it to narrow down the selection.
  • The confirmation message is a simple text, which we can select by its content.

With all that said, we get the code like this:

play: async ({ canvasElement }) => { const canvas = within(canvasElement); // Click and type in the email field await userEvent.click(canvas.getByRole('textbox', { name: 'Email' })); await userEvent.type(canvas.getByRole('textbox', { name: 'Email' }), 'example@gmail.com'); // Click and type in the password field await userEvent.click(canvas.getByRole('textbox', { name: 'Password' })); await userEvent.type(canvas.getByRole('textbox', { name: 'Password' }), 'secret-password'); // Submit the form await userEvent.click(canvas.getByRole('button', { name: 'Submit' })); // Assert that the confirmation message is displayed expect(canvas.getByText('Form submitted successfully')).toBeInTheDocument(); }

It does the job, but writing it is a lot of work. What if there are more steps to take? What if the form is more complex?

Making it easier

What if all of this was done for you? Presenting: Storybook Test Codegen Addon.

With this addon, simply turn on the recording and interact with your stories. And the addon will generate a test code for you!

Storybook Test Codegen

How does it work?

Whenever you interact with the story in recording mode, the addon determines the type of interaction and target element.

Only interactions that can be re-created using Testing Library are recorded, such as click, double click, type, and keydown (for enter and shift keys). The other interactions are ignored.

As for the target element, the algorithm prioritises aria-role, label, placeholder, text, title, and test-id and falls back to CSS selectors if none of the above can be used. (once again, according to Testing Library's guiding principles)

Do I just copy the code as is?

While the idea of the library is that it should be enough to copy the code as is, you may still want to make some changes, such as adding assertions and reformatting the code.

How do we assert dynamic elements?

If you want to assert that the element is displayed, click on it during the recording.

Then, when the test is generated, you can change the code from await user event.click(...) to await expect(...).toBeInTheDocument() as long as the test still works as expected, and this click wasn't a required interaction. Add expect before or after the click if the interaction was needed.

Lastly

Writing tests is crucial for stable software. But it takes time. To keep up with the pace of development, we need to automate this process as much as possible.

That is where Storybook Test Codegen comes in handy. Give it a try and let me know what you think!

Useful resources: