Package detail

wdio-testgen-from-gherkin-js

amiya-pattnaik461MIT1.0.10

Automatically generate WebdriverIO Page Object classes and Mocha test specs from Gherkin

mocha, webdriverio, automation, ai test generator

readme

npm downloads Build Status Automation Level Node.js License: MIT

🤖 wdio-testgen-from-gherkin-js (WebdriverIO Test Generator from Gherkin JavaScript)

CLI and Node.js tool to auto-generate WebdriverIO Page Object classes and Mocha test specs from Gherkin .feature files using NLP for selector and method inference.

🚀 What It Does

  1. Step Map Generation
    Parses .feature files into structured .stepMap.json with fields like action, selectorName, selector, fallbackSelector, and note.

  2. Test Code Generation
    Uses step maps to generate:

    • 🧩 WebdriverIO Page Object Model (POM) classes
    • 🧪 Mocha test spec files

📦 Installation

Option 1: Clone for local development


git clone https://github.com/amiya-pattnaik/wdio-testgen-from-gherkin-js.git
cd wdio-testgen-from-gherkin-js
npm install

Option 2: Install from NPM

npm install wdio-testgen-from-gherkin-js

🧭 Directory Structure (local development through Option 1)

project-root/
├── features/                   # Gherkin .feature files (user input / source file)
├── stepMaps/                   # Auto-generated .stepMap.json files
├── test/                 
│   ├── pageobjects/            # Auto-generated WebdriverIO tests Page Object Model classes
│   └── specs/                  # Auto-generated Mocha test specs
├── src/
│   ├── cli.js                  # Main CLI logic
│   ├── generateStepsMap.js     # Feature-to-stepMap generator
│   ├── generateTestsFromMap.js # stepMap-to-page/spec generator
│   ├── utils.js                # Helper methods
│   └── config.js               # Paths, fallback selectors, aliases
│   └── __tests__/              # Unit tests (Vitest)
├── testgen.js                  # CLI entry point
│── wdio.config.js              # WebdriverIO configuration
├── package.json                # Scripts and dependencies
├── selector-aliases.json       # Optional user-defined selector overrides the primary selector

🚀 CLI Usage

🔹 Option A: # One-time setup

npm install -g
npm install -g tsx          # Required for CLI to run with node shebang
chmod +x testgen.js         # Make CLI executable (Mac/Linux)
npm link                    # If fails, try: sudo npm link

⚠️ Now run from anywhere

# Step 1: Generate stepMap.json from the .feature files
testgen steps --all
testgen steps --file login.feature

# Step 2: Generate test code (Page Objects and Mocha Specs) from stepMap.json
testgen tests --all
testgen tests --file login.stepMap.json
testgen tests --file login.stepMap.json --dry-run

# Step 3: Execute tests and generate Allure report
testgen run --report        # ⬅️ Runs tests and generate allure report
testgen run --report-only   # ⬅️ Generate report without rerunning testsbash

🔹 Option B: Local development (without global install)

# Step 1: Generate stepMap.json from the .feature files
npm run dev:testgen:steps -- --all                 
npm run dev:testgen:steps -- --file login.feature

# Step 2: Generate Page Objects and Mocha Specs from stepMap.json
npm run dev:testgen:tests -- --all
npm run dev:testgen:tests -- --file login.stepMap.json
npm run dev:testgen:tests -- --file login.stepMap.json --dry-run

# Step 3: Execute tests and generate Allure reoprt
npm run dev:testgen:run
npm run dev:testgen:run -- --report         # Run tests + generate report
npm run dev:testgen:run -- --report-only    # Just show last test run report

📜 Programmatic API Usage (through NPM package)

You can use wdio-testgen-from-gherkin-js package both as a CLI tool and as a Node.js module in custom scripts.

In your project workking directory like any other NPM modules install this package as npm install wdio-testgen-from-gherkin-js

Example: generate-tests.js

const { generateStepMaps, generateTestSpecs } = require('wdio-testgen-from-gherkin-js');

// Generate stepMap JSON files from .feature files
generateStepMaps({
  featuresPath: './features',      // path to your .feature files. Make sure features folder exist and has .feature files
  outputPath: './stepMaps',        // where to write stepMap JSONs
  watch: false,
  force: true
});

// Generate Page Object classes and Mocha test specs
generateTestSpecs({
  stepMapDir: './stepMaps',        // location of generated stepMaps
  outputDir: './test',             // base directory to create pageobjects/ and specs/
  dryRun: false,
  watch: false
});

