# SkillPatch skill: cucumber-skill

Generates Cucumber BDD tests including Gherkin feature files and step definitions across Java, JavaScript, and Ruby. Covers core patterns such as Scenarios, Scenario Outlines, Backgrounds, Hooks, and Tags. Designed to trigger when users mention BDD-related keywords like "Cucumber", "Gherkin", or "Given/When/Then".

You (the agent) were given this URL and asked to use this skill. This is a **public** skill — no SkillPatch account, API key, or prior setup is required. Two ways to use it:

**1. Use it right now** — the skill's complete file tree (SKILL.md + all reference files) is inlined below; read `SKILL.md` first, then follow it, consulting the other files as it directs.

**2. Install the exact package onto disk** (recommended if you can run a shell — this reproduces the skill byte-for-byte, including any binary assets that can't be inlined):

```bash
mkdir -p .claude/skills/cucumber-skill
curl -sSL https://skillpatch.dev/install_skill/cucumber-skill | tar -xz -C .claude/skills/
```

(`.claude/skills/` is Claude Code's convention; use whatever directory your agent loads skills from.)


---

## Skill files (3)

- `SKILL.md`
- `reference/advanced-patterns.md`
- `reference/playbook.md`


### `SKILL.md`

````markdown
---
name: cucumber-skill
description: >
  Generates Cucumber BDD tests with Gherkin feature files and step definitions
  in Java, JavaScript, or Ruby. Use when user mentions "Cucumber", "Gherkin",
  "Feature/Scenario", "Given/When/Then", "BDD". Triggers on: "Cucumber",
  "Gherkin", "BDD", "Feature file", "Given/When/Then", "step definitions".
languages:
  - Java
  - JavaScript
  - Ruby
  - TypeScript
category: bdd-testing
license: MIT
metadata:
  author: TestMu AI
  version: "1.0"
---

# Cucumber BDD Skill

## Core Patterns

### Feature File (Gherkin)

```gherkin
Feature: User Login
  As a registered user
  I want to log into the application
  So that I can access my dashboard

  Background:
    Given I am on the login page

  Scenario: Successful login
    When I enter "user@test.com" in the email field
    And I enter "password123" in the password field
    And I click the login button
    Then I should be redirected to the dashboard
    And I should see "Welcome" on the page

  Scenario: Invalid credentials
    When I enter "wrong@test.com" in the email field
    And I enter "wrongpass" in the password field
    And I click the login button
    Then I should see an error message "Invalid credentials"

  Scenario Outline: Login with various users
    When I enter "<email>" in the email field
    And I enter "<password>" in the password field
    And I click the login button
    Then I should see "<result>"

    Examples:
      | email           | password    | result     |
      | admin@test.com  | admin123    | Dashboard  |
      | user@test.com   | password    | Dashboard  |
      | bad@test.com    | wrong       | Error      |
```

### Step Definitions — Java

```java
import io.cucumber.java.en.*;
import static org.junit.jupiter.api.Assertions.*;

public class LoginSteps {
    private LoginPage loginPage;
    private DashboardPage dashboardPage;

    @Given("I am on the login page")
    public void iAmOnTheLoginPage() {
        loginPage = new LoginPage(driver);
        loginPage.navigate();
    }

    @When("I enter {string} in the email field")
    public void iEnterEmail(String email) {
        loginPage.enterEmail(email);
    }

    @When("I enter {string} in the password field")
    public void iEnterPassword(String password) {
        loginPage.enterPassword(password);
    }

    @When("I click the login button")
    public void iClickLogin() {
        dashboardPage = loginPage.clickLogin();
    }

    @Then("I should be redirected to the dashboard")
    public void iShouldBeOnDashboard() {
        assertTrue(driver.getCurrentUrl().contains("/dashboard"));
    }

    @Then("I should see {string} on the page")
    public void iShouldSeeText(String text) {
        assertTrue(dashboardPage.getPageSource().contains(text));
    }
}
```

### Step Definitions — JavaScript

```javascript
const { Given, When, Then } = require('@cucumber/cucumber');
const { expect } = require('chai');

Given('I am on the login page', async function() {
  await this.page.goto('/login');
});

When('I enter {string} in the email field', async function(email) {
  await this.page.fill('#email', email);
});

When('I click the login button', async function() {
  await this.page.click('button[type="submit"]');
});

Then('I should see {string} on the page', async function(text) {
  const content = await this.page.textContent('body');
  expect(content).to.include(text);
});
```

### Hooks

```java
import io.cucumber.java.*;

public class Hooks {
    @Before
    public void setUp(Scenario scenario) {
        driver = new ChromeDriver();
    }

    @After
    public void tearDown(Scenario scenario) {
        if (scenario.isFailed()) {
            byte[] screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
            scenario.attach(screenshot, "image/png", "failure-screenshot");
        }
        driver.quit();
    }
}
```

### Tags

```gherkin
@smoke
Feature: Login
  @critical @fast
  Scenario: Quick login
    ...

  @slow @regression
  Scenario: Full login flow
    ...
```

```bash
# Run by tag
mvn test -Dcucumber.filter.tags="@smoke"
mvn test -Dcucumber.filter.tags="@smoke and not @slow"
```

### Anti-Patterns

| Bad | Good | Why |
|-----|------|-----|
| UI details in Gherkin | Business language | Readability |
| One step per line of code | Meaningful business steps | Abstraction |
| No Background for shared steps | Use Background | DRY |
| Imperative steps | Declarative steps | Maintainable |


### Cloud Execution on TestMu AI

Set environment variables: `LT_USERNAME`, `LT_ACCESS_KEY`

**Java:**
```java
// CucumberHooks.java
ChromeOptions browserOptions = new ChromeOptions();
HashMap<String, Object> ltOptions = new HashMap<>();
ltOptions.put("user", System.getenv("LT_USERNAME"));
ltOptions.put("accessKey", System.getenv("LT_ACCESS_KEY"));
ltOptions.put("build", "Cucumber Build");
ltOptions.put("name", scenario.getName());
ltOptions.put("platformName", "Windows 11");
ltOptions.put("video", true);
browserOptions.setCapability("LT:Options", ltOptions);
driver = new RemoteWebDriver(new URL("https://hub.lambdatest.com/wd/hub"), browserOptions);
```

**JavaScript:**
```javascript
const driver = new Builder()
  .usingServer(`https://${process.env.LT_USERNAME}:${process.env.LT_ACCESS_KEY}@hub.lambdatest.com/wd/hub`)
  .withCapabilities({ browserName: 'chrome', 'LT:Options': {
    user: process.env.LT_USERNAME, accessKey: process.env.LT_ACCESS_KEY,
    build: 'Cucumber Build', platformName: 'Windows 11', video: true
  }}).build();
```
## Quick Reference

| Task | Command |
|------|---------|
| Run all (Java) | `mvn test` with cucumber-junit-platform-engine |
| Run all (JS) | `npx cucumber-js` |
| Run tagged | `--tags "@smoke"` |
| Dry run | `--dry-run` |
| Generate snippets | Run undefined steps |

## Deep Patterns → `reference/playbook.md`

| § | Section | Lines |
|---|---------|-------|
| 1 | Project Setup & Configuration | Maven, runner, rerun |
| 2 | Feature Writing Patterns | Background, outlines, DataTable |
| 3 | Step Definitions | Typed steps, DI injection |
| 4 | Dependency Injection & Shared State | PicoContainer, ScenarioContext |
| 5 | Hooks (Lifecycle Management) | Before/After ordering, screenshots |
| 6 | Custom Parameter Types | Transformers, DocString |
| 7 | Parallel Execution | Thread-safe, TestNG parallel |
| 8 | Reporting | Allure, masterthought, JSON |
| 9 | CI/CD Integration | GitHub Actions, tag matrix |
| 10 | Debugging Quick-Reference | 10 common problems |
| 11 | Best Practices Checklist | 13 items |

````


### `reference/advanced-patterns.md`

````markdown
# Cucumber — Advanced Patterns & Playbook

## Step Definitions with Transforms (Java)

```java
public class StepDefinitions {
    private final WebDriver driver;
    private final World world;

    public StepDefinitions(World world) {
        this.world = world;
        this.driver = world.getDriver();
    }

    @Given("I am on the {page} page")
    public void navigateToPage(Page page) {
        driver.get(page.getUrl());
    }

    @When("I search for {string}")
    public void searchFor(String query) {
        world.searchPage.searchFor(query);
    }

    @Then("I should see {int} results")
    public void verifyResults(int count) {
        assertEquals(count, world.searchPage.getResultCount());
    }

    // Custom parameter type
    @ParameterType("home|login|dashboard|search")
    public Page page(String pageName) {
        return PageFactory.create(pageName);
    }
}
```

## JavaScript Step Definitions

```javascript
const { Given, When, Then, Before, After, setDefaultTimeout } = require('@cucumber/cucumber');
const { expect } = require('@playwright/test');

setDefaultTimeout(30_000);

Before(async function () {
  this.browser = await chromium.launch();
  this.page = await this.browser.newPage();
});

After(async function ({ result }) {
  if (result.status === 'FAILED') {
    const screenshot = await this.page.screenshot();
    this.attach(screenshot, 'image/png');
  }
  await this.browser?.close();
});

Given('I am logged in as {string}', async function (role) {
  await this.page.goto('/login');
  const creds = this.testData.getCredentials(role);
  await this.page.fill('#email', creds.email);
  await this.page.fill('#password', creds.password);
  await this.page.click('#submit');
  await expect(this.page).toHaveURL(/dashboard/);
});

When('I add {string} to cart', async function (product) {
  await this.page.click(`[data-product="${product}"] .add-to-cart`);
  await expect(this.page.locator('.cart-count')).toHaveText(/[1-9]/);
});

Then('the cart total should be {string}', async function (total) {
  const actual = await this.page.locator('.cart-total').textContent();
  expect(actual).toContain(total);
});
```

## Scenario Outline with Examples

```gherkin
Feature: User Registration
  Background:
    Given the registration page is open

  Scenario Outline: Register with different data
    When I register with name "<name>" and email "<email>"
    Then I should see "<message>"

    Examples: Valid registrations
      | name    | email            | message     |
      | Alice   | alice@test.com   | Welcome     |
      | Bob     | bob@example.org  | Welcome     |

    Examples: Invalid registrations
      | name    | email   | message        |
      |         | a@b.com | Name required  |
      | Charlie | invalid | Invalid email  |

  @wip
  Scenario: Register with existing email
    Given a user with email "existing@test.com" exists
    When I register with name "Eve" and email "existing@test.com"
    Then I should see "Email already taken"

  Rule: Password requirements
    Scenario: Weak password rejected
      When I register with password "123"
      Then I should see "Password too weak"
```

## Hooks & World Object

```java
// Hooks.java
public class Hooks {
    @Before(order = 0)
    public void setupDriver(Scenario scenario) { /* ... */ }

    @Before("@database")
    public void seedDatabase() { testDb.seed(); }

    @After
    public void cleanup(Scenario scenario) {
        if (scenario.isFailed()) {
            byte[] screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
            scenario.attach(screenshot, "image/png", "failure");
        }
        driver.quit();
    }

    @BeforeStep
    public void logStep() { /* logging */ }
}
```

## Anti-Patterns

- ❌ Implementation details in feature files — keep Gherkin business-readable
- ❌ One step per line that maps to a single WebDriver call — compose higher-level steps
- ❌ `And I wait 5 seconds` — use implicit waits in step definitions
- ❌ Scenario coupling via shared state — each scenario should be independent
- ❌ Too many `Given` steps — use `Background` for shared setup

````


### `reference/playbook.md`

````markdown
# Cucumber BDD — Advanced Implementation Playbook

## §1 — Project Setup & Configuration

```xml
<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-java</artifactId>
        <version>7.15.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-testng</artifactId>
        <version>7.15.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-picocontainer</artifactId>
        <version>7.15.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>4.18.1</version>
    </dependency>
</dependencies>

<build><plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.2.5</version>
        <configuration>
            <properties>
                <property>
                    <name>dataproviderthreadcount</name>
                    <value>4</value>
                </property>
            </properties>
        </configuration>
    </plugin>
</plugins></build>
```

### Runner Configuration

```java
@CucumberOptions(
    features = "src/test/resources/features",
    glue = {"stepdefs", "hooks"},
    plugin = {
        "pretty",
        "html:target/cucumber-reports/cucumber.html",
        "json:target/cucumber-reports/cucumber.json",
        "io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm",
        "rerun:target/rerun.txt"
    },
    tags = "@smoke and not @wip",
    monochrome = true,
    dryRun = false
)
public class TestRunner extends AbstractTestNGCucumberTests {
    @Override
    @DataProvider(parallel = true)
    public Object[][] scenarios() {
        return super.scenarios();
    }
}

// Rerun failed tests runner
@CucumberOptions(
    features = "@target/rerun.txt",
    glue = {"stepdefs", "hooks"},
    plugin = {"pretty", "html:target/rerun-report.html"}
)
public class RerunRunner extends AbstractTestNGCucumberTests {}
```

## §2 — Feature Writing Patterns

```gherkin
@smoke @auth
Feature: User Authentication
  As a registered user
  I want to log into my account
  So that I can access my dashboard

  Background:
    Given the application is running
    And the test database is seeded

  @positive
  Scenario: Successful login with valid credentials
    Given I am on the login page
    When I enter email "admin@test.com" and password "admin123"
    And I click the login button
    Then I should be redirected to the dashboard
    And I should see welcome message "Hello, Admin"

  @negative
  Scenario Outline: Login validation
    Given I am on the login page
    When I enter email "<email>" and password "<password>"
    And I click the login button
    Then I should see error message "<error>"

    Examples:
      | email            | password | error                |
      | invalid          | pass123  | Invalid email format |
      | admin@test.com   |          | Password is required |
      |                  | pass123  | Email is required    |
      | wrong@test.com   | wrong    | Invalid credentials  |

  @data-driven
  Scenario: Create multiple users
    Given I am logged in as admin
    When I create users with the following details:
      | name    | email             | role   |
      | Alice   | alice@test.com    | editor |
      | Bob     | bob@test.com      | viewer |
      | Charlie | charlie@test.com  | admin  |
    Then all users should be visible in the user list

  @api
  Scenario: API-created data appears in UI
    Given a product "Widget" exists via API
    When I navigate to the products page
    Then I should see "Widget" in the product list
```

## §3 — Step Definitions

```java
public class LoginSteps {
    private final ScenarioContext ctx;
    private LoginPage loginPage;
    private DashboardPage dashboardPage;

    // PicoContainer DI
    public LoginSteps(ScenarioContext ctx) {
        this.ctx = ctx;
    }

    @Given("I am on the login page")
    public void navigateToLogin() {
        loginPage = new LoginPage(ctx.getDriver());
        loginPage.navigate();
    }

    @When("I enter email {string} and password {string}")
    public void enterCredentials(String email, String password) {
        loginPage.enterEmail(email);
        loginPage.enterPassword(password);
    }

    @When("I click the login button")
    public void clickLogin() {
        dashboardPage = loginPage.clickLogin();
    }

    @Then("I should be redirected to the dashboard")
    public void verifyDashboard() {
        assertTrue(dashboardPage.isLoaded(), "Dashboard should be loaded");
    }

    @Then("I should see welcome message {string}")
    public void verifyWelcome(String expectedMessage) {
        assertEquals(expectedMessage, dashboardPage.getWelcomeMessage());
    }

    @Then("I should see error message {string}")
    public void verifyError(String expectedError) {
        assertEquals(expectedError, loginPage.getErrorMessage());
    }
}

// DataTable step
public class UserSteps {
    @When("I create users with the following details:")
    public void createUsers(DataTable dataTable) {
        List<Map<String, String>> users = dataTable.asMaps();
        for (Map<String, String> user : users) {
            adminPage.createUser(user.get("name"), user.get("email"), user.get("role"));
        }
    }
}
```

## §4 — Dependency Injection & Shared State

```java
// ScenarioContext — shared state across steps (PicoContainer)
public class ScenarioContext {
    private static final ThreadLocal<WebDriver> driverThread = new ThreadLocal<>();
    private final Map<String, Object> context = new ConcurrentHashMap<>();

    public WebDriver getDriver() { return driverThread.get(); }
    public void setDriver(WebDriver driver) { driverThread.set(driver); }
    public void removeDriver() { driverThread.remove(); }

    public void set(String key, Object value) { context.put(key, value); }
    @SuppressWarnings("unchecked")
    public <T> T get(String key) { return (T) context.get(key); }
    public void clear() { context.clear(); }
}

// Shared across multiple step definition classes
public class ApiSteps {
    private final ScenarioContext ctx;

    public ApiSteps(ScenarioContext ctx) { this.ctx = ctx; }

    @Given("a product {string} exists via API")
    public void createProductViaApi(String name) {
        String productId = ApiHelper.createProduct(name);
        ctx.set("productId", productId);  // share with UI steps
    }
}
```

## §5 — Hooks (Lifecycle Management)

```java
public class Hooks {
    private final ScenarioContext ctx;

    public Hooks(ScenarioContext ctx) { this.ctx = ctx; }

    @Before(order = 0)
    public void setUp(Scenario scenario) {
        WebDriver driver = DriverFactory.createDriver(
            System.getProperty("browser", "chrome"));
        ctx.setDriver(driver);
        ctx.set("scenario", scenario);
    }

    @Before(value = "@api", order = 1)
    public void setupApiTests() {
        ctx.set("apiToken", ApiHelper.getAuthToken());
    }

    @After(order = 1)
    public void captureOnFailure(Scenario scenario) {
        if (scenario.isFailed() && ctx.getDriver() != null) {
            byte[] screenshot = ((TakesScreenshot) ctx.getDriver())
                .getScreenshotAs(OutputType.BYTES);
            scenario.attach(screenshot, "image/png", "failure-screenshot");

            String pageSource = ctx.getDriver().getPageSource();
            scenario.attach(pageSource.getBytes(), "text/html", "page-source");
        }
    }

    @After(order = 0)
    public void tearDown() {
        WebDriver driver = ctx.getDriver();
        if (driver != null) {
            driver.quit();
            ctx.removeDriver();
        }
        ctx.clear();
    }

    @BeforeStep
    public void beforeStep(Scenario scenario) {
        // Optional: log each step
    }

    @AfterStep
    public void afterStep(Scenario scenario) {
        if (scenario.isFailed()) {
            // Capture intermediate screenshots
        }
    }
}
```

## §6 — Custom Parameter Types & Transformers

```java
// Custom parameter types
@ParameterType("admin|editor|viewer")
public UserRole role(String role) {
    return UserRole.valueOf(role.toUpperCase());
}

@ParameterType("true|false|yes|no")
public boolean booleanValue(String value) {
    return "true".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value);
}

// Usage in steps
@When("I create a user with role {role}")
public void createUser(UserRole role) {
    // role is already typed
}

@Then("the feature should be {booleanValue}")
public void verifyFeatureState(boolean enabled) {
    assertEquals(enabled, featurePage.isEnabled());
}

// DocString for large text
@When("I submit the following JSON:")
public void submitJson(String docString) {
    apiHelper.post("/api/data", docString);
}
```

## §7 — Parallel Execution

```java
// TestNG parallel DataProvider (in runner)
@Override
@DataProvider(parallel = true)
public Object[][] scenarios() {
    return super.scenarios();
}

// Thread-safe driver via ScenarioContext + ThreadLocal
// Configure thread count in maven-surefire-plugin:
// <dataproviderthreadcount>4</dataproviderthreadcount>
```

```xml
<!-- testng.xml for Cucumber -->
<suite name="BDD Suite" parallel="methods" thread-count="4">
    <test name="Cucumber">
        <classes>
            <class name="runners.TestRunner"/>
        </classes>
    </test>
</suite>
```

## §8 — Reporting

```java
// Cucumber built-in reports
plugin = {
    "pretty",                                               // console
    "html:target/cucumber-reports/cucumber.html",           // HTML
    "json:target/cucumber-reports/cucumber.json",           // JSON
    "junit:target/cucumber-reports/cucumber.xml",           // JUnit XML
    "timeline:target/cucumber-reports/timeline",            // Timeline
    "io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm",   // Allure
}

// Cucumber Reports plugin (masterthought)
// pom.xml
<plugin>
    <groupId>net.masterthought</groupId>
    <artifactId>maven-cucumber-reporting</artifactId>
    <version>5.7.7</version>
    <executions><execution>
        <id>execution</id>
        <phase>verify</phase>
        <goals><goal>generate</goal></goals>
        <configuration>
            <projectName>My Project</projectName>
            <outputDirectory>${project.build.directory}/cucumber-html-reports</outputDirectory>
            <jsonFiles>
                <param>**/cucumber.json</param>
            </jsonFiles>
        </configuration>
    </execution></executions>
</plugin>
```

## §9 — CI/CD Integration

```yaml
# GitHub Actions
name: BDD Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        tags: ['@smoke', '@regression']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: 17 }
      - run: |
          mvn test \
            -Dcucumber.filter.tags="${{ matrix.tags }}" \
            -Dbrowser=chrome-headless \
            -Denv=ci
      - run: mvn verify -DskipTests  # Generate reports
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: bdd-results-${{ matrix.tags }}
          path: |
            target/cucumber-reports/
            target/cucumber-html-reports/
            allure-results/
