> ## Content Index
> Fetch the complete content index at: https://www.jonathancreamer.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Running Cypress tests in a monorepo
- URL: https://www.jonathancreamer.com/running-cypress-tests-in-a-monorepo/
- Published: 2020-07-02T18:40:23.000Z
- Updated: 2020-07-02T19:10:41.000Z
- Description: Cypress is a fantastic testing tool for running your applications in a browser like environment. It can be leveraged in a monorepo to do cross package acceptance and feature testing.
- Author: Jonathan Creamer
- Tags: cypress, monorepo, testing

[Cypress](https://www.cypress.io/?ref=jonathancreamer.com) is a fantastic testing tool for running your applications in a browser like environment. It's reminiscent of Selenium, but runs quicker, and has a much nicer developer experience. It can be used for full on acceptance or integration testing, or even at a feature test level with mocking in place. If used correctly, it can even replace the component testing you might would see with a Jest + Enzyme setup as, rather than having to do a lot of mocking setup, mounting, etc in your unit tests, you can simply just run tests against the features you have and directly interact with the components themselves in a runtime environment.

There are many great examples of how to use Cypress out in the wild already including this [https://github.com/cypress-io/cypress-realworld-app](https://github.com/cypress-io/cypress-realworld-app?ref=jonathancreamer.com) example, but there's not many that cover the monorepo use case.

## Monorepo

Go check this post on <https://www.jonathancreamer.com/how-to-successfully-manage-a-monorepo-aka-megarepo/>, but the tldr; there is...

> A ~~mono~~megarepo is a collection of many units of code or "packages" all in the same repository, managed by tooling which enables sharing those packages easily.

## Cypress in a monorepo

Cypress is built around a few conventions, for one, when you first run Cypress it'll create a nice neat structure for you in the root of whatever project you're working in.

That however doesn't necessarily jive with the structure of a megarepo which has lots of packages all over the place. Let's take the following as our megarepo structure. We're going to use yarn workspaces in this example since it's a fairly easy to understand setup. Note that the same principles should apply to a lerna, bolt, or insert your monorepo of choice. 

```
./cypress
./cypress/fixtures
./cypress/integration
./cypress/support
./cypress/plugins
./packages/
./packages/login
./packages/account
./packages/ui-shared
./packages/web
./cypress.json
./package.json
```

So, in this case our Cypress tests are setup in the `./cypress` folder along with the `plugins`, `fixtures`, and `support` folders.

When `cypress run` is executed, it will by default look into the `./cypress/integration` folder for your tests. This in the case of a monorepo is a little less than ideal as it'd be much better if the tests for each package could live in the actual `./packages` themselves.

Let's go ahead then and create some `cypress` folders in each of our packages too.

`./packages/login/cypress/integration`, etc etc. Then our tests can live next to the code that they're actually testing.

Let's create a `packages/tooling` package as well and add some scripts in here. First, let's create `./packages/tooling/src/cypress-runner.js`. Let's assume there's also a package called `@demo/web` which is an express.js app that has routes for loading the other various packages like `@demo/login`, `@demo/account`, etc.

```typescript
import cypress from 'cypress';
import { app } from '@demo/web';
import glob from 'glob';

const tests = glob.sync('src/cypress/integration/**/*.{js,ts}', {
	cwd: app.directory,
	absolute: true,
});

export const main = async () => {
	await app.listen(3000);
	const result = await cypress.run({
        spec: tests,
        reporter: 'junit'
    });
    
	return result;
};

main().then((result) => {
	if (result.failures) {
    	console.log('Tests failed');
        process.exit(1);
    }
    
    process.exit(0);
});
```

Running this script by either first compiling it with babel or using `babel-node`, you will effectively start your web app, ideally with a promise that resolves when the listener is listening. Then programmatically run cypress with a glob of the test files from each package.

We can expand on this in a number of ways. As well.

### Parallelizing the tests in CI

Ideally your CI will parallelize the tests runners because even as fast as Cypress is, it can still be expensive when running it against many tests.

One way of accomplishing this is to utilize a package called, [chunkd](https://www.npmjs.com/package/chunkd?ref=jonathancreamer.com).

```javascript
export const main = async () => {
    const index = process.env.CI_NODE_INDEX;
    const total = process.env.CI_NODE_TOTAL;

	await app.listen(3000);
	const result = await cypress.run({
        spec: chunkd(tests, index, total).join(','),
        reporter: 'junit'
    });
    
	return result;
};
```

What chunkd allows you to do is, pass in an array of something, give it a number of machines available with an environment variable or something like `process.env.CI_NODE_TOTAL`, and the index of the machine you're running on, with `process.env.CI_NODE_INDEX`.

Let's add an alias for running the script we created.

```json
{
	"scripts": {
    	"cypress:ci", "babel-node ./packages/tooling/src/cypress-runner.js"
    }
}
```

So, if you were using Jenkins for your CI as an example...

```groovy
stage('Cypress Tests') {
	failFast true
	parallel {
	stage('Test group 1') {
    	steps {
            sh 'CI_NODE_TOTAL=2 CI_NODE_INDEX=0 yarn cypress:ci`
        }
    }
	stage('Test group 2') {
    	steps {
            sh 'CI_NODE_TOTAL=2 CI_NODE_INDEX=1 yarn cypress:ci`     
        }
    }
    // ...
}

