npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

cucumber-steps-generator

v1.0.9

Published

cucumber steps generator

Downloads

25

Readme

Cucumber steps generator

Package provides a DRY way of creating cucumber steps by combining predefined set of actions, targets and areas. Targets and areas contain xpath selectors while actions contain nightwatch commands. In gherkin it will read as "action applied to target that is located in area": Given I click button "Submit" in modal would be composed out of I click action, button "Submit" target and in modal area. The power of this tool is in automatic generation of all configured combinations for actions, targets and areas.

Installation

npm i -D cucumber-steps-generator

Dependencies

  1. nigthwatch-api
  2. cucumber

Steps generator configuration

to generate steps create generate.js, config.js, actions.js, targets.js and areas.js and run:
node ./e2e/stepsGenerator/generate.js

//generate.js
const path = require('path')
const { generateSteps } = require('cucumber-steps-generator')

generateSteps({
  outputFile: {
    path: path.resolve(__dirname, './generatedSteps.js'),
    injections: {
      helpers: path.resolve(__dirname, '../helpers.js') // possibility to inject custom modules
    }
  },
  configPath: path.resolve(__dirname, './config.js'),
  actionsPath: path.resolve(__dirname, './actions.js'),
  targetsPath: path.resolve(__dirname, './targets.js'),
  areasPath: path.resolve(__dirname, './areas.js')
})
//config.js
const { extendXpath } = require('cucumber-steps-generator')
const testEnv = process.env.env || 'dev'

module.exports = {
  variables: {
    url: ['qa, stg'].includes(testEnv) ?  `https://sitename-${testEnv}.domain.com` : 'localhost:8080',
  },
  // this section provides all possible set of selectors/selector functions for the website
  xpath: {
    button: {
      // extendXpath adds functions like has-text, has-class and text-is (see utils section for details)
      withTextOrClass: text => extendXpath(`//button[has-text("${text}") or has-class("${text}")]`),
    },
    input: {
      withName: name => extendXpath(`//*[(self::input or self::textarea) and @name="${name}"]`),
    },
    select: {
      withName: name => extendXpath(`//select/option[text-is("${name}")]/parent::select`)
    },
    navbar: {
      top: extendXpath(`//nav[has-class("navbar") and has-class("sticky-top")]`),
    },
    modal: {
      content: extendXpath(`//div[has-class("modal-dialog")]`),
    },
    link: {
      withText: text => extendXpath(`//a[has-text("${text}")]`),
    }
  }
}
//actions.js
module.exports = {
  followUrl: {
    // {string} is placeholder. In gherkin it may look: Given I redirect to "/home"
    gherkin: 'I redirect to {string}',
    // empty targets array means that action can not be used with targets
    targets: [],
    // func takes ctx(read more in context section) as first arg and then arguments for each placeholder in gherkin part so 'path' here is "/home"
    func: ({ client, variables }, path) => client.useXpath().url(variables.url + path)
  },
  type: {
    // {target} placeholder is used for inserting defined targets: I type "Alex" in input "name"
    gherkin: 'I type {string} in {target}',
    // provided targets mean that action can be used only with targets having mentioned key
    targets: ['inputWithName'],
    // here value is "Alex" and target is `input "name"` which corresponds to key 'inputWithName' in targets.js file
    func: ({ client }, value, target) => client.useXpath().setValue(target, value)
  },
  xpathClick: {
    gherkin: 'I click {target}',
    // not defining targets here indicates that action could be appliead to any target
    func: ({ client }, target) => client.useXpath().click(target)
  }
}
//targets.js
module.exports = {
  link: {
    gherkin: 'link {string}', // {string} is placeholder. In gherkin it may look: And I click link "click here"
    // areas work the same way as in actions. Areas specify where target is located to solve multiple DOM matches
    areas: ['modal'],
    // func takes ctx(read more in context section) as first arg and then arguments for each placeholder in gherkin part so 'text' here is "click here"
    // xpath here refers to config.js xpath definitions
    func: ({ xpath }, text) => xpath.link.withText(text)
  },
  buttonWithTextOrClass: {
    gherkin: 'button {string}',
    func: ({ xpath }, text) => xpath.button.withTextOrClass(text)
  },
  inputWithName: {
    gherkin: 'input {string}',
    func: ({ xpath }, name) => xpath.input.withName(name)
  }
}
//areas.js
module.exports = {
  topNavbar: {
    gherkin: 'in top navbar',
    // xpath here refers to config.js xpath definitions
    func: ({ xpath }) => xpath.navbar.top
  },
  modal: {
    gherkin: 'in modal',
    func: ({ xpath }) => xpath.modal.content
  },
  hamburgerMenu: {
    gherkin: 'in hamburger menu',
    func: ({ xpath }) => xpath.burger.menuSidebar
  }
}

Part of the output file for the given configuration:

