5 min read

Cypress testing enhanced by Cucumber

Care to share?

In the previous post, I introduced you to the basics of Cypress. It owes a part of its popularity to customization possibilities that can improve the overall development and testing experience. One of my favorite tricks is enhancing Cypress with Cucumber. It closes the gap between technical and business people involved in the project and leaves us with documentation of the app as a byproduct. 

Using Cypress with the Cucumber preprocessor 


Cucumber is built on the foundations of behavior-driven development (BDD).

The basic flow of BDD can be described in three steps:

  1. Creating a user story: The business describes what a feature should do in general.

  2. Describing real-life examples: We split  stories into scenarios of the actual behavior of a user. It’s written in plain English.

  3. Automation: Scenarios are automatically tested against the app behavior.

Cucumber allows for connecting the scenarios with the actual tests created in Cypress. It speeds up the development process by assuring that every person involved in the project is on the same page when it comes to the app’s behavior. There is no room for misunderstanding because scenarios are the executable documentation of the system behavior. 

Example of a testing scenario


Cucumber uses Gherkin as its syntax. It’s almost plain English but structured enough for Cucumber to understand. This syntax includes keywords like:

  • Feature describes the feature we want to test.
  • Scenario specifies the part of the feature we want to focus on.
  • Others like And, Then, When, etc. help to structure the scenario.

Let’s assume I want to google “NBA”

  •  I would first open google homepage.
  •  Then type “NBA”.
  •  Click the search button.
  • Verify if the results include “NBA”

The above steps can be written in Gherkin in a dedicated .feature file as follows:

# google.feature
Feature: google search
Scenario: results fits the searched term
  Given i open google homepage
  When i search for "NBA"
  Then i should see "NBA" related pages


It’s pretty clear what the requirements are and what each step tries to achieve. Then, each step has to have a step definition that is written in a programming language. For example, inside the .js file:

# google.js
import { Given, When } from "cypress-cucumber-preprocessor/steps";
Given("i open google homepage", () => {
  cy.visit("https://google.com");
});
When("i search for {string}", (searchTerm) => {
  cy.get('input').type(searchTerm);
  cy.get('button').click();
})
(...)


The When the definition is flexible enough to accept any string defined in the .feature file and still link those two together.

Cypress Cucumber testing in practice

Let’s go back to the to-do app created for this series of articles. It’s all set up and working, so you can see the final result there. Below, I’ll show you the whole process to help you understand it better and easily do the same in your own project. 

Unfortunately, adding Cucumber is not as easy as adding Cypress to the project, but it’s not that hard either. First, you have to install Cucumber:

yarn add -D cypress-cucumber-preprocessor

Then, its config can be placed in package.json as follows:

"cypress-cucumber-preprocessor": {
"nonGlobalStepDefinitions": true,
"stepDefinitions": "cypress/integration",
"cucumberJson": {
  "generate": true,
  "outputFolder": "cypress/cucumber-json",
  "filePrefix": "",
  "fileSuffix": ".cucumber"
}
}


Since we’ll use the test files with the .feature extension, the cypress.json must be enhanced with:

"testFiles": "*.{feature,features,js}"

Finally, Cypress must be informed that a new plugin is in town. cypress/plugins/index.js should look like this:

const cucumber = require('cypress-cucumber-preprocessor').default;
module.exports = (on) => {
  on("file:preprocessor", cucumber());
};

After all that configuration, let's create a .feature file under cypress/integration/to-do-cucumber.feature:

Feature: To-do app
Scenario: it initializes properly
  Given i open "/" page
   Then i should see 2 items


When we open Cypress, the tests will fail with information:

Step implementation missing for: i open “/” page

To avoid that, we need to create a folder with the same name next to the .feature file where we have a file with steps definitions. In this case, cypress/integration/to-do-cucumber/index.js. Then we want to tell Cypress what it should do in each step.

import { Given, Then } from "cypress-cucumber-preprocessor/steps";
Given("i open {string} page", (url) => {
  cy.visit(url);
});
Then("i should see {int} items", (qty) => {
  cy.the("to-do-list").children().should("have.length", qty);
});


If you’re wondering whether the is a new Cypress command, then no, it’s a custom one which I explained how to create in the last article.


The definition of the step is just a step function from Cucumber. It accepts two parameters:

  •  A string that must be equal to the step description from the .feature file.
  •   A callback where the actual Cypress commands are used.

We can pass as many parameters as we like to the callback function and name it whatever we want because it will be taken with the order of appearance in the step name. It’s just required to specify the type in the name, so in the first step, {string} is a URL passed to the callback function, and in the second step, {int} is qty.

We can reuse step definitions by passing the proper values. That means that for the step, Then i should see {int} items, the {int} variable can have a different value depending on if you test delete method or adding a new item. There’s no need to write a separate test for those. You only need to check for different values.

Final thoughts on Cypress Cucumber testing

Changing keywords

Since we need to keep the proper folder structure for Cucumber to work, we don’t need to keep the same step definition keyword. That means that if we decide to change a keyword in the .feature file, we don’t need to adjust that in step definitions. It makes sense because we want to keep the .feature files readable, and we do not care that much about the keyword in .js files. In short,  we can use only the Given method in the .js file for all tests, and it’ll work just fine.

Do repeat yourself

We are taught the “don't repeat yourself” coding principle, but for the tests with Cucumber, it’s not always desired. That’s because the code itself does double duty as documentation of our project. That’s why, in every scenario, we open the homepage first. Now, if the test fails, we can see every step and it’s easier to find a bug. On the other hand, if we want to go back to check how a specific feature should work, we just check scenarios, and it’s all written down without needing to jump between files. Remember that this part should be equally readable for tech and business people.

Time to write your own Cucumber Cypress tests

I hope this post was useful and helped you understand how to leverage Cypress end-to-end (E2E) testing with Cucumber. It can truly save you a lot of time. Just check for yourself how easy it is.

If you have a question, feel free to drop a comment or message me on LinkedIn.

Published June 28, 2022