@jambit/wdio-cucumber-selected-steps

Cucumber step definition basics for easy import

Usage no npm install needed!

<script type="module">
  import jambitWdioCucumberSelectedSteps from 'https://cdn.skypack.dev/@jambit/wdio-cucumber-selected-steps';
</script>

README

Description

This library ships a set of common steps for writing cucumber feature files for use with webdriver.io.

It is based on the official boilerplate, but has been rewritten to bring several improvements:

  • Cleaned up sentences
    • Proper english
    • Each sentence can be used correctly in any sentence type (Given, When, Then and And).
  • No need to write CSS and XPath selectors in .feature files.
    • Files are written in separate selector files instead: json, yaml and javascript modules (default export) are supported.
    • This makes sentences more readable for non-developers.
    • This allows to change selectors at one place rather than in multiple .feature files when the selectors change.
    • You can even combine multiple selector names in the sentences to select child elements.
      • for example: 'User Table -> User Row X -> Role Column (where each of the parts separated by " -> " are individual selectors)
  • Values extracted from the regular expression are passed through converters before they are passed to the callback.
    • For example to convert text to boolean, integer or floating point values.
    • string, int, float, bool, selector, element and elements are supported out of the box.
    • You can write your own.
  • Support logic for built-in steps can be reused in custom step definitions.
  • For example if you write a login step definition it can use the setValue() and click() support helpers.

How to Use

Requirements

Webdriver.io V7 with Cucumber Framework.

Setup

Take a look at the sample project

The most important steps are:

  • Install the project: npm install @jambit/wdio-cucumber-selected-steps
  • Install babel (@babel/core, @babel/preset-env and @babel/register)
  • Adjust the wdio.conf.js file (see below)

wdio.conf.js Adjustments

// This is needed, since the project uses ES6 modules.
require("@babel/register")({
    // This will override `node_modules` ignoring - you can alternatively pass
    // an array of strings to be explicitly matched or a regex / glob
    ignore: [
        // Gherkin, however, is not compatible with babel, so ignore it.
        'node_modules/gherkin/**/*.js'
    ],
});

// At the top:
const setupSelectors = require('@jambit/wdio-cucumber-selected-steps').setupSelectors;

