cypress-cucumber-preprocessor

by TheBrainFamily

Run cucumber/gherkin-syntaxed specs with cypress.io

558 Stars 144 Forks Last release: about 1 month ago (v2.5.5) MIT License 193 Commits 42 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

styled with prettier CircleCI

Run cucumber/gherkin-syntaxed specs with Cypress.io

The cypress-cucumber-preprocessor adds support for using feature files when testing with Cypress.

You can follow the documentation below, or if you prefer to hack on a working example, take a look at https://github.com/TheBrainFamily/cypress-cucumber-example

Table of contents

Get started

Installation

Install the plugin by running:

npm install --save-dev cypress-cucumber-preprocessor

Cypress Configuration

Add it to your plugins:

cypress/plugins/index.js
```javascript const cucumber = require('cypress-cucumber-preprocessor').default

module.exports = (on, config) => { on('file:preprocessor', cucumber()) } ```

Add support for feature files to your Cypress configuration

cypress.json
json
{
  "testFiles": "**/*.feature"
}

Configuration

Please make use of cosmiconfig to create a configuration for the plugin, for example, by adding this section to your package.json:

"cypress-cucumber-preprocessor": {
  "nonGlobalStepDefinitions": true
}

This will become the default option in a future version

Configuration option

Option

Default value Description
commonPath

cypress/integration/common
when
nonGlobalStepDefinitions
is true
cypress/support/step_definitions
when
nonGlobalStepDefinitions
is false
${nonGlobalStepBaseDir}/common
when
nonGlobalStepBaseDir
is defined
Define the path to a folder containing all common step definitions of your tests. When
nonGlobalStepBaseDir
is defined this path is defined from that base location. e.g
${nonGlobalStepBaseDir}/${commonPath}
.
nonGlobalStepDefinitions false If true use the Cypress Cucumber Preprocessor Style pattern for placing step definitions files. If false, we will use the "oldschool" (everything is global) Cucumber style.
nonGlobalStepBaseDir undefined If defined and
nonGlobalStepDefinitions
is also true then step definition searches for folders with the features name will start from the directory provided here. The cwd is already taken into account. e.g
test/step_definitions
.
stepDefinitions
cypress/integration
when
nonGlobalStepDefinitions
is true
cypress/support/step_definitions
when
nonGlobalStepDefinitions
is false
Path to the folder containing our step definitions.

How to organize the tests

Single feature files

Put your feature files in

cypress/integration/

Example: cypress/integration/Google.feature ```gherkin Feature: The Facebook

I want to open a social network page

@focus Scenario: Opening a social network page Given I open Google page Then I see "google" in the title ```

The @focus tag is not necessary, but we want to you to notice it so you look it up below. *It will speed up your workflow significantly*!

Bundled features files

When running Cypress tests in a headless mode, the execution time can get pretty bloated, this happens because by default Cypress will relaunch the browser between every feature file. The cypress-cucumber-preprocessor gives you the option to bundle all feature files before running the tests, therefore reducing the execution time.

You can take advantage of this by creating

.features
files. You choose to have only one in the root of the directory
cypress/integrations
or per directory.

You also have to add support for

.features
files to your Cypress configuration

cypress.json
json
{
  "testFiles": "**/*.{feature,features}"
}

To run the bundled tests:

shell
cypress run --spec **/*.features

Step definitions

This is the RECOMMENDED way

Step definitions creation

The

.feature
file will use steps definitions from a directory with the same name as your
.feature
file. The javascript files containing the step definitions can have other names if you want to break them into different concerns.

Easier to show than to explain, so, assuming the feature file is in

cypress/integration/Google.feature
, as proposed above, the preprocessor will read all the files inside
cypress/integration/Google/
, so:

cypress/integration/Google/google.js
(or any other .js file in the same path) ```javascript import { Given } from "cypress-cucumber-preprocessor/steps";

const url = 'https://google.com' Given('I open Google page', () => { cy.visit(url) }) ```

This is a good place to put before/beforeEach/after/afterEach hooks related to that particular feature. This is incredibly hard to get right with pure cucumber.

Reusable step definitions

We also have a way to create reusable step definitions. Put them in

cypress/integration/common/

Example: cypress/integration/common/iseestringinthe_title.js ```javascript import { Then } from "cypress-cucumber-preprocessor/steps";

Then(

I see {string} in the title
, (title) => { cy.title().should('include', title) }) ```

This is a good place to put global before/beforeEach/after/afterEach hooks.

How to write tests

Cucumber Expressions

We use https://docs.cucumber.io/cucumber/cucumber-expressions/ to parse your .feature file, please use that document as your reference

Given/When/Then functions

Since Given/When/Then are on global scope please use

javascript
/* global Given, When, Then */
to make IDE/linter happy or import them directly as shown in the above examples.

