JavaScript Testing

Integrating Cypress with Cucumber and Gherkin

Marcin Wanago
JavaScriptTesting

Cypress has proven to be an appropriate tool for writing E2E tests. In the previous parts of this series, we’ve gone through its basics. That’s a proper moment to take a look at a useful addition: the Gherkin syntax. In this article, we look into its principles and how to make a Cypress Cucumber integration.

When testing our application, it is very beneficial to simulate the behavior of our users. A proper approach is to define scenarios that describe the system from the customer’s point of view. Doing so is incredibly useful. Such scenarios are helpful when working with team members having less technical knowledge, stakeholders, and product owners.

Making a Cypress Cucumber integration

A reliable way of doing the above is to write in the Gherkin syntax. It comes from the Cucumber tool and is designed to be easily readable even for non-technical people. It promotes Behaviour-driven development in its core. Most importantly, it formalizes an understanding of how the flow of the application should look like.

Let’s start by installing a preprocessor that we need to use the Gherkin syntax:

1npm install cypress-cucumber-preprocessor

As noted in one of the previous parts of this series, the plugins directory contains files that aim to extend and modify the behavior of Cypress. Therefore, it is a proper place to include  cypress-cucumber-preprocessor.

cypress/plugins/index.js
1const cucumber = require('cypress-cucumber-preprocessor').default;
2 
3module.exports = (on) => {
4  on('file:preprocessor', cucumber())
5};

The second thing to do is to modify the  cypress.json file:

1{
2  "testFiles": "**/*.feature"
3}

The above is caused by the fact that we use Gherkin syntax in files with the  .feature extension.

To avoid making our step definitions global, we also add this configuration to our  package.json:

1"cypress-cucumber-preprocessor": {
2  "nonGlobalStepDefinitions": true
3}

This above is going to become the default option in upcoming versions of cypress-cucumber-preprocessor

There seem to bee some performance issues on Windows. If that’s a concern for you, check out this issue.

The principles of the Gherkin syntax

Gherkin is a language operating using a set of keywords. With them, we structure our  .feature files. This is an example of such file:

1Feature: Search functionality
2  Scenario: Using the search input
3    Given I am on the homepage
4    When I fill the search input with the "JavaScript" term
5    And I click on the submit button
6    Then I should be redirected to a search page with the results of the "JavaScript" search

Let’s look into various keywords and their meanings.

Feature

The Feature keyword provides a short description of a tested feature. It has to be the first keyword used in a  .feature file. We can follow it with an additional description below, but we don’t have to.

1Feature: Search functionality
2  This feature allows searching through articles using a string

Scenario

A scenario is an example that represents a business rule. That is to say, it serves as a specification of the application. A scenario consists of multiple steps.

1Scenario: Using the search input

Use meaningful names for scenarios and features to increase readability. Moreover, use additional descriptions if one line is not enough

Given

The first type of step is called a Given. Its purpose is to define the context of a scenario and therefore put the system into a specific state.

1Given I am on the homepage
1Given I am authenticated

When

The When step describes an action. Therefore, it is a fitting place to represent actions taken by the users of our application.

1When I fill the search input with the "JavaScript" term
1When I add a book "You Don't Know JS" into the shopping cart

Then

Then keyword represents a step verifying an outcome of the actions. By using it, we can observe changes in the interface and verify if they are correct.

1Then I should be redirected to a search page with the results of the "JavaScript" search
1Then the book "You don't know JS" should be visible on the list in the shopping cart

And & But

We often find ourselves in need of writing multiple steps of the same type in a row. Instead of doing so, we can use keywords And & But.

1When I fill the search input with the "JavaScript" term
2And I click on the submit button
1Then the book "You don't know JS" should be visible on the list in the shopping cart
2But I should be able to add more books

Background

One feature can have multiple scenarios. If every one of them is in the same context and repeats the same Given steps, we can put them in the Background statement.

1Feature: Managing blog posts
2 
3  Background:
4    Given I am on the admin page
5    And I am authenticated
6 
7  Scenario: Adding a blog post
8    # ...
9 
10  Scenario: Removing a blog post
11    # ...

