I'm in a Pickle - Configuring Gherkin with NX Workspaces
Tags: tutorial, cypress, testing, typescript
Published: 2020-04-18 09:59:37

## Why NX?

I believe, that every project should start with a proper development environment setup. [NX](nx.dev) does that or at least gives us a head-start. When you generate a project in a monorepo structure, it provides you with very good tools already set up. I encourage you to try it out, it has helped me deliver well tested and stable code for short deadlines. By default, it sets up [Jest](jestjs.io) as the unit test framework, and it also generates a well set up [cypress](cyoress.io) test environment for every front-end project.

However, there comes a time, when you encounter a project, where the client has been using Selenium driven e2e tests with [Gherkin](<https://en.wikipedia.org/wiki/Cucumber_(software)#Gherkin_language>)
as their test language. There is a good reason for that because Gherkin is the format for cucumber specifications. It is a language abstraction which helps you to describe business behaviour without the need to go into details of the implementation. This text acts as documentation, and a skeleton for your automated tests. Although, setting it up inside an NX workspace might require some effort.

![I turned this blog post into a pickle](https://dev-to-uploads.s3.amazonaws.com/i/hb0ghiq7ppnr5ubsdipa.png "A print screenshot of a .feature file using Gherkin syntax as a step-by-step outline of this blog post")

## Set up Gherkin

Cypress uses JavaScript for writing the test cases, but NX ships it with nice TypeScript support. We would like to support Gherkin, therefore, we need to install [cypress-cucumber-preprocessor](https://www.npmjs.com/package/cypress-cucumber-preprocessor) as a dev dependency.

`npm install cypress-cucumber-preprocessor cucumber-html-reporter --save-dev`


With the packages installed, let's set up a configuration file in our root folder, so let's add the `cypress-cucumber-preprocessor.config.js` file to our root folder. We set up our universe config, so each of our cypress tests will look for their step definitions in their respective folders.


```javascript
const path = require("path")

const stepDefinitionsPath = path.resolve(process.cwd(), "./src/integration")
const outputFolder = path.resolve(process.cwd(), "../../cyreport/cucumber-json")

module.exports = {
  nonGlobalStepDefinitions: true,
  stepDefinitions: stepDefinitionsPath,
  cucumberJson: {
    generate: true,
    outputFolder: outputFolder,
    filePrefix: "",
    fileSuffix: ".cucumber",
  },
}
```

It is important that this file had the `.config.js` ending, otherwise, your step definitions will not be found. Notice, that we have configured the `stepDefinitionPath` variable, so it looks for the feature files from the current working directory. This is important because that is going to be the folder from where cypress is going to run. We created an `outputFolder` variable as well because we are going to need it to generate our `cucumber-json` file for our reports, but more on that later. Finally, we export our universe config, with setting the `nonGlobalStepDefinitions` property to true. We do this because we want our step definitions to be under the `{your_e2e_folder}/integration/common` folder.


## Migrate our tests


A freshly generated NX e2e project uses the following folder structure:


```
project-e2e
|_src
| |_fixtures
| | |_example.json
| |_integration
| | |_app.spec.ts
| |_plugins
| | |_index.js
| |_support
| | |_app.po.ts
| | |_commands.ts
| | |_index.ts
|_cypress.json
|_tsconfig.e2e.json
|_tsconfig.json
|_tslint.json
```


The default test looks like the following:


```typescript
// app.po.ts
export const getGreeting = () => cy.get("h1")

// app.spec.ts
import { getGreeting } from "../support/app.po"

describe("sandbox", () => {
  beforeEach(() => cy.visit("/"))

  it("should display welcome message", () => {
    // Custom command example, see `../support/commands.ts` file
    cy.login("my-email@something.com", "myPassword")

    // Function helper example, see `../support/app.po.ts` file
    getGreeting().contains("Welcome to app!")
  })
})
```


What does this test do? Before every test, the application is opened at the `/` url. The `cy.login()` method is a custom command added in the commands.ts file. It simulates a login step. Then finally, checks if the page displays `Welcome to app!` inside an `h1` HTML tag. Let's implement the same in Gherkin. We can delete the `app.spec.ts` and the `app.po.ts` files and create a `Landing.feature` file with the following content:

```Gherkin
Feature: Landing page

Scenario: The application displays the welcome message.
  Given the user is logged in
  Then the "Welcome to app!" message is displayed
```


We create two folders near our feature file, the `Landing` and the `common`. The `common` folder contains our shared steps, the `Landing` folder deals with the feature-specific steps. Make sure that you use the same name for these steps as you use for your `.feature` file. In the `common/common.steps.ts`, add the following:

```typescript
import { Before } from "cypress-cucumber-preprocessor/steps"

Before(() => {
  cy.server()
})
```


This will make sure, that we can stub requests in steps. Be careful though, the order of the steps matters. In our `Landing/landing.steps.ts` file, we add our `Given` and `Then` steps.

```typescript
import { Before, Given, Then } from "cypress-cucumber-preprocessor/steps"

Before(() => {
  cy.visit("/")
})

Given("the user is logged in", () => {
  cy.login("my-email@something.com", "myPassword")
})

Then("the {string} message is displayed", (message: string) => {
  cy.get("h1").contains(message)
})
```


## Preprocessed Pickles

Our roughly-the-same test case is ready, however, we still cannot run our tests. Our cypress runner is not-yet set up for processing .feature files, and it still expects only typescript files as test sources. We need to update our cypress configuration. Let's update our cypress.json first:

```json
{
  "fileServerFolder": ".",
  "fixturesFolder": "./src/fixtures",
  "integrationFolder": "./src/integration",
  "modifyObstructiveCode": false,
  "pluginsFile": "./src/plugins/index",
  "supportFile": "./src/support/index.ts",
  "video": true,
  "videosFolder": "../../dist/cypress/apps/sandbox-e2e/videos",
  "screenshotsFolder": "../../dist/cypress/apps/sandbox-e2e/screenshots",
  "chromeWebSecurity": false,
  "testFiles": "**/*.{feature,features}"
}
```

We also need to update our cypress plugins file. Let's open the `plugins/index.js` file and replace its contents with the following:

_Update (2021.02.02.)_
\_As of [Cypress v4.4.0](https://github.com/cypress-io/cypress/releases/tag/v4.4.0), TypeScript is supported out-of-the-box.

```javascript
const browserify = require('@cypress/browserify-preprocessor');
const cucumber = require('cypress-cucumber-preprocessor').default;
const resolve = require('resolve');

module.exports = (on, config) => {
  const options = {
    ...browserify.defaultOptions,
    typescript: resolve.sync('typescript', { baseDir: config.projectRoot }),
  };

  on('file:preprocessor', cucumber(options));
};
```


We don't want to override the existing typescript compilation solution provided by our NX workspace, we just want to extend it. We add two loaders, one for `.feature` files and one for `.features` files. In the cypress plugin setup method that we export we need to make some changes. Our `webpackConfig` needs to set the node options to `'empty'`, so compilation does not throw node-specific errors. We push our feature configurations into the rules, and we set the preprocessor to use our new config.

Now if you run your cypress tests with `npm run e2e` it uses Gherkin. One note though, if you are used to tag-based test running with CucumberJS that does not work well in an NX workspace. At least I have not found a proper way to pass the TAGS environment variables in the command line. That said, all the debugging capabilities are present if you open cypress in their application, you can select your tests, and you can debug them easily.

However, If anybody can provide a good solution for tags, I'm open to suggestions.

## Lt. Pickle reports for duty

One of the reasons to use cucumber is human-readable test cases. When your Product Owner wants to validate properly how a feature set works you can present them with cool Cucumber test run reports. In our initial config setup, we have already added configuration to generate a cypress report with a cucumber.json file. We just need to add a tool to generate a nice report. Let's create a `generate-cucumber-report.js` file in our `tools` folder:

```javascript
const path = require("path")
const reporter = require("cucumber-html-reporter")
const chalk = require("chalk")

const options = {
  theme: "bootstrap",
  jsonDir: path.join(process.cwd(), "cyreport/cucumber-json"),
  output: path.join(process.cwd(), "cyreport/cucumber_report.html"),
  reportSuiteAsScenarios: true,
  scenarioTimestamp: true,
  launchReport: false,
  storeScreenshots: true,
  noInlineScreenshots: false,
}

try {
  reporter.generate(options)
} catch (e) {
  console.log(chalk.red(`Could not generate cypress reports`))
  console.log(chalk.red(`${e}`))
}
```


We put the whole report generator method into a try-catch, because if we run our e2e tests in the affected mode, sometimes they won't run, and report generation would break our build pipeline. Let's update our package.json to run this generator script.

```json
{
  "scripts": {
    "devtools:cucumber:report": "node ./tools/generate-cucumber-report.js"
  }
}
```


After running your e2e tests, running `npm run devtools:cucumber:report` in the terminal would generate a report. We can find it in the `cyreport` folder.

---

This guide could not have been written without several hours of reading GitHub issues. I'd like to thank the open-source community for creating these awesome tools, and for documenting them properly. Huge thanks for the people who found solutions and workarounds and comment them on GitHub issues. I wanted to document my final solution, so it might help others in the future.

You can read the original article [here](https://tapaibalazs.netlify.app/how-to-set-up-gherkin-with-nx-cypress-ts/)