```

## §10 — Debugging Quick-Reference

| Problem | Cause | Fix |
|---------|-------|-----|
| Step undefined | Glue path wrong or step not matching | Verify `glue` in runner, check regex/expression |
| Ambiguous step | Two steps match same Gherkin line | Make step expressions more specific |
| Scenario Outline fails | Wrong column name in Examples | Match `<placeholder>` exactly to column header |
| PicoContainer error | Missing no-arg constructor | Use constructor injection, add `cucumber-picocontainer` dep |
| Parallel thread interference | Shared mutable state | Use `ThreadLocal` in `ScenarioContext` |
| Hook not executing | Wrong package in `glue` | Ensure hooks class is in package listed in `glue` |
| Tag filter not working | Wrong syntax | Use `@tag and not @wip` (Cucumber 7+ expression) |
| DataTable type mismatch | Wrong DataTable method | Use `asMaps()` for Map, `asList()` for typed objects |
| Rerun file empty | First run passed | `rerun:target/rerun.txt` only populated on failures |
| Screenshot not attached | Driver null or quit | Capture in `@After(order=1)` before quit in `@After(order=0)` |

## §11 — Best Practices Checklist

- ✅ Write features in business language, not technical steps
- ✅ Use `Background` for shared preconditions across scenarios
- ✅ Use `Scenario Outline` + `Examples` for data-driven tests
- ✅ Use tags (`@smoke`, `@regression`, `@wip`) for selective execution
- ✅ Use PicoContainer for dependency injection across step classes
- ✅ Use `ScenarioContext` with `ThreadLocal` for thread-safe shared state
- ✅ Use hook ordering: `@After(order=1)` for screenshots, `@After(order=0)` for cleanup
- ✅ Use custom parameter types for domain objects
- ✅ Attach screenshots and page source on failure
- ✅ Use `rerun:target/rerun.txt` for re-running failed scenarios
- ✅ Use Allure + masterthought for rich reports
- ✅ Keep step definitions thin — delegate to Page Objects
- ✅ Structure: `features/`, `stepdefs/`, `hooks/`, `pages/`, `runners/`, `utils/`

````