NOTE: If you don't mention the stepMapDir path then stepMaps and test folder will be created under your project root directory.

Output Structure:

test/
├── pageobjects/
│   └── login.page.js
└── specs/
    └── login.spec.js

⚙️ Available Commands & Flags

testgen steps

Flag Description
--all Parse all feature files
--file Parse specific feature file(s)
--watch Watch for changes
--verbose Print detailed logs
--dry-run Show files that would be created
--force Overwrite existing stepMap files

testgen tests

Flag Description
--all Generate tests for all step maps
--file Generate tests for specific step maps
--watch Watch and regenerate on change
--verbose Print detailed logs
--dry-run Show files that would be created
--force Overwrite existing test files

testgen run

Flag Description
--report Generate Allure report after test run
--report-only Generate only Allure report (skip running tests)

📁 Minimal Example

features/login.feature

Feature: Login
  Scenario: Successful login
    Given I open the login page
    When I enter "admin" into the username field
    And I enter "adminpass" into the password field
    And I click the login button
    Then I should see the dashboard

Generated: stepMaps/login.stepMap.json

{
  "Successful login": [
    {
      "action": "setValue",
      "selectorName": "userNameField",
      "selector": "[data-testid=\"userNameField\"]",
      "fallbackSelector": "#username, input[name=\"username\"]",
      "note": "admin"
    },
    {
      "action": "setValue",
      "selectorName": "passwordField",
      "selector": "[data-testid=\"passwordField\"]",
      "fallbackSelector": "#password, input[type=\"password\"]",
      "note": "adminpass"
    },
    {
      "action": "click",
      "selectorName": "loginButton",
      "selector": "[data-testid=\"loginButton\"]",
      "fallbackSelector": "#login, button[type=\"submit\"]",
      "note": ""
    },
    {
      "action": "assertVisible",
      "selectorName": "dashboard",
      "selector": "[data-testid=\"dashboard\"]",
      "fallbackSelector": "",
      "note": ""
    }
  ]
}

Note: Additionally, ensure that you update the relevant selector for the DOM element of your application under test after generating your JSON file. This will serve as your foundation, and your page objects and test spec files will be constructed based on this data.

Generated: test/pageobjects/page.js

const { browser, $ } = require('@wdio/globals');

class Page {
  open(path) {
    return browser.url(`https://the-internet.herokuapp.com/${path}`);
  }

  async trySelector(primary, fallbacks) {
    try {
      const el = await $(primary);
      if (await el.isExisting() && await el.isDisplayed()) {
        console.log(`✅ Using primary selector: ${primary}`);
        return el;
      }
    } catch (e) {
      console.warn(`⚠️ Failed to find element with primary selector: ${primary}`);
    }
    for (const sel of fallbacks) {
      try {
        const fallback = await $(sel);
        if (await fallback.isExisting() && await fallback.isDisplayed()) {
          console.log(`↪️ Using fallback selector: ${sel}`);
          return fallback;
        }
      } catch {}
    }
    throw new Error(`❌ All selectors failed:\nPrimary: ${primary}\nFallbacks: ${fallbacks.join(', ')}`);
  }
}

module.exports = Page;

Generated: test/pageobjects/login.page.js

const Page = require('./page');
class LoginPage extends Page {
  get loginbutton() {
    return this.trySelector('button.login-btn', ['#login', 'button[type="submit"]']);
  }
  get usernamefield() {
    return this.trySelector('#login-username-amiya', ['#username', 'input[name="username"]']);
  }
  get passwordfield() {
    return this.trySelector('#login-password-Patt', ['#password', 'input[type="password"]']);
  }
  get welcomebanner() {
    return this.trySelector('[data-testid="welcomeBanner"]', ['#welcome-message', '.welcome']);
  }
  async mySuccessfulLogin() {
    await (await this.usernamefield).setValue('');
    await (await this.passwordfield).setValue('');
    await (await this.loginbutton).click();
    await expect(await this.welcomebanner).toBeDisplayed();
  }
  open(pathSegment = 'login') {
    return super.open(pathSegment);
  }
}
module.exports = new LoginPage();

Generated: test/specs/login.spec.js

