cucumber-step-definitions
$
npx mdskill add TheBushidoCollective/han/cucumber-step-definitionsCraft maintainable step definitions for Cucumber tests
- Transforms Gherkin scenarios into executable code
- Supports JavaScript/TypeScript and Java implementations
- Generates reusable test logic from feature files
- Outputs formatted code ready for integration
SKILL.md
.github/skills/cucumber-step-definitionsView on GitHub ↗
---
name: cucumber-step-definitions
user-invocable: false
description: Writing effective step definitions and organizing test code
---
# Cucumber Step Definitions
Master writing maintainable and reusable step definitions for Cucumber tests.
## Basic Step Definitions
Define steps that match Gherkin syntax:
### JavaScript/TypeScript (Cucumber.js)
```javascript
const { Given, When, Then } = require('@cucumber/cucumber');
Given('I am on the login page', async function() {
await this.page.goto('/login');
});
When('I enter valid credentials', async function() {
await this.page.fill('#username', 'testuser');
await this.page.fill('#password', 'password123');
});
Then('I should be logged in', async function() {
const welcomeMessage = await this.page.textContent('.welcome');
expect(welcomeMessage).toContain('Welcome, testuser');
});
```
### Java (Cucumber-JVM)
```java
import io.cucumber.java.en.*;
import static org.junit.Assert.*;
public class LoginSteps {
@Given("I am on the login page")
public void i_am_on_login_page() {
driver.get("http://example.com/login");
}
@When("I enter valid credentials")
public void i_enter_valid_credentials() {
driver.findElement(By.id("username")).sendKeys("testuser");
driver.findElement(By.id("password")).sendKeys("password123");
}
@Then("I should be logged in")
public void i_should_be_logged_in() {
String welcome = driver.findElement(By.className("welcome")).getText();
assertTrue(welcome.contains("Welcome, testuser"));
}
}
```
### Ruby
```ruby
Given('I am on the login page') do
visit '/login'
end
When('I enter valid credentials') do
fill_in 'username', with: 'testuser'
fill_in 'password', with: 'password123'
end
Then('I should be logged in') do
expect(page).to have_content('Welcome, testuser')
end
```
## Parameterized Steps
Capture values from Gherkin steps:
```javascript
// Scenario: I search for "Cucumber" in the search bar
When('I search for {string} in the search bar', async function(searchTerm) {
await this.page.fill('#search', searchTerm);
await this.page.click('#search-button');
});
// Scenario: I add 5 items to my cart
When('I add {int} items to my cart', async function(quantity) {
for (let i = 0; i < quantity; i++) {
await this.addItemToCart();
}
});
// Scenario: The price should be $99.99
Then('the price should be ${float}', async function(expectedPrice) {
const actualPrice = await this.page.textContent('.price');
expect(parseFloat(actualPrice)).toBe(expectedPrice);
});
```
## Regular Expressions
Use regex for flexible matching:
```javascript
// Matches: "I wait 5 seconds", "I wait 10 seconds"
When(/^I wait (\d+) seconds?$/, async function(seconds) {
await this.page.waitForTimeout(seconds * 1000);
});
// Matches: "I should see a success message", "I should see an error message"
Then(/^I should see (?:a|an) (success|error) message$/, async function(type) {
const message = await this.page.textContent(`.${type}-message`);
expect(message).toBeTruthy();
});
```
## Data Tables
Handle tabular data in steps:
```javascript
When('I create a user with the following details:', async function(dataTable) {
// dataTable.hashes() converts to array of objects
const users = dataTable.hashes();
for (const user of users) {
await this.api.createUser({
firstName: user['First Name'],
lastName: user['Last Name'],
email: user['Email']
});
}
});
// Alternative: dataTable.raw() for raw 2D array
When('I select the following options:', async function(dataTable) {
const options = dataTable.raw().flat(); // ['Option1', 'Option2']
for (const option of options) {
await this.page.check(`input[value="${option}"]`);
}
});
```
## Doc Strings
Handle multi-line text:
```javascript
When('I submit a message:', async function(messageText) {
await this.page.fill('#message', messageText);
await this.page.click('#submit');
});
```
## World Context
Share state between steps using World:
```javascript
const { setWorldConstructor, World } = require('@cucumber/cucumber');
class CustomWorld extends World {
constructor(options) {
super(options);
this.cart = [];
this.user = null;
}
async login(username, password) {
this.user = await this.api.login(username, password);
}
addToCart(item) {
this.cart.push(item);
}
}
setWorldConstructor(CustomWorld);
// Use in steps
Given('I am logged in', async function() {
await this.login('testuser', 'password');
});
When('I add an item to my cart', async function() {
this.addToCart({ id: 1, name: 'Product' });
});
```
## Hooks
Set up and tear down test state:
```javascript
const { Before, After, BeforeAll, AfterAll } = require('@cucumber/cucumber');
BeforeAll(async function() {
// Runs once before all scenarios
await startTestServer();
});
Before(async function() {
// Runs before each scenario
this.browser = await launchBrowser();
this.page = await this.browser.newPage();
});
Before({ tags: '@database' }, async function() {
// Runs only for scenarios with @database tag
await this.db.clear();
});
After(async function() {
// Runs after each scenario
await this.browser.close();
});
AfterAll(async function() {
// Runs once after all scenarios
await stopTestServer();
});
```
## Step Organization
### Page Object Pattern
```javascript
// pages/LoginPage.js
class LoginPage {
constructor(page) {
this.page = page;
}
async navigate() {
await this.page.goto('/login');
}
async fillCredentials(username, password) {
await this.page.fill('#username', username);
await this.page.fill('#password', password);
}
async submit() {
await this.page.click('#login-button');
}
}
// step-definitions/login-steps.js
const LoginPage = require('../pages/LoginPage');
Given('I am on the login page', async function() {
this.loginPage = new LoginPage(this.page);
await this.loginPage.navigate();
});
When('I enter {string} and {string}', async function(username, password) {
await this.loginPage.fillCredentials(username, password);
await this.loginPage.submit();
});
```
### Helper Functions
```javascript
// support/helpers.js
async function waitForElement(page, selector, timeout = 5000) {
await page.waitForSelector(selector, { timeout });
}
async function takeScreenshot(page, name) {
await page.screenshot({ path: `screenshots/${name}.png` });
}
module.exports = { waitForElement, takeScreenshot };
// Use in steps
const { waitForElement } = require('../support/helpers');
Then('I should see the dashboard', async function() {
await waitForElement(this.page, '.dashboard');
});
```
## Best Practices
1. **Keep steps simple and focused** - One action or assertion per step
2. **Reuse steps** - Write generic steps that work for multiple scenarios
3. **Avoid implementation details** - Don't expose internal structure in step names
4. **Use the World** - Share state through World, not global variables
5. **Organize by domain** - Group related steps together
6. **Don't duplicate logic** - Extract common functionality to helpers
7. **Make steps readable** - Step definitions should read like documentation
8. **Handle async properly** - Use async/await consistently
## Anti-Patterns to Avoid
❌ **Don't create overly specific steps:**
```javascript
Given('I am on the login page as a premium user with valid credentials')
```
✅ **Create composable steps:**
```javascript
Given('I am on the login page')
And('I am a premium user')
And('I have valid credentials')
```
❌ **Don't put assertions in Given/When:**
```javascript
When('I click login and see the dashboard')
```
✅ **Separate actions and assertions:**
```javascript
When('I click login')
Then('I should see the dashboard')
```
❌ **Don't use steps as functions:**
```javascript
// Don't call steps from within steps
When('I log in', async function() {
await this.Given('I am on the login page'); // Bad!
await this.When('I enter credentials'); // Bad!
});
```
✅ **Extract to helper functions:**
```javascript
// support/auth-helpers.js
async function login(world, username, password) {
await world.page.goto('/login');
await world.page.fill('#username', username);
await world.page.fill('#password', password);
await world.page.click('#login-button');
}
// Use in steps
When('I log in', async function() {
await login(this, 'user', 'pass');
});
```
Remember: Step definitions are the glue between readable scenarios and automation code. Keep them clean, maintainable, and focused.
More from TheBushidoCollective/han
- absinthe-resolversUse when implementing GraphQL resolvers with Absinthe. Covers resolver patterns, dataloader integration, batching, and error handling.
- absinthe-schemaUse when designing GraphQL schemas with Absinthe. Covers type definitions, interfaces, unions, enums, and schema organization patterns.
- absinthe-subscriptionsUse when implementing real-time GraphQL subscriptions with Absinthe. Covers Phoenix channels, PubSub, and subscription patterns.
- act-docker-setupUse when configuring Docker environments for act, selecting runner images, managing container resources, or troubleshooting Docker-related issues with local GitHub Actions testing.
- act-local-testingUse when testing GitHub Actions workflows locally with act. Covers act CLI usage, Docker configuration, debugging workflows, and troubleshooting common issues when running workflows on your local machine.
- act-workflow-syntaxUse when creating or modifying GitHub Actions workflow files. Provides guidance on workflow syntax, triggers, jobs, steps, and expressions for creating valid GitHub Actions workflows that can be tested locally with act.
- ameba-configurationUse when configuring Ameba rules and settings for Crystal projects including .ameba.yml setup, rule management, severity levels, and code quality enforcement.
- ameba-custom-rulesUse when creating custom Ameba rules for Crystal code analysis including rule development, AST traversal, issue reporting, and rule testing.
- ameba-integrationUse when integrating Ameba into development workflows including CI/CD pipelines, pre-commit hooks, GitHub Actions, and automated code review processes.
- analyze-performanceAnalyze performance metrics and identify slow transactions in Sentry