---
title: "Guest Post: High Quality React apps with Nx & Cypress"
url: "https://www.cypress.io/blog/high-quality-react-apps-with-nx-cypress"
description: "It all started with sprinkling some jQuery here and there to make our server-side rendered pages more dynamic and appealing. Since then we’ve come a long way."
---

# Guest Post: High Quality React apps with Nx & Cypress

April 14, 2020

By Guest

[_Juri Strumpflohner_](https://twitter.com/juristr?ref=cypress-io.ghost.io) _is a passionate software developer, most recently focusing on the JavaScript ecosystem. He’s a Google Developers Expert in Web Technologies, regularly speaks at conferences, meetups, teaches at_ [_Egghead.io_](https://egghead.io/instructors/juri-strumpflohner?ref=cypress-io.ghost.io) _or writes articles such as this one. Currently Juri works as a JavaScript Architect at_ [_Nrwl_](https://nrwl.io/?ref=cypress-io.ghost.io) _where he helps clients build high-quality software with monorepos and Nx. You cand find him on_ [_Twitter_](https://twitter.com/juristr?ref=cypress-io.ghost.io) _or his_ [_personal blog_](https://juristr.com/blog?ref=cypress-io.ghost.io)_._

* * *

It all started with sprinkling some jQuery here and there to make our server-side rendered pages more dynamic and appealing. Since then we’ve come a long way. Nowadays, entire platforms are being built on the frontend, with JavaScript/TypeScript and your framework of choice. Those are no more only simple web pages, but rather sophisticated, feature-rich applications built for the browser.

As a result, we need to have a different approach to developing such software. Also on the frontend we need to think about topics such as application architecture, state management, modularisation, scaling development across multiple teams and most importantly, automation and quality assurance.

  
[Nrwl’s Nx](https://nx.dev/?ref=cypress-io.ghost.io) has been my preferred choice over the last couple of years when it comes to tackling such projects. Nx is a set of extensible dev tools for monorepos. While monorepos have their origin in large enterprises such as Google or Facebook, they’ve recently become more and more popular also for smaller projects. Why? Increased team velocity and less code/version management overhead. By having all the relevant code co-located in one git repository, it is easy to make project-wide refactoring, implement cross-project features, which otherwise would be much more tedious and time consuming. Monorepos also come with a cost though which is why you need great tooling to support you! And this is where Nx and Cypress come into play.

## React App with Cypress tests in under a minute

_\*\* = npm installs excluded_ 😉

Nx supports Angular, React and Node out of the box and is potentially open to other frameworks via [its plugin system](https://nx.dev/web/guides/nx-plugin?ref=cypress-io.ghost.io). You can even have multiple different types of projects in the same workspace. But for now, going forward we’ll use use React as an example.

To get started let’s create a new workspace:  
`$ npx create-nx-workspace mynxworkspace`

After the workspace has been initialized, you’ll see a series preconfigured setups to choose from. We’ll choose React for this article:

The wizard continues asking for a couple of configs and workspace settings, like the app name to be generated, the styling framework to use etc. After that we should get the following Nx workspace:

_Cypress e2e setup generated for our_ `myfirstreactapp`

Nx workspaces are structured into two main categories: apps & libs. As you can see we have the `myfirstreactapp` generated in the `apps` folder, while the `libs` folder is still empty. Notice the `myfirstreactapp-e2e`. That’s a fully functional Cypress setup to test our `myfirstreactapp`.

Let’s launch the app with  
`$ nx run myfirstreactapp:serve`

or simply  
`$ npm start`

as `myfirstreactapp` is the default project.

_The generated React app in action_

If we open the `myfirstreactapp-e2e` folder, we se a fully functional Cypress setup with a pre-generated `app.spec.ts` Cypress test.

_Nx generates an e2e setup for each app with a first spec for the app_

These Cypress tests can simply be executed with  
`$ nx run myfirstreactapp-e2e:e2e`

To run them in watch mode, simply append `--watch` to it and you’ll get the Cypress test runner we all learned to love ☺️

_Cypress e2e tests in action_

## Cypress Code completion thanks to TypeScript

Nx loves TypeScript! Thus, all projects and Cypress tests are generated and preconfigured to use TypeScript. No more guessing, but rather code completion for Cypress commands.

_Full autocomplete support_

## Sharing Cypress Commands across apps & libs

If you haven’t checked out the [Cypress Best Practices](https://docs.cypress.io/guides/references/best-practices.html?ref=cypress-io.ghost.io) page, you definitely should. It is the first thing I suggest people to go read. Especially when it comes to [selecting elements](https://docs.cypress.io/guides/references/best-practices.html?ref=cypress-io.ghost.io#Selecting-Elements), which - if done wrongly - can lead to very fragile tests.

_From the Cypress Best Practices docs_

Hence, rather than writing a selector like..  
`cy.get('h1')_.contains_('Welcome to myfirstreactapp!');`

..I add a `data-cy` selector on the element I’d like to test. So in my `app.tsx` component, let’s add `data-cy="page-title"`

In our `app.spec.ts` we can then use the following selector:  
`cy_.get_('[data-cy="page-title"]')_.contains_('Welcome to myfirstreactapp!');`

Always writing the entire `..get('[data-cy…` selector is repetitive, might be error prone and tedious. A perfect case for making it become a [custom Cypress command](https://docs.cypress.io/api/cypress-api/custom-commands.html?ref=cypress-io.ghost.io). Normally you would simply place them in Cypress’s `support/commands.ts` file but since an Nx workspace might potentially host multiple apps and libraries and thus have multiple Cypress based setups as well, I definitely want to share these Cypress commands among these.

That’s where Nx libs come into play. Libs are where most of the work happens. It is where you implement the domain/business features and import them into one or even multiple apps. Let’s create a library called `e2e-utils` and place it under a `shared` folder.

`$ nx generate @nrwl/workspace:library --name=e2e-utils --directory=shared`

We generate a `@nrwl/workspace` library, which is a plain TypeScript library since we won’t need any React specific things in there. Note, you don’t have to know all these commands by heart. If you happen to use Visual Studio Code, you can install [NxConsole](https://nx.dev/nx-console?ref=cypress-io.ghost.io) which provides a nice UI driven approach for generating new libraries.

_NxConsole extension for VSCode to generate new libraries_

In the newly generated `libs/shared/e2e-utils` library, we create a new folder `commands` and an according `index.ts` inside it. We use that file to host our [custom Cypress commands](https://docs.cypress.io/api/cypress-api/custom-commands.html?ref=cypress-io.ghost.io) that should be shared with the entire workspace.

Copy the following into your `commands/index.ts` file:

```typescript
/// <reference types="Cypress" />
declare namespace Cypress {  
  interface Chainable<Subject = any> {    
    getEl<E extends Node = HTMLElement>(      
      identifier: string    
    ): Chainable<JQuery<E>>;  
  }
}

Cypress.Commands.add(  
  'getEl',  
  { prevSubject: 'optional' },  
  (subject: Cypress.Chainable, identifier: string) => {    
    if (subject) {      
      return subject.find(`[data-cy="${identifier}"]`);    
    } else {      
      return cy.get(`[data-cy="${identifier}"]`);    
    }  
  }
);
```

As you can see, we extend the `cy` object with a new function `getEl` that automatically uses the `data-cy` attribute.Let’s also export the file from our library, by adding the following to the `libs/shared/e2e-utils/src/index.ts`:

`_import_ './lib/commands';`

At this point we’re able to import it in our e2e tests for the `myfirstreactapp`  app. Open `myfirstreactapp-e2e/src/support/index.ts` and import it accordingly:

> Note, Nx uses path mappings (in the `tsconfig.json`) to map our library exports to a proper scoped name `@mynxworkspace/shared/e2e-utils`. That would potentially even allow us to publish our library and import it via NPM.

Finally we can refactor our `app.spec.ts` to use the new `cy.getEl(…)` function:

```javascript
cy.getEl('page-title').contains('Welcome to myfirstreactapp!');
// cy.get('[data-cy="page-title"]').contains('Welcome to myfirstreactapp!');
```

With this setup, it is easy to place shareable commands into the `e2e-utils` library and they’ll be ready to be used across the various Cypress setups in your workspace.

# Cypress based component tests with Storybook

I love to use [Storybook](https://storybook.js.org/?ref=cypress-io.ghost.io) when creating shared UI components. It gives developers an easy way to visually test their components and fellow team members to check out what’s available. In an Nx workspace this makes even more sense because you might potentially have multiple teams working on it.

Storybook allows us to develop a component in isolation and provides an excellent documentation for UI components. Wouldn’t it be cool to also automatically test those Storybook’s with Cypress? Luckily Nx has got your back here as well.

To get started, let’s generate a React component library:  
`$ nx generate @nrwl/react:library --name=greeter --directory=shared --style=scss`

This should generate a new React library under `shared/greeter`:

The component - intentionally - is super simple:

```typescript
import React from 'react';
import './shared-greeter.scss';
export interface SharedGreeterProps {  
  name: string;
}
export const SharedGreeter = (props: SharedGreeterProps) => {  
  return (    
    <div>      
      <h1>Hi there, {props.name}</h1>    
    </div>  
  );
};
export default SharedGreeter;
```

As the next step, let’s add Storybook support, first of all, installing Nrwl’s Storybook dependency:  
`$ npm i @nrwl/storybook --save-dev`

Next we can again use one of the Nx code generators (called schematics) to generate the storybook configuration for our `greeter` component library:  
`$ nx generate @nrwl/react:storybook-configuration --name=shared-greeter --configureCypress`

Note the `--configureCypress`! The above command generates the storybook configuration for our greeter library, as well as a `shared-greeter-e2e` Cypress setup  

Before running the Storybook we have to add a story for our greeter component. At the same level as our `shared-greeter.tsx` create a new file `shared-greeter.stories.tsx` with the following content:

```typescript
import { text } from '@storybook/addon-knobs';
import React from 'react';
import { SharedGreeter, SharedGreeterProps } from './shared-greeter';

export default {  
  component: SharedGreeter,  
  title: 'Shared Greeter'
};

export const primary = () => {  
  const sharedGreeterProps: SharedGreeterProps = {    
    personName: text('Person Name', 'Juri')  
  };  
  return <SharedGreeter personName={sharedGreeterProps.personName} />;
};
```

Then we can run it with:  
`$ nx run shared-greeter:storybook`

There’s one interesting property of Storybook. You can navigate to `/iframe.html` and and control it via the URL. In our case, the story id would be `shared-greeter--primary` and we can control the “Person Name” via the knob-Person Name query param.  
  
For example:  
`/iframe.html?id=shared-greeter--primary&knob-Person Name=Juri`

We can leverage this knowledge in our Cypress tests! By having provided `--configureCypress` when adding the Storybook configuration to our libary, Nx has automatically generated a Cypress setup for it. Open the `apps/shared-greeter-e2e` project and create a new test `greeter.spec.ts` inside the `integration` folder (create it if it isn’t there).

```javascript
describe('greeter component', () => {  

  it('should display greeting message', () => {   
    cy.visit('/iframe.html?id=shared-greeter--primary&knob-Person Name=Juri');    
    cy.getEl('greeting').contains('Hi there, Juri!');  
  });  
  
  it('should display the person name properly', () => { 
    cy.visit('/iframe.html?id=shared-greeter--primary&knob-Person Name=John');    
    cy.getEl('greeting').contains('Hi there, John!');  
  });
  
});
```

> Note, following best practices, similarly as in the previous section, we add the `data-cy="greeting"`  onto our  `SharedGreeter` component and import our shared `e2e-utils` Cypress commands.

From within the Cypress test we exercise our Story with different inputs and see whether our component reacts properly.

We can run the tests in the same way we did for the app previously, but now obviously passing our library project (feel free to pass `--watch` as a param):  
`$ nx run shared-greeter-e2e:e2e`

> Also, check out Isaac Mann’s talk about “[E2E Testing at Half the Cost](https://youtu.be/C88th0SbepE?ref=cypress-io.ghost.io)” which talks about Cypress and Storybook integration.

### Running on CI

Automated tests are only useful if you can run them in an automated fashion on your CI server. Cypress has already an in-depth guide on [Continuous Integration](https://docs.cypress.io/guides/guides/continuous-integration.html?ref=cypress-io.ghost.io) which is especially helpful to configure your CI environment to be able to run Cypress tests. Nx is fully optimized to be able to run in CI environments as well. As such it comes with a series of so-called “affected” commands. Internally Nx builds a graph of the workspace apps and libraries. You can generate it by running `npm run dep-graph`. Right now the graph looks as follows:

_Nx dependency graph for our sample workspace_

Let’s create another react app and import the `SharedGreeter` component. The graph changes to the following:  

We also get a Cypress test setup for our 2nd react app, which also happens to import our greeter component. In a normal workspace, CI would run all of the tests. Clearly as our app grows (in particular in a monorepo scenario) this is not scalable. Nx however, is able to use this graph to calculate the libraries that have been touched and thus only run the necessary tests. Assume someone creates a PR, changing the SharedGreeter component.  
  
In such scenario, running  
`$ npm run affected:e2e`

...would only execute the Cypress tests for our GreeterComponent as well as of `my2ndreactapp` as they might both potentially be affected by the change. Running `npm run affected:dep-graph` visualizes this:

This greatly improves the running time and helps to avoid unnecessarily executing commands for libraries/apps that are not affected by the changes.  
Note this doesn’t only apply to e2e tests, but also to unit tests, linting and building.

### More speed: never test the same code twice, with Nx Cloud

Nx’s affected commands already help a lot to reduce CI time. But still, based on your changes & workspace library structure, you might still end up affecting a lot of libraries and thus running lots of builds/tests.  
However, you could even improve this further by never running the same command twice. How? With **computation caching**! Starting from v9.2 Nx has a built-in computation caching mechanism. Whenever your run a command, Nx analyzes the involved source files and configuration and caches the result. If you happen to run the same command again, without any changes to your src files, Nx simply outputs the previous result from the cache. You can [read more about it here](https://blog.nrwl.io/how-to-never-build-or-test-the-same-code-twice-2dc58e413279?source=collection_home---4------0-----------------------).

This greatly speeds up your local runs. But you can even make this cache distributed and remote by subscribing and configuring [Nx Cloud](https://nx.app/?ref=cypress-io.ghost.io). That way you can share your cache with co-workers or your CI server.Nx Cloud keeps track of all the executed commands, indexes the involved environment and library contents as well as the execution result. Whenever some of your work mates executed a particular set of Cypress tests and you happen to run them as well, instead of loosing precious time, waiting for the test run to finish,  you’ll get the cached result from your co-worker.

This also works for CI! Here’s what it looks like when the build pipeline has already executed the tests and I re-run them locally again on my machine:

> Q: How long does it take to run 42 [@Cypress\_io](https://twitter.com/Cypress_io?ref_src=twsrc%5Etfw&ref=cypress-io.ghost.io) e2e tests with [@NxDevTools](https://twitter.com/NxDevTools?ref_src=twsrc%5Etfw&ref=cypress-io.ghost.io)?  
>   
> 🔥🔥🔥 0 seconds!! 🔥🔥🔥  
>   
> 🧐 How? With [https://t.co/moab7DVk0j](https://t.co/moab7DVk0j?ref=cypress-io.ghost.io) & [https://t.co/bfu8lRl6nv](https://t.co/bfu8lRl6nv?ref=cypress-io.ghost.io)'s distributed caching 😜😎  
>   
> Request an invite on [https://t.co/bfu8lRl6nv](https://t.co/bfu8lRl6nv?ref=cypress-io.ghost.io)![#cypress](https://twitter.com/hashtag/cypress?src=hash&ref_src=twsrc%5Etfw&ref=cypress-io.ghost.io) [#angular](https://twitter.com/hashtag/angular?src=hash&ref_src=twsrc%5Etfw&ref=cypress-io.ghost.io) [#reactjs](https://twitter.com/hashtag/reactjs?src=hash&ref_src=twsrc%5Etfw&ref=cypress-io.ghost.io) [pic.twitter.com/izOkzPzQ74](https://t.co/izOkzPzQ74?ref=cypress-io.ghost.io)
> 
> — Juri Strumpflohner (@juristr) [March 18, 2020](https://twitter.com/juristr/status/1240393333610024965?ref_src=twsrc%5Etfw&ref=cypress-io.ghost.io)

All this doesn’t need any particular configuration but can lead to significant time savings. Here’s a graph of running Cypress e2e tests on CI. On day 18 Nx Cloud has been activated, immediately leading to drastic time savings from around ~30 minutes down to ~15 minutes in a couple of days.

Curious? Get access to Nx Cloud on [https://nx.app](https://nx.app/?ref=cypress-io.ghost.io) and make your Cypress tests blazingly fast!

### Conclusion

In this article we learned about how we can leverage Nx together with Cypress to automate our testing setup. We’ve seen

-   how to setup a new React based workspace with Cypress e2e tests for our apps
-   how to generate Nx libraries with Storybook support
-   how to share custom Cypress commands
-   how to leverage Storybook to create Cypress based tests for our React components
-   how TypeScript can help explore the Cypress API via code completion support
-   how to speed up Cypress test runs with Nx’s affected commands
-   how to never run Cypress tests twice with the support of Nx Cloud

You can check out the source code used in this article at [https://github.com/juristr/nx-react-cypress-blogpost](https://github.com/juristr/nx-react-cypress-blogpost?ref=cypress-io.ghost.io).

* * *

_Update as of 5/15/20: nx v9.3 has been released, with further improvements and updates. See below for a video walkthrough of the topics discussed in this article:_