← All skills

Cucumber Skill

Bdd testingJavaJavaScriptRubyTypeScript

Copy and Paste in your Terminal

npx skills add https://github.com/LambdaTest/agent-skills.git --skill cucumber-skill

Playbook

Complete implementation guide with code samples, patterns, and best practices.

Cucumber BDD — Advanced Implementation Playbook

§1 — Project Setup & Configuration

<!-- 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

@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

@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

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

// 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)

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

// 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

// 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>
<!-- 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

// 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

# 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

ProblemCauseFix
Step undefinedGlue path wrong or step not matchingVerify glue in runner, check regex/expression
Ambiguous stepTwo steps match same Gherkin lineMake step expressions more specific
Scenario Outline failsWrong column name in ExamplesMatch <placeholder> exactly to column header
PicoContainer errorMissing no-arg constructorUse constructor injection, add cucumber-picocontainer dep
Parallel thread interferenceShared mutable stateUse ThreadLocal in ScenarioContext
Hook not executingWrong package in glueEnsure hooks class is in package listed in glue
Tag filter not workingWrong syntaxUse @tag and not @wip (Cucumber 7+ expression)
DataTable type mismatchWrong DataTable methodUse asMaps() for Map, asList() for typed objects
Rerun file emptyFirst run passedrerun:target/rerun.txt only populated on failures
Screenshot not attachedDriver null or quitCapture 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/