Using Gherkin with Cypress

Since we are familiar with the Gherkin syntax, we can now start using it with Cypress. Aside from using  .feature files, we need to describe every step using JavaScript.

The recommended way of structuring files is having one directory per a feature file.

1├── cypress
2│   ├── fixtures
3│   ├── integration
4│   │   ├── Search
5│   │   │   └── Search.js
6│   │   ├── Search.feature
7│   ├── plugins
8│   │   └── index.js
9│   └── support
10│       ├── commands.js
11│       └── index.js

The directory needs to have the same name as the feature file. You can name the files in the directory in any way you like.

After importing the desired step from  cypress-cucumber-preprocessor/steps, we can use it to describe actions.

1import { Given } from 'cypress-cucumber-preprocessor/steps';
2 
3Given('I am on the homepage', () => {
4  cy.visit('');
5});

Thanks to doing the above, we associate a piece of JavaScript code with a particular Gherkin command.

Parameters

We can also pass arguments to our Gherkin expressions. To do that, we use curly brackets with the type of the argument.

1When I fill the search input with the "JavaScript" term
1When('I fill the search input with the {string} term', (term) => {
2  cy.get('.search-field').type(term);
3});

Aside from using just a regular string type, we can define custom types and use them in our feature files.

Sharing context

Even though every step definition is a separate function, we can share some context between them. To do so, we can use the “as” command.

1Then the book "You don't know JS" should be visible on the list in the shopping cart
2And I should be able to add more items of the same type
1import { Then, And } from 'cypress-cucumber-preprocessor/steps';
2 
3Then('the book {string} should be visible on the list in the shopping cart', (bookName) => {
4  cy.get('div[data-testid="shoppingCart"]').contains(bookName).as('addedBook');
5});
6 
7And('I should be able to add more items of the same type', () => {
8  cy.get('@addedBook').find('button[data-testid="addButton"]')
9});

Common steps

There are often steps that we would like to reuse across all of our tests. A proper place to define them is in the common directory.

1├── cypress
2│   ├── fixtures
3│   ├── integration
4│   │   ├── common
5│   │   │   └── Navigation.js
6│   │   ├── Search
7│   │   │   └── Search.js
8│   │   ├── Search.feature
9│   ├── plugins
10│   │   └── index.js
11│   └── support
12│       ├── commands.js
13│       └── index.js
cypress/integration/common/Navigation.js
1import { Given } from 'cypress-cucumber-preprocessor/steps';
2 
3Given('I am on the homepage', () => {
4  cy.visit('');
5});

Hooks

Aside from defining the background, we can also use Cucumber Before and After hooks.

cypress/integration/Search/Search.js
1import { Before, When, And, Then } from 'cypress-cucumber-preprocessor/steps';
2 
3Before(() => {
4  cy.visit('');
5});
6 
7When('I fill the search input with the {string} term', (term) => {
8  cy.get('.search-field').type(term);
9});
10 
11And('I click on the submit button', () => {
12  cy.get('.search-submit').click();
13});
14 
15Then('I should be redirected to a search page with the results of the {string} search', (term) => {
16  cy.get('.content-box .post-title--archive span').should('contain.text', term);
17});

Our hooks run before and after each of the defined scenarios.

We can also use tagged hooks. They run conditionally if we prepend a scenario with the desired tag.

1import { Before } from 'cypress-cucumber-preprocessor/steps';
2 
3Before(({ tags: '@homepage' }) => {
4  cy.visit('');
5});
1Feature: Search functionality
2  This feature allows searching through articles using a string
3 
4  @homepage
5  Scenario: Using the search input
6    When I fill the search input with the "JavaScript" term
7    And I click on the submit button
8    Then I should be redirected to a search page with the results of the "JavaScript" search

Summary

In this article, we’ve gone through both the Gherkin syntax and how to make the Cypress Cucumber integration. It proves to build a bridge between the Quality Assurance team, developers, and less tech-oriented parts of the team. Thanks to approaching testing in such a way, we can keep them well organized and readable.