```

In this case we're leveraging a declarative Jenkins syntax for running tests in parallel.

In [travis ci](https://travis-ci.com/?ref=jonathancreamer.com), you could accomplish something similar with a "build matrix" configuration...

```yaml
script: "yarn cypress:ci`"
env:
  global:
    - CI_NODE_TOTAL=2
  jobs:
    - CI_NODE_INDEX=0
    - CI_NODE_INDEX=1
```

### Only run tests against changed packages

Another nice optimization especially as a monorepo gets larger is to only run tests against packages that have actually changed.

With a tool like lerna, you could leverage the \[lerna changed\]([https://github.com/lerna/lerna/tree/master/commands/changed](https://github.com/lerna/lerna/tree/master/commands/changed?ref=jonathancreamer.com)) command to get a list of the changed packges, and then just iterate through each package, and glob for those same test files.

```javascript
// https://github.com/lerna/lerna/issues/2013
const changed = somehowGetChangedPackages();

const tests = [];
for (const [location] of changed) {
  tests.push(...glob.sync(`${location}/src/cypress/integration/**`))
}
```

You can also utilize something like \[changesets\]([https://github.com/atlassian/changesets](https://github.com/atlassian/changesets?ref=jonathancreamer.com)) from atlassian as well, which keeps track of all the changes to each package in your monorepo as well.

```javascript
import getReleasePlan from '@changesets/get-release-plan';
import getWorkspaces from '@changesets/get-release-plan';

const workspaces = await getWorkspaces();
const releasePlan = await getReleasePlan({ cwd });
const changed = [];

for (const release of releasePlan.releases) {
    const workspace = workspaces.find((w) => w.name === release.name);

    if (workspace) {
        changed.push(workspace.directory);
    }
}

const tests = [];
for (const directory of changed) {
  tests.push(...glob.sync(`${directory}/src/cypress/integration/**`))
}
```

Now you're not only parallelizing the cypress tests you're running, but also only parallelizing the tests against packages which have changes!

## Conclusion

Cypress is a very powerful tool that can be used in many ways. Using it in a monorepo gives you a chance to see how a change in one part of the code base can affect other downstream dependent packages. It also gives you a chance to try a different way to test your code. Rather than mounting your React apps in Jest where you can't actually see them running, Cypress affords you the opportunity to test a whole bunch of cases at once, all the while being able to actually physically see how your components are rendering. It even provides screenshots and videos of your tests! Go give Cypress a try.