// action followUrl
Then(/^I redirect to "([^"]+)"$/, (arg1) => {
  return (({ client, variables }, path) => client.useXpath().url(variables.url + path))(context, arg1)
})
// action type on target inputWithName
Then(/^I type "([^"]+)" in input "([^"]+)"$/, (arg1, arg2) => {
  const path = (({ xpath }, name) => xpath.input.withName(name))(context, arg2)
  return (({ client }, value, target) => client.useXpath().setValue(target, value))(context, arg1, path)
})
// action type on target inputWithName in area topNavbar
Then(/^I type "([^"]+)" in input "([^"]+)" in top navbar$/, (arg1, arg2) => {
  const path = (({ xpath }) => xpath.navbar.top)(context) + (({ xpath }, name) => xpath.input.withName(name))(context, arg2)
  return (({ client }, value, target) => client.useXpath().setValue(target, value))(context, arg1, path)
})
// action type on target inputWithName in area modal
Then(/^I type "([^"]+)" in input "([^"]+)" in modal$/, (arg1, arg2) => {
  const path = (({ xpath }) => xpath.modal.content)(context) + (({ xpath }, name) => xpath.input.withName(name))(context, arg2)
  return (({ client }, value, target) => client.useXpath().setValue(target, value))(context, arg1, path)
})
// action type on target inputWithName in area hamburgerMenu
Then(/^I type "([^"]+)" in input "([^"]+)" in hamburger menu$/, (arg1, arg2) => {
  const path = (({ xpath }) => xpath.burger.menuSidebar)(context) + (({ xpath }, name) => xpath.input.withName(name))(context, arg2)
  return (({ client }, value, target) => client.useXpath().setValue(target, value))(context, arg1, path)
})
// action xpathClick on target link
Then(/^I click link "([^"]+)"$/, (arg1) => {
  const path = (({ xpath }, text) => xpath.link.withText(text))(context, arg1)
  return (({ client }, target) => client.useXpath().click(target))(context, path)
})
// action xpathClick on target link in area modal
Then(/^I click link "([^"]+)" in modal$/, (arg1) => {
  const path = (({ xpath }) => xpath.modal.content)(context) + (({ xpath }, text) => xpath.link.withText(text))(context, arg1)
  return (({ client }, target) => client.useXpath().click(target))(context, path)
})
// action xpathClick on target buttonWithTextOrClass
Then(/^I click button "([^"]+)"$/, (arg1) => {
  const path = (({ xpath }, text) => xpath.button.withTextOrClass(text))(context, arg1)
  return (({ client }, target) => client.useXpath().click(target))(context, path)
})
// action xpathClick on target buttonWithTextOrClass in area topNavbar
Then(/^I click button "([^"]+)" in top navbar$/, (arg1) => {
  const path = (({ xpath }) => xpath.navbar.top)(context) + (({ xpath }, text) => xpath.button.withTextOrClass(text))(context, arg1)
  return (({ client }, target) => client.useXpath().click(target))(context, path)
})
// action xpathClick on target buttonWithTextOrClass in area modal
Then(/^I click button "([^"]+)" in modal$/, (arg1) => {
  const path = (({ xpath }) => xpath.modal.content)(context) + (({ xpath }, text) => xpath.button.withTextOrClass(text))(context, arg1)
  return (({ client }, target) => client.useXpath().click(target))(context, path)
})
// action xpathClick on target buttonWithTextOrClass in area hamburgerMenu
Then(/^I click button "([^"]+)" in hamburger menu$/, (arg1) => {
  const path = (({ xpath }) => xpath.burger.menuSidebar)(context) + (({ xpath }, text) => xpath.button.withTextOrClass(text))(context, arg1)
  return (({ client }, target) => client.useXpath().click(target))(context, path)
})
// action xpathClick on target inputWithName
Then(/^I click input "([^"]+)"$/, (arg1) => {
  const path = (({ xpath }, name) => xpath.input.withName(name))(context, arg1)
  return (({ client }, target) => client.useXpath().click(target))(context, path)
})
// action xpathClick on target inputWithName in area topNavbar
Then(/^I click input "([^"]+)" in top navbar$/, (arg1) => {
  const path = (({ xpath }) => xpath.navbar.top)(context) + (({ xpath }, name) => xpath.input.withName(name))(context, arg1)
  return (({ client }, target) => client.useXpath().click(target))(context, path)
})
// action xpathClick on target inputWithName in area modal
Then(/^I click input "([^"]+)" in modal$/, (arg1) => {
  const path = (({ xpath }) => xpath.modal.content)(context) + (({ xpath }, name) => xpath.input.withName(name))(context, arg1)
  return (({ client }, target) => client.useXpath().click(target))(context, path)
})
// action xpathClick on target inputWithName in area hamburgerMenu
Then(/^I click input "([^"]+)" in hamburger menu$/, (arg1) => {
  const path = (({ xpath }) => xpath.burger.menuSidebar)(context) + (({ xpath }, name) => xpath.input.withName(name))(context, arg1)
  return (({ client }, target) => client.useXpath().click(target))(context, path)
})

Context

A context object is passed as first argument to action, target and area functions.
Context can contain:

  • client - nightwatch client contains all the nightwatch functions. Used in actions.
  • xpath - refers to xpath section of config.js. Contains full list of xpath selectors for the website.
  • variables - refers to variables section of config.js. Might be usefull when shared state is required between the sessions or for defining constants.
  • utils - a set of built in function (see section below)
  • injections - if provided in generate.js exposes custom modules in steps

Utils

  • extendXpath - extends xpath string with functions (see config.js for examples):
    • has-class("className") - maches elements, class of which contain className class,
    • has-text("textPart") - maches elements, text of which contain textPart text,
    • text-is("textExact") - maches elements that have text === textExact
      N.B: all above functions has to have doublequotes around argument and argument iself should be a string that doesnt contain doublequotes inside (not even escaped)
  • xpathToFileName - converts xpath strings into readable valid filenames. Can be usefull to name file after xpath selector when taking screenshots.
    //*[contains(concat(" ", normalize-space(@class), " "), " Active ")] converts to el_with_class_active
  • lastElement - returns last element matched given xpath
  • elementAtPosition - returns element at given position matched given xpath
  • illegalFilenameCharactersRegExp - regExp for maching illegal filenames

Todos:

  • Area doesnt not work for multiple targets declared with 'OR': target = target1 | target2
    It renders: //area//target1 | target2 instead of //area//target1 | //area//target2
    Workaround for now: 'OR' should be declared in a single target selector: //div[@target="1" or @target="2"]
  • Cover action, target, area combinations with unit tests
  • Decouple from nightwatch