Data table parameters

To create steps that use gherkin data tables, the step definition needs to take an object and handle it like in these examples: Example Feature, Example Step Definition.

Custom Parameter Type Resolves

Thanks to @Oltodo we can now use Custom Parameter Type Resolves. Here is an example with related .feature file

Before and After hooks

The cypress-cucumber-preprocessor supports both Mocha's

before/beforeEach/after/afterEach
hooks and Cucumber's
Before
and
After
hooks.

The Cucumber hooks implementation fully supports tagging as described in the cucumber js documentation. So they can be conditionally selected based on the tags applied to the Scenario. This is not possible with Mocha hooks.

Cucumber Before hooks run after all Mocha before and beforeEach hooks have completed and the Cucumber After hooks run before all the Mocha afterEach and after hooks.

Example: ```javascript const { Before, After, Given, Then } = require("cypress-cucumber-preprocessor/steps");

// this will get called before each scenario Before(() => { beforeCounter += 1; beforeWithTagCounter = 0; });

// this will only get called before scenarios tagged with @foo Before({ tags: "@foo" }, () => { beforeWithTagCounter += 1; });

Given("My Step Definition", () => { // ...test code here }) ```

Note: to avoid confusion with the similarly named Mocha before and after hooks, the Cucumber hooks are not exported onto global scope. So they need explicitly importing as shown above.

Background section

Adding a background section to your feature will enable you to run steps before every scenario. For example, we have a counter that needs to be reset before each scenario. We can create a given step for resetting the counter.

let counter = 0;

Given("counter has been reset", () => { counter = 0; });

When("counter is incremented", () => { counter += 1; });

Then("counter equals {int}", value => { expect(counter).to.equal(value); });

Feature: Background Section

Background: Given counter has been reset

Scenario: Basic example #1 When counter is incremented Then counter equals 1

Scenario: Basic example #2 When counter is incremented When counter is incremented Then counter equals 2

Sharing context

You can share context between step definitions using

cy.as()
alias.