exports.config = {
    //...
    cucumberOpts: {
        //...
        require: [
            // Add the selected steps to cucumber:
            './node_modules/@jambit/wdio-cucumber-selected-steps/lib/steps/*.js',
            // ...
        ],
        //...
    },
    // ...
    before: function (capabilities, specs) {
        // Setup the path to your selectors:
        setupSelectors([
            './src/selectors/*.js'
        ]);
        // ...
    },

wdio.conf.js Adjustments for Selector Replacements

In some cases, you want to set up some variables in the config to be used in the test. This can be useful when you need to create data on a remote server during tests and later access them. If you need those variables to be in the selector files, you can now easily do this!

const { setupSelectors, setupTextMethod } = require('@jambit/wdio-cucumber-selected-steps');

exports.config = {
    // ...
    before: function (capabilities, specs) {
        // Setup the path to your selectors:
        setupSelectors(...);
        setSelectorVariable("TEST_ID", Date.now().toString());
        // ...
    },

In your selector files, you can now use {{TEST_ID}} inside of your selector files:

---
My Selector: ".data-1-{{TEST_ID}}"
My Selector Nested:
  foo: ".data-2-{{TEST_ID}}"
  bar: ".data-3-{{TEST_ID}}"

Any value, which is a text (even in a nested object or array) will be replaced with the defined replacement. You can even specify a function to generate a value:

        setSelectorVariable("RANDOM", () => Math.random());

This function will be called freshly for every text value to be replaced.

wdio.conf.js Adjustments for Text Method

Additionally, you can configure the text method used. By default, wdio uses element.getText(), which will return the transformed text. I.e. if you set your css to upper-case some text, you will get the upper-case version of the text. If you want to get the non-transformed version, you would have to test the attribute textContent. To make this more convenient, you can configure the default behavior.

const { setupSelectors, setupTextMethod } = require('@jambit/wdio-cucumber-selected-steps');

exports.config = {
    // ...
    before: function (capabilities, specs) {
        // Setup the path to your selectors:
        setupSelectors(...);
        setupTextMethod('textContent');
        // ...
    },

If you want to use this logic in your custom steps, use our getText() helper.

Creating Selector Files

In src/selectors (of course, you can change the path), create .js files that export a map as default like this:

export default {
    'Header': '#header',
    'Header - Username': '#header .username',
    'Edit User Dialog - Title': '//h3[@class=\'.dialog__title\' and text()=\'Edit User\']',
};

You can write these files as .json or .yaml as well. Writing it in javascript has the advantage, that you can use functions as selectors as well.

The key can be used in .feature files wherever paramType.selector/element/elements is used:

  • paramType.selector will pass this value through getSelector(), which will return the value from the selector files.
  • paramType.element will pass this value through elementQuery(), which will return a function, that returns the WebdriverIO.Element.
  • paramType.elements will pass this value through elementsQuery(), which will return a function, that returns WebdriverIO.Element[].
  • paramType.element and paramType.elements also override toString(), so it can easily be used to print out the selector key (i.e. what is written in the .feature files ).

Visual Studio Code Support:

If you use the Cucumber (Gherkin) Full Support extension, you can set it up like this:

{
    "cucumberautocomplete.steps": [
        "node_modules/@jambit/wdio-cucumber-selected-steps/lib/steps/*.js",
        "cucumberautocomplete.syncfeatures": "src/features/*.feature",
        "src/steps/*.js"
    ],
    "cucumberautocomplete.gherkinDefinitionPart": "(Given|When|Then|defineTypedStep)\\("
}

Using Steps

Assuming you know Gherkin syntax, you should be ready to go. Here is an example:

    Given I open the url "/"
    And   the element "Header" exists
    And   the element "Header - Username" matches the text "Zaphod Beeblebrox"
    When  I click on the element "Header - Username"
    And   I wait for the element "Edit User Dialog - Title" to exist
    Then  ...

List of Included Steps

All of these steps can be used with Given, When, Then and And.

alert

cookies

delay

elements

windows

Writing Your Own Steps

Support Helpers

The step definitions above implement their logic in methods found in the src/support folder. These methods can be reused, if you want to write your own step definitions. Have a look at the docs file for a list of methods.

These methods can be imported like this:

import { selectOption } from '@jambit/wdio-cucumber-selected-steps/lib/support';

Writing Your Own Steps

Say, you want to do a bit more than just one of the elemental things above. Just write your own step definition to re-use that logic.

Example login step (ideally split into separate files, but here in one for simplicity):

import { defineTypedStep, setValue, paramType, click } from '@jambit/wdio-cucumber-selected-steps/lib/support';
// ...

const loginStep = (user) => {
    const credentials = CREDENTIALS[user];
    if (!credentials) {
        throw new Error("User " + user + " is not implemented!");
    }
    setValue(getSelector('Login Page - Login Name Input'), credentials.user);
    setValue(getSelector('Login Page - Login Password Input'), credentials.password);
    click('click', getSelector('Login Page - Submit'));
};

defineTypedStep(
    /^I log in as "(user|admin)"$/,
    [
        paramType.string,
    ],
    loginStep,
);

defineTypedStep is a special method that takes these parameters:

  • {RegExp | string} pattern The regular expression used to match the step
  • {ParamType[]} types The param types used to map the expression matches to actual values
  • {StepDefinitionOptions} options The options to use (optional)
  • {StepDefinitionCode} code The function to execute

Apart from the types array, this is the same as in normal cucumber. The types array is used to perform a transformation of the strings that the regex returns.

These are the built-in ParamTypes. Use <property>.optional instead if the parameter is optional (for example paramType.string.optional).

  • paramType.string The parameter is expected to be a string. No conversion done.
  • paramType.int The parameter is expected to be an integer. parseInt() is applied.
  • paramType.float The parameter is expected to be a float. parseFloat() is applied.
  • paramType.bool The parameter is expected to be a boolean value. !! is applied.
  • paramType.selector The parameter is expected to be a selector key. getSelector() is applied.
  • paramType.element The parameter is expected to be a selector key. elementQuery() is applied.
  • paramType.elements The parameter is expected to be a selector key. elementsQuery() is applied.

If your boolean value is one of two strings, for example (contains|does not contain), then you can specify this like this:

// Either:
paramType.bool.setTrue('does not contain'),
// Or:
paramType.bool.setFalse('contains'),

If you want to avoid always adding the same prefix/suffix for a paramType.selector/element(s), you can use the .format() option:

paramType.selector.format("Prefix{{VALUE}}Suffix"),

In this case, if your value received from the .feature file was "FooBar", then it would become "PrefixFooBarSuffix" before being evaluated.

You can even write your own paramTypes. Just have a look at the built-in ones: src/support/paramType.ts.

For samples of how steps can look, just take a look at the src/steps folder of this library. The logic implementation resides in the src/support folder.

Documenting Your Own Steps

The documentation for the steps included in this project are automatically generated using the package @jambit/wdio-cucumber-selected-steps-stepdoc. This tool was tailored to be used with this project, but you might be able to use it for your tests as well.

License

Licensed under MIT