Claude-skill-registry cucumber-step-definitions
Writing effective step definitions and organizing test code
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/cucumber-step-definitions" ~/.claude/skills/majiayu000-claude-skill-registry-cucumber-step-definitions && rm -rf "$T"
manifest:
skills/data/cucumber-step-definitions/SKILL.mdsource content
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)
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)
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
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:
// 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:
// 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:
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:
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:
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:
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
// 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
// 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
- Keep steps simple and focused - One action or assertion per step
- Reuse steps - Write generic steps that work for multiple scenarios
- Avoid implementation details - Don't expose internal structure in step names
- Use the World - Share state through World, not global variables
- Organize by domain - Group related steps together
- Don't duplicate logic - Extract common functionality to helpers
- Make steps readable - Step definitions should read like documentation
- Handle async properly - Use async/await consistently
Anti-Patterns to Avoid
❌ Don't create overly specific steps:
Given('I am on the login page as a premium user with valid credentials')
✅ Create composable steps:
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:
When('I click login and see the dashboard')
✅ Separate actions and assertions:
When('I click login') Then('I should see the dashboard')
❌ Don't use steps as functions:
// 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:
// 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.