Setting up Storybook Interaction Tests with Nx

Storybook interaction tests allow you to test user interactions within your Storybook stories. It enhances your Storybook setup, ensuring that not only do your components look right, but they also work correctly when interacted with.

You can read more about Storybook interaction tests in the following sections of the Storybook documentation:

Set up Storybook in your workspace

You first need to set up Storybook for your Nx workspace, if you haven't already. You can read the Storybook plugin overview guide to get started.

Setup Storybook Interaction Tests

During the setup, you'll be prompted about setting up interaction tests. Choose yes when asked if you want to set up Storybook interaction tests.

The --interactionTests flag is true by default, so when you're setting up Storybook, you can just press Enter to accept the default value, or even pass the flag directly. Make sure to use the framework-specific generators, so that you also get your stories set up correctly:

nx g @nx/angular:storybook-configuration project-name --interactionTests=true

This command will:

  • Set up Storybook for your project - including the @storybook/addon-interactions addon.
  • Add a play function to your stories.
  • Install the necessary dependencies.
  • Infer the task test-storybook for the project, which has a command to invoke the Storybook test runner.
Using explicit tasks

If you're on an Nx version lower than 18 or have opted out of using inferred tasks, the test-storybook target will be explicitly defined in the project's project.json file.

Writing an Interaction Test

The Nx generator will create a very simple interaction test for you. You can find it in the .stories.ts file for your component. This test will only be asserting if some text exists in your component. You can modify it to suit your needs.

Let's take an example of a simple React component for a button:

1import React, { useState } from 'react'; 2 3export function Button() { 4 const [count, setCount] = useState(0); 5 6 const handleClick = () => { 7 setCount(count + 1); 8 }; 9 10 return ( 11 <button role="button" onClick={handleClick}> 12 You've clicked me {count} times 13 </button> 14 ); 15} 16 17export default Button; 18

In your .stories.ts file for that component, you can use the play function to simulate interactions. For example:

1export const ButtonClicked: Story = { 2 play: async ({ canvasElement }) => { 3 const canvas = within(canvasElement); 4 const button = canvas.getByRole('button'); 5 await userEvent.click(button); 6 expect(canvas.getByRole('button').innerText).toBe( 7 "You've clicked me 1 times" 8 ); 9 await userEvent.click(button); 10 expect(canvas.getByRole('button').innerText).toBe( 11 "You've clicked me 2 times" 12 ); 13 }, 14}; 15

Here, the play function is simulating a click on the button using the userEvent.click method. It is then asserting that the button's text has changed to reflect the number of times it has been clicked.

You can read more about how to write interaction tests in the Storybook documentation.

Running Interaction Tests

To run the interaction tests, you can use the test-storybook target that was generated for your project:

nx test-storybook project-name

Make sure you have Storybook running in another tab, so that the test runner can connect to it:

nx storybook project-name

Interaction Tests vs. Cypress E2E Tests

As the landscape of testing has evolves, we are also adjusting our approach to testing in Nx, and the Storybook generators. Nx has been consistently suggesting the use of Cypress for e2e tests in conjunction with Storybook. This approach, while effective, had its own challenges. A new, separate e2e project would be generated, and setup specifically to work with Storybook, leading to a bifurcated setup: your component stories in one place, and the e2e tests that validated them in another.

However, with the introduction of Storybook Interaction Tests, this approach can be simplified. This new feature allows developers to integrate tests directly within the stories they are already creating, making the development and testing process more straightforward. Why maintain two separate projects or setups when you can do everything in one place?

Choosing to promote Storybook Interaction Tests over the previous Cypress e2e setup was not a decision made lightly. Here's why we believe it's a beneficial shift:

  • Unified Workflow: Interaction tests allow developers to keep their focus on one tool. Instead of juggling between Storybook and a separate e2e project, everything is integrated. This means quicker iterations and a simplified dev experience.

  • Leaner Project Structure: Avoiding the need to generate a separate e2e project means fewer files, less configuration, and a more straightforward project structure. This can make onboarding new developers or navigating the codebase simpler.

  • Optimized Performance: Interaction tests are lightweight and quick to execute compared to traditional e2e tests. They run directly in the context of the component story, ensuring that tests are precise and fast.

  • Consistency: Keeping the stories and their associated tests together ensures a tighter bond between what's developed and what's tested. It reduces the chances of tests becoming outdated or misaligned with the component's actual behavior.

Because of these benefits, it made sense for our Storybook configuration generator to switch from the Cypress e2e + Storybook combination to the integrated approach of Storybook Interaction Tests. By integrating e2e-like tests into the existing Storybook setup, we offer developers a smoother, more efficient, and simpler setup and testing experience.