Want To Write Clear End-To-End Tests? Try Cucumber.

Setting Up Cucumber In A Rhino Project

5 min read

End-to-end testing can easily get difficult to maintain if we don’t pay extra attention.

End-to-end testing, compared to unit testing requires more setup to get the system to the desired state. If we don’t reuse test code, testing scripts may get very difficult to update if the application changes. We can manage the complexity by wrapping reusable steps in functions and hiding page structure with Page Objects. Those are good ideas, but even then, what we’re testing is obscured by the programming language syntax.

We can abstract it even further, use an approach that makes it super clear what we’re testing.

Cucumber is a tool allows you to write tests in a human-readable format, using Gherkin – a language that is structured to be easy to understand by non-programmers and easy to parse by a test runner.

What is easier to understand what is tested?

Pure Cypress test?

// cypress/e2e/featureDsiplay.cy.js
describe('Feature display', () => {
  it('Selecting Transmission as the grouping variable', () => {
    cy.visit('http://localhost:3333');
    cy.get('#variable_select').select('Transmission');
    cy.get('#formula_display').should('have.text', 'mpg ~ am');
  });

  it('Selecting Gears as the grouping variable', () => {
    cy.visit('http://localhost:3333');
    cy.get('#variable_select').select('Gears');
    cy.get('#formula_display').should('have.text', 'mpg ~ gear');
  });
});

Or a Cucumber specification:

# cypress/e2e/featureDisplay.feature
Feature: Formula display
  Scenario: Selecting Transmission as the grouping variable
    Given I am on the main page
    When I select Transmission variable
    Then the formula display should show 'mpg ~ am'

  Scenario: Selecting Gears as the grouping variable
    Given I am on the main page
    When I select Gears variable
    Then the formula display should show 'mpg ~ gear'

with support JS code?

// cypress/e2e/featureDisplay.js
const { Given, When, Then } = require('@cypress/cucumber-preprocessor');

Given('I am on the main page', () => {
  cy.visit('http://localhost:3333');
});

When('I select {string} variable', (variable) => {
  cy.get('#variable_select').select(variable);
});

Then('the formula display should show {string}', (formula) => {
  cy.get('#formula_display').should('have.text', formula);
});

If you like the latter, read on. 👇

Why should you use Cucumber?

Adding Cucumber to a Rhino project

Rhino comes packaged with Cypress for end-to-end testing. To use Cucumber we can use the cypress-cucumber-preprocessor that will parse Gherkin files and run them as Cypress tests.

Since Rhino uses webpack to bundle its JS dependencies, we can follow the webpack-cjs example to set up the preprocessor.

The structure of the tests/ directory in a rhino (>=1.6.0) project looks like this:

tests/
  testthat/
  cypress/
    e2e/
    plugins/
    support/
      e2e.js
  cypress.config.js

1. Initialize package in tests directory

In tests/ directory we need to add a package.json:

{
  "dependencies": {
    "@badeball/cypress-cucumber-preprocessor": "latest",
    "@cypress/webpack-preprocessor": "latest",
    "cypress": "latest",
    "webpack": "latest"
  }
}

2. Install dependencies

Run cd tests & npm install to install those dependencies. A tests/package-lock.json will be created.

💡 Why not install those dependencies with the rest of JavaScript dependencies in .rhino/ directory?

This directory is git-ingored by design. When a newer version of rhino is released, the package.json is overwritten.

If you modify rhino/package.json you’ll loose those changes after an upgrade.

3. Adjust Cypress config to use the preporcessor

We need to modify tests/cypress.config.js to use the cucumber preprocessor:

const { defineConfig } = require("cypress");
const webpack = require("@cypress/webpack-preprocessor");
const { addCucumberPreprocessorPlugin } = require("@badeball/cypress-cucumber-preprocessor");

async function setupNodeEvents(on, config) {
  await addCucumberPreprocessorPlugin(on, config);

  on(
    "file:preprocessor",
    webpack({
      webpackOptions: {
        resolve: {
          extensions: [".js"],
        },
        module: {
          rules: [
            {
              test: /\.feature$/,
              use: [
                {
                  loader: "@badeball/cypress-cucumber-preprocessor/webpack",
                  options: config,
                },
              ],
            },
          ],
        },
      },
    })
  );

  return config;
}

module.exports = defineConfig({
  e2e: {
    baseUrl: "http://localhost:3333",
    specPattern: "**/*.feature",
    setupNodeEvents,
  },
});

This change tells Cypress preprocess .feature files before running tests.

4. Add required support file

We also need to add an empty e2e.js file to tests/cypress/support/ directory.

5. Add feature files and step implementations

Now we’re ready to start adding .feature files and their corresponding .js step definitions.

# cypress/e2e/featureDisplay.feature
Feature: Formula display
  Scenario: Selecting Transmission as the grouping variable
    Given I am on the main page
    When I select Transmission variable
    Then the formula display should show 'mpg ~ am'
// cypress/e2e/featureDisplay.js
const { Given, When, Then } = require('@cypress/cucumber-preprocessor');

Given('I am on the main page', () => {
  cy.visit('http://localhost:3333');
});

When('I select {string} variable', (variable) => {
  cy.get('#variable_select').select(variable);
});

Then('the formula display should show {string}', (formula) => {
  cy.get('#formula_display').should('have.text', formula);
});

6. Make it run on CI

If you push those changes to CI you’ll notice that they fail because we don’t have the preprocessor installed.

To make it work on CI let’s modify rhino-ci.yml

At any step before one that runs Cypress tests we need to install the preprocessor.

- name: Install Cucumber preprocessor
  if: always()
  run: >
    cd tests && npm install

💡 Note that we’re modifying one of the rhino files.

If a new version of rhino changes the CI configuration, you’ll need to bring back this step.

🚀 And that’s it!

After completing these steps your should run flawlessly.