Example: ```javascript Given('I go to the add new item page', () => { cy.visit('/addItem'); });

When('I add a new item', () => { cy.get('input[name="addNewItem"]').as('addNewItemInput'); cy.get('@addNewItemInput').type('My item'); cy.get('button[name="submitItem"]').click(); })

Then('I see new item added', () => { cy.get('td:contains("My item")'); });

Then('I can add another item', () => { expect(cy.get('@addNewItemInput').should('be.empty'); });

For more information please visit: https://docs.cypress.io/api/commands/as.html

Smart tagging

Start your tests without setting any tags. And then put a @focus on the scenario (or scenarios) you want to focus on while development or bug fixing.

For example:

Feature: Smart Tagging

  As a cucumber cypress plugin which handles Tags
  I want to allow people to select tests to run if focused
  So they can work more efficiently and have a shorter feedback loop

  Scenario: This scenario should not run if @focus is on another scenario
    Then this unfocused scenario should not run

  @focus
  Scenario: This scenario is focused and should run
    Then this focused scenario should run

  @this-tag-affects-nothing
  Scenario: This scenario should also not run
    Then this unfocused scenario should not run

  @focus
  Scenario: This scenario is also focused and also should run
    Then this focused scenario should run
</pre>
<h2>How to run the tests</h2>

<p>Run your Cypress Launcher the way you would usually do, for example:</p>
<pre class="language-bash">./node_modules/.bin/cypress open
</pre>
<p>click on a </p><pre>.feature</pre> file on the list of specs, and see the magic happening!

<h3>Running tagged tests</h3>

<p>You can use tags to select which test should run using <a href="https://github.com/cucumber/cucumber/tree/master/tag-expressions">cucumber's tag expressions</a>.
Keep in mind we are using newer syntax, eg. </p><pre>'not @foo and (@bar or @zap)'</pre>.
In order to initialize tests using tags you will have to run cypress and pass TAGS environment variable.

<p>Example:
</p><pre>shell
  ./node_modules/.bin/cypress-tags run -e TAGS='not @foo and (@bar or @zap)'
</pre>

<p>Please note - we use our own cypress-tags wrapper to speed things up.
This wrapper calls the cypress executable from local modules and if not found it falls back to the globally installed one.
For more details and examples please take a look to the <a href="https://github.com/TheBrainFamily/cypress-cucumber-example">example repo</a>.</p>

<h3>Ignoring specific scenarios using tags when executing test runner</h3>

<p>You can also use tags to skip or ignore specific tests/scenarios when running cypress test runner (where you don't have the abilitiy to pass parameters like in the examples above for the execution)</p>

<p>The trick consists in adding the "env" property with the "TAGS" subproperty in the cypress.json configuration file. It would look something like this:</p>
<pre class="language-javascript">{
    "env": {
        "TAGS": "not @ignore"
    },
    //rest of configuration options
    "baseUrl": "yourBaseUrl",       
    "ignoreTestFiles": "*.js",
    //etc
}
</pre>
<p>Then, any scenarios tagged with @ignore will be skipped when running the tests using the cypress test runner</p>

<h3>Limiting to a subset of feature files</h3>

<p>You can use a glob expression to select which feature files should be included.</p>

<p>Example:
</p><pre>shell
  ./node_modules/.bin/cypress-tags run -e GLOB='cypress/integration/**/*.feature'
</pre>

<h3>Output</h3>

<p>The <strong>cypress-cucumber-preprocessor</strong> can generate a </p><pre>cucumber.json</pre> file output as it runs the features files. This is separate from, and in addition to, any Mocha reporter configured in Cypress.

<p>These files are intended to be used with one of the many available Cucumber report generator packages. 
Seems to work fine with both https://github.com/jenkinsci/cucumber-reports-plugin and https://github.com/wswebcreation/multiple-cucumber-html-reporter</p>

<p>Output, by default, is written to the folder </p><pre>cypress/cucumber-json</pre>, and one file is generated per feature.

<p>This behaviour is configurable. Use <a href="https://github.com/davidtheclark/cosmiconfig">cosmiconfig</a> to create a configuration for the plugin, see step definition discussion above and add the following to the cypress-cucumber-preprocessor section in package.json to turn it off or change the defaults:</p>
<pre>  "cypress-cucumber-preprocessor": {
    "cucumberJson": {
      "generate": true,
      "outputFolder": "cypress/cucumber-json",
      "filePrefix": "",
      "fileSuffix": ".cucumber"
    }
  }
</pre>
<h4>Cucumber.json options</h4>

<p>Option | Default value | Description
------------ | ------------- | -------------
outputFolder | </p><pre>cypress/cucumber-json</pre> | The folder to write the files to
filePrefix | <pre>''</pre> <em>(no prefix)</em> | A separate json file is generated for each feature based on the name of the feature file. All generated file names will be prefixed with this option if specified
fileSuffix | <pre>.cucumber</pre> | A suffix to add to each generated filename
generate | <pre>false</pre> | Flag to output cucumber.json or not

<h2>IDE support</h2>

<h3>WebStorm</h3>

<p>If you want WebStorm to resolve your steps, use the capitalized </p><pre>Given/When/Then</pre> function names (instead of the initial given/when/then).

<p>Note, only WebStorm 2019.2 and later versions are able to <a href="https://youtrack.jetbrains.com/issue/WEB-11505">resolve steps located outside of a step_definitions folder</a></p>

<h3>Intellij IDEA</h3>

<p>Intellij IDEA Community Edition does not support cucumber in javascript, but the Ultimate Edition can provide the same level support for step resolution as WebStorm.</p>

<p>To enable cucumber step resolution in Intellij IDEA Ulimate edition you will need to download and enable the JetBrains <a href="https://plugins.jetbrains.com/plugin/7418-cucumber-js/">Cucumber JS plugin</a>. In WebStorm this plugin is already bundled into the distribution.</p>

<h3>Visual Studio Code</h3>

<p>To get vscode to resolve your steps, install the <a href="https://marketplace.visualstudio.com/items?itemName=alexkrechik.cucumberautocomplete">Cucumber (Gherkin) Full Support</a> extension from the marketplace.</p>

<p>You will also need to tell the extension the locations of your feature and step definition files <a href="https://github.com/alexkrechik/VSCucumberAutoComplete#settings-example">as described here</a>.</p>

<p>Note, that unlike WebStorm which will correctly identify multiple implementations of matching steps, the vscode extension currently resolves to the first matching occurence it finds on its path.</p>

<h2>TypeScript Support</h2>

<h3>Install</h3>

<p>Install the plug-in type definitions:</p>
<pre class="language-shell">npm install --save-dev @types/cypress-cucumber-preprocessor
</pre>
<h3>With out-of-the-box support</h3>

<p>As of <a href="https://github.com/cypress-io/cypress/releases/tag/v4.4.0">Cypress
v4.4.0</a>, TypeScript
is supported out-of-the-box. To use it, add this to your </p><pre>plugins/index.js</pre>:
<pre class="language-javascript">const browserify = require('@cypress/browserify-preprocessor');
const cucumber = require('cypress-cucumber-preprocessor').default;
const resolve = require('resolve');

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

  on('file:preprocessor', cucumber(options));
};
</pre>
<h3>With Webpack</h3>

<p>You can also use a Webpack loader to process feature files (TypeScript supported). To see how it is done please take 
a look here: <a href="https://github.com/TheBrainFamily/cypress-cucumber-webpack-typescript-example">cypress-cucumber-webpack-typescript-example</a></p>

<h3>Without Webpack</h3>

<p>If you want to use TypeScript, add this to your plugins/index.js:</p>
<pre class="language-javascript">const cucumber = require("cypress-cucumber-preprocessor").default;
const browserify = require("@cypress/browserify-preprocessor");

module.exports = (on) =&gt; {
  const options = browserify.defaultOptions;

  options.browserifyOptions.plugin.unshift(['tsify']);
  // Or, if you need a custom tsconfig.json for your test files:
  // options.browserifyOptions.plugin.unshift(['tsify', {project: 'path/to/other/tsconfig.json'}]);

  on("file:preprocessor", cucumber(options));
};
</pre>
<p>...and install <em>tsify</em>. I'm assuming you already have typescript installed. :-)</p>
<pre class="language-bash">npm install tsify
</pre>
<p>Then in your .ts files you need to make sure you either require/import the functions defining step definitions, or declare them as global:</p>
<pre class="language-typescript">declare const Given, When, Then;
// OR
import { Given, Then, When } from "cypress-cucumber-preprocessor/steps";
</pre>
<p>To see an example take a look here: <a href="https://github.com/TheBrainFamily/cypress-cucumber-typescript-example/">https://github.com/TheBrainFamily/cypress-cucumber-typescript-example/</a></p>

<p><a name="legacy"></a></p>

<h2>How to contribute</h2>

<p>Install all dependencies:
</p><pre>bash
npm install
</pre>

<p>Link the package:
</p><pre>bash
npm link 
npm link cypress-cucumber-preprocessor
</pre>

<p>Run tests:
</p><pre>bash
npm test
</pre>

<h3>Submitting your PR</h3>

<p>This repo uses <a href="https://github.com/commitizen/cz-cli">commitizen</a> to manage commits messages and <a href="https://github.com/semantic-release/semantic-release">semantic-release</a> to use those commit messages to automatically release this package with proper release version.</p>

<p>If you are confused please ask questions or/and read the documentation of these two fantastic tools! As far as the development goes, you should just do git commit from the command line, and commitizen will guide you through creating a proper commit message.</p>

<h3>Issues</h3>

<p>Please let me know if you find any issues or have suggestions for improvements by opening a new issue.</p>

<h2>Roadmap</h2>

<ul>
<li>(Under discussion) Add option to customize mocha template <a href="https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/3">#3</a>
</li>
</ul>

<p><a name="credits"></a></p>

<h2>Credit where it's due!</h2>

<p>Based/inspired by the great work from <a href="https://github.com/sitegeist/gherkin-testcafe">gherkin-testcafe</a>, although, with this package we don't have to run Cypress programmatically - with an external runner, we can use Cypress as we are used to :)</p>

<p>Thanks to the Cypress team for the fantastic work and very exciting tool! :-)</p>

<p>Thanks to @fcurella for fantastic work with making the <strong>cypress-cucumber-preprocessor</strong> reactive to file changes. :-)</p>

<hr>

<h2>Oldschool/Legacy Cucumber style</h2>

<p><strong>Not recommended. Please let us know if you decide to use it!</strong></p>

<h3>Why avoid it</h3>

<p>The problem with the legacy structure is that everything is global. This is problematic for multiple reasons.</p>

<ul>
<li>It makes it harder to create <pre>.feature</pre> files that read nicely - you have to make sure you are not stepping on toes of already existing step definitions. You should be able to write your tests without worrying about reusability, complex regexp matches, or anything like that. Just write a story. Explain what you want to see without getting into the details. Reuse in the .js files, not in something you should consider an always up-to-date, human-readable documentation.</li>
<li>The startup times get much worse - because we have to analyze all the different step definitions so we can match the .feature files to the test.</li>
<li>Hooks are problematic. If you put before() in a step definition file, you might think that it will run only for the .feature file related to that step definition. You try the feature you work on, everything seems fine and you push the code. Here comes a surprise - it will run for ALL .feature files in your whole project. Very unintuitive. And good luck debugging problems caused by that! This problem was not unique to this plugin, but to the way cucumberjs operates. </li>
</ul>

<p>Let's look how this differs with the proposed structure. Assuming you want to have a hook before ./Google.feature file, just create a ./Google/before.js and put the hook there. This should take care of long requested feature - <a href="https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/blob/master/#25">https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/25</a></p>

<p>If you have a few tests the "oldschool" style is completely fine. But for a large enterprise-grade application, with hundreds or sometimes thousands of .feature files, the fact that everything is global becomes a maintainability nightmare. </p>

<p>We suggest to put: 
</p><pre>json
  "ignoreTestFiles": "*.js"
</pre>
in your cypress.json to have a clean view of your tests in the cypress dashbord, and also so cypress doesn't try to run your step definition files as tests in CI. 

<h3>Step Definition location configuration</h3>

<p>Step definition files are by default in: </p><pre>cypress/support/step_definitions</pre>. If you want to put them somewhere please use <a href="https://github.com/davidtheclark/cosmiconfig">cosmiconfig</a> format. For example, add to your package.json :
<pre class="language-javascript">  "cypress-cucumber-preprocessor": {
    "step_definitions": "cypress/support/step_definitions/"
  }
</pre>
<p>Follow your configuration or use the defaults and put your step definitions in </p><pre>cypress/support/step_definitions</pre>

<p>Examples:
cypress/support/step_definitions/google.js
```javascript
import { Given } from "cypress-cucumber-preprocessor/steps";</p>

<p>const url = 'https://google.com'
Given('I open Google page', () =&gt; {
  cy.visit(url)
})
```</p>

<p>cypress/support/step_definitions/shared.js
```javascript
import { Then } from "cypress-cucumber-preprocessor/steps";</p>

<p>Then(</p><pre>I see {string} in the title</pre>, (title) =&gt; {
  cy.title().should('include', title)
})

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.