wdio-testgen-from-gherkin-ts
v1.0.3
Published
Automatically generate WebdriverIO Page Object classes and Mocha test specs from Gherkin
Maintainers
Readme
🤖 wdio-testgen-from-gherkin-ts (WebdriverIO Test Generator from Gherkin TypeScript)
CLI tool to generate WebdriverIO Page Objects, utilize AI/NLP for Selector Name, Method Name Inference, and generate Mocha Specs from Gherkin
.featurefiles.
🚀. What It Does:
Generate Step Maps: Parses Gherkin feature files to produce structured .stepMap.json files which contains -
action,selectorName,selector,fallbackSelector,note.Generate Tests: Uses the .stepMap.json to generate:
WebdriverIO-compatible Page Object Model (POM) classes.Mocha-based test specs.
📦 Installation
Option 1: Clone for local development
git clone https://github.com/amiya-pattnaik/wdio-testgen-from-gherkin-ts.git
cd wdio-testgen-from-gherkin-ts
npm installOption 2: Install from NPM
npm i wdio-testgen-from-gherkin-ts -D 🧭 Directory Structure
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.ts # Main CLI logic
│ ├── generateStepsMap.ts # Feature-to-stepMap generator
│ ├── generateTestsFromMap.ts # stepMap-to-page/spec generator
│ ├── utils.ts # Helper methods
│ └── config.ts # Paths, fallback selectors, aliases
│ └── __tests__/ # Unit tests (Vitest)
├── testgen.ts # CLI entry point
│── tsconfig.json # TypeScript configuration
│── vitest.config.ts # Vitest unit testing configuration - Optional
│── wdio.config.ts # WebdriverIO configuration
├── package.json # Scripts and dependencies
├── selector-aliases.json # Optional user-defined selector overrides the primary selector
Note: Above directory structure when using Option 1: Clone for local development🚀 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 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-ts
Example: generate.ts
import { processSteps, processTests } from 'wdio-testgen-from-gherkin-ts';
processSteps({ all: true, force: true, verbose: true });
processTests({ all: true, force: true, verbose: true });Run it with:
npx tsx generate.tsAvailable Options
Both processSteps() and processTests() accept:
all: true– generate for all filesfile: ['filename']– generate for specific fileforce: true– overwrite if file existsdryRun: true– preview output onlywatch: true– auto-regenerate on file changesverbose: true– detailed logs
🗂 Folder Structure Assumption
Your root folder should contain:
features/ # Gherkin feature files
stepMaps/ # Will be generated
selector-aliases.json # OptionalThe generated output goes to:
test/
├─ pageobjects/
└─ specs/⚙️ 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 dashboardGenerated: 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.ts
import { browser, $ } from '@wdio/globals';
export default class Page {
open(path: string) {
return browser.url(`https://the-internet.herokuapp.com/${path}`);
}
async trySelector(primarySelector: string, fallbackSelectors: string[]) {
try {
const primary = await $(primarySelector);
if (await primary.isExisting() && await primary.isDisplayed()) {
console.log(`✅ Using primary selector: ${primarySelector}`);
return primary;
}
} catch (e) {
console.warn(`⚠️ Failed to find element with primary selector: ${primarySelector}`);
}
for (const selector of fallbackSelectors) {
try {
const alt = await $(selector);
if (await alt.isExisting() && await alt.isDisplayed()) {
console.log(`↪️ Using fallback selector: ${selector}`);
return alt;
}
} catch (e) {}
}
throw new Error(`❌ All selectors failed:\nPrimary: ${primarySelector}\nFallbacks: ${fallbackSelectors.join(', ')}`);
}
}Generated: test/pageobjects/login.page.ts
import Page from './page';
class LoginPage extends Page {
get userNameField() {
return this.trySelector('[data-testid="userNameField"]', ['#username', 'input[name="username"]']);
}
get passwordField() {
return this.trySelector('[data-testid="passwordField"]', ['#password', 'input[type="password"]']);
}
get loginButton() {
return this.trySelector('[data-testid="loginButton"]', ['#login', 'button[type="submit"]']);
}
get dashboard() {
return this.trySelector('[data-testid="dashboard"]', []);
}
async successfulLogin() {
await (await this.userNameField).setValue('admin');
await (await this.passwordField).setValue('adminpass');
await (await this.loginButton).click();
await expect(await this.dashboard).toBeDisplayed();
}
open(pathSegment: string = 'login') {
return super.open(pathSegment);
}
}
export default new LoginPage();Generated: test/specs/login.spec.ts
import { expect } from '@wdio/globals';
import LoginPage from '../pageobjects/login.page';
describe('login feature tests', () => {
it('successfulLogin', async () => {
await LoginPage.open();
await (await LoginPage.userNameField).setValue('admin');
await (await LoginPage.passwordField).setValue('adminpass');
await (await LoginPage.loginButton).click();
await expect(await LoginPage.dashboard).toBeDisplayed();
// Or simply use:
// await LoginPage.successfulLogin();
});
});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.
✅ Unit Testing (optional)
npm run vitestVitest is used for testing core logic in
src/utils.ts.
✅ Features Implemented
🔁 1. Two-Step Test Generation Flow
- Step 1: Parse
.featurefiles and generate scenario-wisestepMap.json. - Step 2: Use
stepMap.jsonto auto-generate:- WebdriverIO Page Object classes.
- Mocha test spec files.
🧠 2. AI/NLP-Driven Selector Name Inference
- Uses the
compromiseNLP library to generate meaningful selector, method names based on verbs/nouns in step text. - Example:
"When user clicks login"→selectorName: "clicklogin"
🧠 3. Logical Selector Mapping with Fallback Selector
Applies regex-based matching to map common UI elements to logical names:
- e.g.,
username→userNameField login→loginButton
- e.g.,
Logical names are mapped to selector and fallbackSelector:
{ "selector": "[data-testid=\"loginButton\"]", "fallbackSelector": "#login, button[type=\"submit\"]", }The
fallbackSelectoris 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 thefallbackSelector. 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:
- Selector aliases (selector-aliases.json), if exists it will take the first priority over the regex-based default
selectorgenerated by tool. - 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
- JS version:
wdio-testgen-from-gherkin-js - TS version: This repo/package
📢 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.
