One of the most common questions I get on webinars and livestreams is: How do I use "X" in Cucumber?. Whether it is API testing, cy.session()
or other functionality, Cucumber seems to be a requirement in many teams.
Main advantage of using Cucumber is the ability to use Gherkin syntax for test definitions. All tests are written as behavior scenarios and therefore test not only fulfill the role of veryfing the functionality, but also serve as a living documentation. The goal of such approach is to provide more visibility into what’s being tested. The benefit is that besides engineering, another company stakeholders can review whether acceptibility criteria is being met.
I’ve seen this approach work well in medical and banking sector, where tests were not only functional, but were further used to generate documentation or provide a high level report.
I have been known for criticising the Gherkin syntax approach in the past. My main objection to this is that it uses "black-box" approach to testing. I find this approach to not be very effective - especially with Cypress.
Cypress tests are executing inside browser, giving you the ability to enter application’s internals, accessing API, caching sessions, changing application’s state, mocking network. Well designed Cypress tests can help you achieve a decent coverage with a small amount of tests.
Using a black-box approach throws away all the powers of Cypress and uses it simply as a test automation tool.
There’s also a question of maintenance and readability. Cypress commands are readable out of the box, and with some good practices applied, you can keep your tests lean and easy to maintain even with applications that change their behavior on a regular basis.
Cucumber uses step-based definitions that encapsule each series of commands into its own file. While they can also have a good readability, any change introduced to the application might require multiple steps to be redefined or added. This means that the bigger the system the harder it might be to introduce new changes. I’ve had a great explanation provided by Gleb Bahmutov on how introducing a change into Cucumber-based test might be challenging.
That all said, I wanted to create this tutorial so that you can effectively set up Cypress with Cucumber in case this is a requirement in your company. I still believe you can be successful even with this abstraction model, so let’s dive into it.
To start off, you need to install the cypress-cucumber-preprocessor plugin. There are currently multiple different versions flowing, but I believe this one is the best and it is actively maintained. You can install it by running the following command:
Besides installation of the preprocessor, plugin docs recommend installing the esbuild bundler by Gleb Bahmutov, which will make your run much faster.
After installation of these packages, you need to configure Cypress to use the plugins. The final configuration will look something like this:
There’s a lot to unpack here, so let’s go step by step.
The configuration file is written in TypeScript. A Javascript file might be a tiny bit simpler, but essentially contains all the same parts. We are importing different packages and adding them into our setupNodeEvents()
function.
The specPattern
attribute tells Cypress that we want to be looking for .feature
files in our e2e
folder. This means that it will ignore all other formats and only use .feature
files as our test.
addCucumberPreprocessorPlugin()
function takes care of digesting these .feature
files and convert them into Javascript. Since Cypress runs in browser, we need to make sure that everything we run (whether it is .ts
files .jsx
or other formats) will eventually get compiled into plain Javascript. This is what preprocessors do.
The on("file:preprocessor")
part takes care of combining the esbuild plugin with the cucumber plugin so they play nicely together.
The final return config
statement makes sure that everything we have set up will actually be set into our config. This step is oftentimes forgotten, so if your plugins ever behave as if they are not installed at all, check for this return statement.
Since the compilation into Javascript is an important part of working with
.feature
files, usually the intitial setup is the biggest hurdle to overcome. I find the setup from the docs the easiest one to work with, but if you are working with some other bundler, such as Webpack or Browserify, you can find examples here.
Now that we have the plugin installed and configured, let's explore how to write tests.
Let's start by writing a simple test scenario in Gherkin syntax. Create a new file cypress/e2e/board.feature
and add the following content:
Now, we need to create step definitions for each step in the scenario. The easiest way to define our steps is to create a new file called board.ts
in the cypress/e2e
folder, that may look something like this:
You can place your board.ts
definition file into cypress/e2e
folder, or choose a different name and put it into cypress/e2e/board
or into cypress/support/step_definitions
folder and cucumber preprocessor will automatically pick them up. For a custom path, you need to put explicitly state this in the configuration. We’ll get to the configuration later in this post.
To improve your experience when writing tests in VS Code, I recommend installing the extension by Alexander Krechik. It will give you proper highlighting in
.feature
files and easy access to step definitions.
Step definitions can accept parameters, allowing you to create more flexible and reusable test scenarios. Let’s rewrite our previous step definition file so that we can pass a board name of our own to our test:
The parameters are automatically passed to the corresponding step definition functions as arguments. Check out the {string}
in the step definition. This will actually check whether we are passing the proper type into our step.
Let's now create a scenario in our cypress/e2e/board.feature
file that accepts the boardName
as a parameter. It will look a little something like this:
Another important concept in the cucumber that you should know are data tables. DataTable
in Gherkin syntax allows you to pass a table of data to a step, making it easier to handle multiple data sets in your test scenarios. This is particularly useful for data-driven testing, where you want to test the same scenario with different sets of input data.
Data tables are defined in the Examples
section of your .feature
file. Let’s continue with our previous file:
With Examples
steps defined, you’ll run your test multiple times, passing different data with every step. Notice how we create variables boardName
and listName
, wrap them in <>
to be passed as parameters into our step definitions.
These data tables can also be used to feed data into a single step as shown in the following example:
The step however needs to be able to digest the datatable. This is how you can make it work:
The table.raw()[0]
function will return the first line ([0]
) of the table as an array. Inside the step definition, we are looping over this array to create items in the list.
In addition to Given
, When
, Then
and And
keywords, there are some other ways of how to organize multiple tests in a single .feature
file. Our test so far has been creating a new board and a new list, but let’s change our test slightly and create one test that will just create another board and put it in front of our existing test:
Similarly to describe()
, context()
and it()
blocks in Mocha, we can further organize our tests and group them into logical clusters. Feature
keyword acts as a describe()
block and serves as top level group.
Inside a Feature
scope, you can add a Rule
block, that would further split your scenarios into sub-groups.
As you test different scenarios, you can add a Background
step, that will act sort of like a beforeEach()
hook in Mocha and run a sequence of steps before every scenario. We can abstract our Given
and When
steps from our current .feature
file and make our test a little bit cleaner.
Together with a Rule
keyword, our test can look a little something like this:
While there is possibility to add Background
we can still define a Before
and After
steps, that act like beforeEach()
and afterEach()
hooks in Mocha. Failure in these will not make your tests fail as they are actually running inside your tests.
Before
and After
steps are part of your step definition file, which means you don’t need to add them into .feature
file.
Tags are a powerful feature in Cucumber syntax that allows you to categorize and filter scenarios. You can use tags to run specific scenarios or exclude scenarios from the test run.
To add tags to your scenarios, simply prefix the scenario or feature with an @ symbol followed by the tag name. For example, let's add a @regression tag to the successful login scenario in the cypress/e2e/board.feature file:
To run tests with a specific tag, use the following command:
This will skip the tests that do not contain the @smoke
tag.
You can also test this on open
mode using the same command but with open
instead of run
.
In addition to running all tests with certain tag, you can pass a not
keyword to run all tags exept specified one.
There’s also a way of running all tests that contain either one of tags:
Or tests that contain both:
To speed up the test execution, you can use filterSpecs
and omitFiltered
options that work similarly to how @cypress/grep
plugin works. You can enable this functionality by adding following options into your cypress.config.ts
file:
There are two way of how you can modify the default configuration of the Cucumber preprocessor. You can either create a .cypress-cucumber-preprocessorrc.json
config file that may look like this:
Or set everything up right in your package.json
by adding the equivalent:
The settings from examples are defaults. Unless you want to change anthing, there’s no need to add this to your project.
Cucumber plugin for Cypress comes with a variety of options for setting up reporters. I’m goint to show you the simplest one - HTML reporter.
Pretty much all you need to do is to set up your configuration:
After you run your test, you will get a nicely formatted HTML report that looks like this:
If you need a more advanced output that you later want to parse and feed into your own reporting system, I recommend checking out json-formatter
directly from cucumber authors. You will need to install it separately and set it up to run in your configuration file.
As I mentioned in the beginning, there are more effective ways of using Cypress. Doing everything in your end to end tests using UI is a slight overkill and given the design of Cypress, you can unlock much more power by using it the way it was intended.
However, I hope you found this blogpost useful if you are going to use Cucumber with Cypress. If you have any additional questions, feel free to reach out to me on Twitter or on LinkedIn. It would mean a world to me if you share this blogpost further.
From time to time I send some useful tips to your inbox and let you know about upcoming events. Sign up if you want to stay in loop.