Share Cypress Commands in an Nx Workspace
Tags: react, angular, nx, cypress
Published: 2022-04-13 20:03:28

---
title: Share Cypress Commands in an Nx Workspace
canonical_url: 'https://www.cypress.io/blog/2022/04/13/share-cypress-commands-in-an-nx-workspace/'
published: true
description: Learn how easily you can share Cypress custom commands in an Nx workspace
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/taklpybm6f9uwrpjzk0a.png
tags:
  - React
  - Angular
  - Nx
  - Cypress
---

[Cypress](https://www.cypress.io/) has revolutionized e2e testing. It successfully addressed many pain points developers had specifically with e2e testing solutions. In this article, we’re going to see how to bring that to the next level by learning how to use Cypress in an [Nx](https://nx.dev) workspace, in particular how to share common [Cypress custom commands](https://docs.cypress.io/api/cypress-api/custom-commands) in an Nx monorepo.


## Nx ❤️ Cypress

One of the big advantages of using Nx is that it sets your development environment up with some best practices tools, taking away all the burden of dealing with configurations, reading through docs, and setting it up by yourself. 

Whenever generating a new application with Nx, you’ll get

- TypeScript set up and configured;
- Jest set up for unit testing;
- ESLint set up for linting;
- Prettier set up so you don’t have to rant over tabs vs spaces;
- and finally, Cypress set up for e2e testing (and component testing coming soon!)

Every application (unless you actively opt-out) gets a corresponding Cypress setup generated, in the form of `<app-name>-e2e`(you can change that name if you’d like).

This makes Nx pretty appealing as your [default development CLI](https://blog.nrwl.io/the-react-cli-you-always-wanted-but-didnt-know-about-eaa2494aab9e), even if you don’t expect to fully leverage a multi-app Nx monorepo workspace.

## Prefer the Video version?

Here you go! You’re welcome.

{% youtube YNH4wBUZvkA %}

## Setting up a new Nx workspace

If you have already set up a workspace, feel free to skip this section. Nothing new for you expert here 😉. Instead, if you’re not really sure what Nx is all about, please follow along.

Let’s generate a new React-based Nx workspace.

```bash
npx create-nx-workspace nxlovescypress --preset=react --appName=happynrwl
```

This generates a new setup with a React app `happynrwl`. For the styles and [Nx Cloud](https://nx.app) setup, feel free to choose however you like. It is not relevant for this article.

> Note, you can also use an Angular-based Nx workspace setup. It doesn’t really make any difference for this article. You can refer to the GitHub example repo linked at the end of this article, which uses an Nx workspace containing an Angular and React application.
> 

You should end up having a new Nx workspace with the following situation:

![Nx folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o6q8j234v10wgk1jab6o.png)

Let’s explore our setup.

## Running Cypress in an Nx workspace

`happynrwl-e2e` is the Cypress application that got generated for our `happynrwl` React app. 

You can run the Cypress e2e tests headlessly with

```bash
npx nx e2e happynrwl-e2e
```

You can also pass `--watch` to run it interactively with the Cypress test runner such that the tests get re-executed whenever we change our source.

The awesome part of this is that you don’t have to configure anything at all. No need to

- spin up our development server manually first that serves our React app such that we are able to load it in our Cypress tests environment
- set up linting for our e2e project (yes writing good quality test code is just as important)

## Cypress custom commands

In Cypress you typically interact via the global `cy` object, for example you’d write

```jsx
cy.get('[data-testid="some-link"]').click();
```

..to grab some DOM element then interact with it. In this case by clicking the link.

Luckily Cypress is extensible, allowing you to add your own commands to the `cy` object such that they can be easily invoked within your test implementation.

In your Cypress setup, open the `support/commands.ts` file to see an example declaration of such a custom command:

```tsx

// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  interface Chainable<Subject> {
    login(email: string, password: string): void;
  }
}
//
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
  console.log('Custom command example: Login', email, password);
});
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
```

Read more about it on the official Cypress docs: [https://docs.cypress.io/api/cypress-api/custom-commands](https://docs.cypress.io/api/cypress-api/custom-commands).

## Sharing Cypress custom commands

Consider a simple custom Cypress command named `getEl` which follows [Cypress best practice](https://docs.cypress.io/guides/references/best-practices#Selecting-Elements) to grab elements via a dedicated `data-testid` property. 

Here’s what the pure implementation looks like.

```tsx
Cypress.Commands.add('getEl', (identifier: string) => {
  return cy.get(`[data-testid=${identifier}]`);
});
```

Ideally, we want to reuse this type of command across all our e2e tests. Let’s explore how Nx can significantly help us with this endeavor.

### Sharing functionality in an Nx workspace

When you generate a new Nx workspace, you’ll get a setup that includes an `apps` and `libs` folder. This distinction allows us to have a more modular architecture by following a separation of concerns methodology, incentivizing the organization of our source code and logic into smaller, more focused, and highly cohesive units.

This is also where reuse and sharing functionality happens within an Nx workspace. Nx automatically creates TypeScript path mappings in the **`tsconfig.base.json`** file, such that they can be easily consumed by other apps or libs. As such, functionality organized in libraries can be imported as easily as 

```tsx
import { Button } from '@my-organization/ui';
```

No need to rebuild the library, publish it, or whatsoever (although you still can).

In this article we leverage this type of setup, to organize our custom Cypress commands in an Nx library for then being able to reuse, not across other apps or libs, but across all of our, Cypress e2e tests that potentially live in the Nx monorepo workspace.

Read more about the mental model of apps and libs in the official Nx docs: [https://nx.dev/structure/applications-and-libraries](https://nx.dev/structure/applications-and-libraries)

### Generating a new library to host the custom commands

As mentioned, we need a library to share functionality within our Nx workspace. So let’s generate one:

```bash
npx nx generate @nrwl/js:library --name=cypress-commands --directory=shared --buildable=false
```

> The `@nrwl/js` package is used to create pure TypeScript packages that are framework agnostic. Learn more: [https://nx.dev/getting-started/nx-and-typescript](https://nx.dev/getting-started/nx-and-typescript)
> 

Let’s explore what we got. The newly generated library (as all Nx based libs) has an `index.ts` file which is the “public API” of your library (often also called “barrel file”). This entry point allows to perfectly control what should be exposed to other libraries and applications and what should remain private within the library itself.

### Creating our `getEl` Cypress custom command

In our library, we’d like to have our custom commands specified in separate files. As such, let’s create a `get-el-command.ts` in our library:

```tsx
// libs/shared/cypress-commands/src/lib/get-el-command.ts

// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  interface Chainable<Subject> {
    getEl: (identifier: string) => Chainable<JQuery<HTMLElement>>;
  }
}

Cypress.Commands.add('getEl', (identifier: string) => {
  return cy.get(`[data-testid=${identifier}]`);
});
```

In order to expose it, we need to also export it from the `index.ts` file of our lib:

```tsx
// libs/shared/cypress-commands/src/index.ts
import './lib/get-el-command';
```

🤔 wait a minute, we did an import rather than export in the `index.ts`. Correct. This is a bit of a special case since Cypress commands are created by registering a function to the global `Cypress.Commands` object. As such, all we need to do is to import the file, such that `get-el-command.ts` is executed and hence our `Cypress.Commands.add('getEl',...` function is being called and registered.

### Adding Cypress Type support to our Library

You may notice that TypeScript does not recognize Cypress’ global `cy` object in our generated `cypress-commands` library.

![Cypress types not recognized](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ohrvm8tvuja58iybuo6s.png)

Each Nx lib is already set up and configured to work well with TypeScript. There is a

- `tsconfig.json` which is the entry level TypeScript config file and extends from the root-level `tsconfig.base.json`
- `tsconfig.lib.json` which holds the library specific TypeScript configuration
- `tsconfig.spec.json` which is mainly for Jest tests

To make Cypress types work, we need to add `cypress` and `node` to the `types` property of the `compilerOptions` in `tsconfig.lib.json`:

```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    ...
    "types": ["cypress", "node"]
  },
  ...
}
```

## Consuming Cypress Commands

We are ready to use our custom command in our e2e test, more specifically in `happynrwl-e2e`. To do so, we need to import our custom commands to make sure they are registered with Cypress. Go to `apps/happynrwl-e2e/src/support/index.ts` and import our lib containing the shared Cypress commands:

```tsx
// apps/happynrwl-e2e/src/support/index.ts
...
import '@nxlovescypress/shared/cypress-commands';
```

Note how we can conveniently use `@nxlovescypress/...` . This works because Nx automatically creates a path mapping for every generated library in `tsconfig.base.json`:

```json
{
  "compileOnSave": false,
  "compilerOptions": {
    ...
    "paths": {
      "@nxlovescypress/shared/cypress-commands": [
        "libs/shared/cypress-commands/src/index.ts"
      ]
    }
  },
  "exclude": ["node_modules", "tmp"]
}
```

To test the command, let’s open the main component `nx-welcome.tsx` in the `happynrwl` app and change the title area with a text that says `Nx ❤️ Cypress`:

```json
// apps/happynrwl/src/app/nx-welcome.tsx
<div id="welcome">
  <h1 data-testid="message">
    <span> Hello there, </span>
    Nx ❤️ Cypress
  </h1>
</div>
```

Next, in the corresponding e2e test in `apps/happynrwl-e2e/src/integration/app.spec.ts` we change it to the following:

```tsx
// apps/happynrwl-e2e/src/integration/app.spec.ts
describe('happynrwl', () => {
  beforeEach(() => cy.visit('/'));

  it('should display welcome message', () => {
    cy.getEl('message').should('contain', 'Nx ❤️ Cypress');
  });
});
```

Note how the TypeScript autocomplete support is picked up properly and shows our custom Cypress command:

![Cypress custom command properly recognized by TypeScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bljlessi99ococ1q6wvd.png)


Finally, running the test in watch mode should give you this, which demonstrates the successful use of our command.

```bash
npx nx e2e happynrwl-e2e --watch
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sd49mbt7xreugjwbzjlx.png)

![](https://juristr.com/blog/assets/imgs/nx-cypress-sharing/cypress-running.png)

## Reusing Cypress commands across our Nx workspace

Reusing is simple now. Since our custom Cypress command is now encapsulated in a library within our Nx workspace, we can easily reuse it across other Cypress based e2e tests. All that needs to be done is to import it into the `support/index.ts` of our Cypress setup:

```tsx
import '@nxlovescypress/shared/cypress-commands';
```

I will leave it up to you to

- generate a new application with its corresponding e2e test
- import our shared commands
- use them in a Cypress e2e tests

## Conclusion

This article should have given you a good understanding of how Nx helps set up and configure Cypress and how to leverage libraries in an Nx workspace to not only share functionality among applications, but also among e2e tests.

Here’s the GitHub repository I used for this article: [https://github.com/nrwl/nx-cypress-command-sharing](https://github.com/nrwl/nx-cypress-command-sharing)

---

## Learn more

🧠 [Nx Docs](https://nx.dev)
👩‍💻 [Nx GitHub](https://github.com/nrwl/nx)
💬 [Nrwl Community Slack](https://go.nrwl.io/join-slack)
📹 [Nrwl Youtube Channel](https://www.youtube.com/nrwl_io)
🥚 [Free Egghead course](https://egghead.io/courses/scale-react-development-with-nx-4038)
🧐 [Need help with Angular, React, Monorepos, Lerna or Nx? Talk to us 😃](https://nrwl.io/contact-us)

Also, if you liked this, click the :heart: and make sure to follow [Juri](https://twitter.com/juristr) and [Nx](https://twitter.com/nxdevtools) on Twitter for more!

{% embed https://dev.to/t/nx %}