const { expect } = require('@wdio/globals');
const LoginPage = require('../pageobjects/login.page');
describe('login feature tests', () => {
  it('mySuccessfulLogin', async () => {
    await LoginPage.open();
    await (await LoginPage.usernamefield).setValue('');
    await (await LoginPage.passwordfield).setValue('');
    await (await LoginPage.loginbutton).click();
    await expect(await LoginPage.welcomebanner).toBeDisplayed();
    // Or simply use:
    // await LoginPage.mySuccessfulLogin();
  });
});

Note: It is recommended to examine the generated code and implement any required adjustments to meet your needs, such as invoking methods from test spec files to the page class, incorporating reusable methods, renaming selector name, method name (if any) and managing your test data etc.


✅ Features Implemented

🔁 1. Two-Step Test Generation Flow

  • Step 1: Parse .feature files and generate scenario-wise stepMap.json.
  • Step 2: Use stepMap.json to auto-generate:
    • WebdriverIO Page Object classes.
    • Mocha test spec files.

🧠 2. AI/NLP-Driven Selector Name Inference

  • Uses the compromise NLP library to generate meaningful selector, method names based on verbs/nouns in step text.
  • Example:
    "When user clicks login"selectorName: "clicklogin"

🧠 3. Logical Selector + Fallback Selector with priority

  • Applies regex-based matching to map common UI elements to logical names:

    • e.g., usernameuserNameField
    • loginloginButton
  • Logical names are mapped to selector and fallbackSelector:

    {
      "selector": "[data-testid=\"loginButton\"]",
      "fallbackSelector": "#login, button[type=\"submit\"]",
    }

    The fallbackSelector is a palce holder for containing more than one alternative selector. At the run time if the primary selector (i.e. "selector": "[data-testid=\"loginButton\"]") fails to locate the element, it will log ⚠️ Failed to find element with primary selector, and then it will pick one of the alternative selctor mentioned in the fallbackSelector. If it finds the right selector it will log ↪️ Using fallback selector. If none of the alternative selector found, then it will trrow error ❌ All selectors failed.

🔄 4. User-Defined Selector Aliases (Optional)

  • Optional file: selector-aliases.json. When implemented it overrides the default primary selector ("selector": "#login-username",) of the generated .stepMap.json. If you don't need the selector-aliases.json then either you rename it or delete it from the root.

    {
    "userNameField": "#login-username",
    "loginButton": "#login-btn"
    }

    Priority Order:

    1. Selector aliases (selector-aliases.json), if exists it will take the first priority over the regex-based default selector generated by tool.
    2. Fallback selector

🧪 5. Action Inference Engine

Automatically extracts values from steps:

When user enters "admin" in the username field

→ action: "setValue", note: "admin"

Gherkin Step Example Action Notes
When user enters "admin" setValue "admin"
When user clicks login Click
Then user should see the welcome message assertVisible
Then title should be "Dashboard" assertTitle "Dashboard"
Then url should contain "success" assertUrlContains

🧠 Supported Actions Example

Supports a wide range of actions: setValue, click, selectDropdown, uploadFile, hover, clearText, scrollTo, assertVisible, assertText, assertEnabled, assertDisabled, assertTitle, assertUrlContains, etc.

Action Description
setValue Sets input value
click Clicks on the element
hover Hovers over an element
doubleClick Performs a double-click
selectDropdown Selects dropdown option by visible text
uploadFile Uploads a file
scrollTo Scrolls element into view
assertVisible Validates visibility of element
assertText Checks element text
clearText Clears input field
assertEnabled Validates element is enabled
assertDisabled Validates element is disabled
assertTitle Validates page title
assertUrlContains Checks partial match on URL
waitForVisible Waits until element is visible

Please be advised that any unrecognized actions have been commented out in the generated code for your review. Should you wish to include any additional actions, kindly refer to the source code (src\) and incorporate the necessary actions, which is quite straightforward. You may utilize any WebdriverIO commands as needed.


🧰 Troubleshooting

Error: command not found: testgen
✅ Run npm link again inside the project root.

Error: env: tsx: No such file or directory
✅ Install tsx globally: npm install -g tsx

Error: ENOENT: no such file or directory, open 'package.json'
✅ You’re running npm run outside the project — run from root.


🔗 Related

📢 Releases and Feedback

Check out the Releases tab for version history and improvements.

Want to discuss features or share feedback? Use GitHub Discussions or open an issue.

🧑 Author

Amiya Pattanaik

For issues, enhancements or feature requests, open an issue.