Tagged Hooks in Cucumber

  

What are Tagged Hooks in Cucumber?

Tagged Hooks in Cucumber are special types of hooks (@Before, @After) that run only for scenarios with specific tags. This allows for fine-grained control over the setup and teardown of tests based on the tags assigned to scenarios or features.

Instead of running a hook before/after every scenario (as general hooks do), tagged hooks run only when the tag matches.


Why Use Tagged Hooks?

  • Run different setup/teardown for different scenario groups (like @Smoke@Regression, etc.)

  • Optimize test execution time

  • Avoid unnecessary setup for unrelated tests



Syntax of Tagged Hooks

@Before("@Smoke")
public void beforeSmokeTests() {
    // Code to run before scenarios tagged with @Smoke
}

@After("@Regression")
public void afterRegressionTests() {
    // Code to run after scenarios tagged with @Regression
}


Example Code

Feature File – login.feature

@Smoke
Feature: Login Feature

  @Smoke
  Scenario: Valid Login
    Given User is on login page
    When User enters valid credentials
    Then User should be navigated to the homepage

  @Regression
  Scenario: Invalid Login
    Given User is on login page
    When User enters invalid credentials
    Then User should see an error message




Step Definition Class – LoginSteps.java

package stepDefinitions;

import io.cucumber.java.en.*;

public class LoginSteps {

    @Given("User is on login page")
    public void user_is_on_login_page() {
        System.out.println("User navigates to login page");
    }

    @When("User enters valid credentials")
    public void user_enters_valid_credentials() {
        System.out.println("User enters correct username and password");
    }

    @When("User enters invalid credentials")
    public void user_enters_invalid_credentials() {
        System.out.println("User enters wrong username and password");
    }

    @Then("User should be navigated to the homepage")
    public void user_should_be_navigated_to_the_homepage() {
        System.out.println("User is redirected to the homepage");
    }

    @Then("User should see an error message")
    public void user_should_see_an_error_message() {
        System.out.println("Error message is displayed");
    }
}




Hooks Class – Hooks.java

package stepDefinitions;

import io.cucumber.java.Before;
import io.cucumber.java.After;

public class Hooks {

    @Before("@Smoke")
    public void setupForSmoke() {
        System.out.println("Setting up SMOKE environment...");
    }

    @After("@Smoke")
    public void tearDownForSmoke() {
        System.out.println("Tearing down SMOKE environment...");
    }

    @Before("@Regression")
    public void setupForRegression() {
        System.out.println("Setting up REGRESSION environment...");
    }

    @After("@Regression")
    public void tearDownForRegression() {
        System.out.println("Tearing down REGRESSION environment...");
    }
}


Output When Running Scenarios

If you run the @Smoke scenario, output will be:

Setting up SMOKE environment...
User navigates to login page
User enters correct username and password
User is redirected to the homepage
Tearing down SMOKE environment...


If you run the @Regression scenario, output will be:

Setting up REGRESSION environment...
User navigates to login page
User enters wrong username and password
Error message is displayed
Tearing down REGRESSION environment...

Execution Order of Hooks

  

Execution Order of Hooks in Cucumber

In CucumberHooks allow us to run blocks of code at specific points in the test execution cycle. They help in setting up and tearing down test environments.

You can define multiple hooks using annotations like @Before@After, etc. When there are multiple hooks of the same typeexecution order is determined by assigning an order value.


Execution Rule:

  • Lower order value means higher priority, so it runs first.

  • For @Before hooks: lower order runs first

  • For @After hooks: higher order runs first (reverse order of @Before)



Execution Flow:

Hook TypeOrder ValueExecution
@Before(order = 0)LowerExecutes First
@Before(order = 1)HigherExecutes After
@After(order = 1)LowerExecutes Last
@After(order = 0)HigherExecutes Before


Code Example: Hook Execution Order in Cucumber (Java)

Hooks.java

package stepDefinitions;

import io.cucumber.java.Before;
import io.cucumber.java.After;

public class Hooks {

    @Before(order = 0)
    public void beforeHookOrder0() {
        System.out.println("Before Hook - Order 0");
    }

    @Before(order = 1)
    public void beforeHookOrder1() {
        System.out.println("Before Hook - Order 1");
    }

    @After(order = 1)
    public void afterHookOrder1() {
        System.out.println("After Hook - Order 1");
    }

    @After(order = 0)
    public void afterHookOrder0() {
        System.out.println("After Hook - Order 0");
    }
}




Feature: HookDemo.feature

Feature: Hook Order Demo

  Scenario: Check the order of hook execution
    Given This is a test step




StepDefinitions.java

package stepDefinitions;

import io.cucumber.java.en.Given;

public class StepDefinitions {

    @Given("This is a test step")
    public void this_is_a_test_step() {
        System.out.println("Executing Test Step");
    }
}




Console Output:

Before Hook - Order 0
Before Hook - Order 1
Executing Test Step
After Hook - Order 0
After Hook - Order 1




Important Points:

Hooks are used for setup (@Before) and teardown (@After) logic.
Use order to control the sequence.
@Before hooks run in ascending order.
@After hooks run in descending order.

Background Keyword in Cucumber

  

In Cucumber, the Background keyword is used to define common steps that are executed before each scenario in a feature file. It helps avoid repetition when multiple scenarios have the same initial steps or setup.


Why Use Background?

When several scenarios share the same set of preconditions or initial context, instead of copying those steps into each scenario, you define them once in a Background section. This improves:

  • Readability

  • Maintainability

  • DRY (Don't Repeat Yourself) principle




Syntax of Background keyword in feature file:

Feature: User Login Feature

  Background:
    Given The user is on the login page

  Scenario: Successful Login with valid credentials
    When The user enters valid username and password
    Then The user should be redirected to the dashboard

  Scenario: Login fails with invalid credentials
    When The user enters invalid username or password
    Then An error message should be displayed


The step 'Given The user is on the login page' is common for both scenarios and will be executed before each scenario.



Java Step Definition Example

public class LoginSteps {

    @Given("The user is on the login page")
    public void user_is_on_login_page() {
        System.out.println("User navigates to login page");
    }

    @When("The user enters valid username and password")
    public void enter_valid_credentials() {
        System.out.println("User enters valid credentials");
    }

    @When("The user enters invalid username or password")
    public void enter_invalid_credentials() {
        System.out.println("User enters invalid credentials");
    }

    @Then("The user should be redirected to the dashboard")
    public void redirected_to_dashboard() {
        System.out.println("User is on dashboard");
    }

    @Then("An error message should be displayed")
    public void error_message_displayed() {
        System.out.println("Error message shown");
    }
}
 



At a glance:

KeywordPurpose
BackgroundDefines steps that run before each scenario
ScenarioA single test case in a feature
GivenPreconditions or setup
WhenAction performed
ThenExpected outcome


Important Points:

  • Only one Background is allowed per feature file.

  • It runs before each Scenario or Scenario Outline in the feature file.

  • It should not be overused — if too many steps go into Background, scenarios may become hard to understand.

How can we convert Selenium Test to Cucumber Test

  

To convert a Selenium test into a Cucumber BDD test, you need to follow a structured approach where the test scenarios are written in Gherkin syntax (in .feature files) and the test logic is implemented using step definitions that internally use Selenium WebDriver.


Steps to Convert Selenium Test to Cucumber BDD

Step 1: Traditional Selenium Test (Without Cucumber)

Here is a simple Selenium test in Java:

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

public class LoginTest {
    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();
        driver.get("https://example.com/login");

        WebElement username = driver.findElement(By.id("username"));
        WebElement password = driver.findElement(By.id("password"));
        WebElement loginButton = driver.findElement(By.id("login"));

        username.sendKeys("admin");
        password.sendKeys("admin123");
        loginButton.click();

        String expectedUrl = "https://example.com/dashboard";
        assert driver.getCurrentUrl().equals(expectedUrl);

        driver.quit();
    }
}




Step 2: Add Cucumber and Selenium Dependencies in pom.xml

<dependencies>
    <!-- Selenium -->
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>4.21.0</version>
    </dependency>

    <!-- Cucumber -->
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-java</artifactId>
        <version>7.14.0</version>
    </dependency>

    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-junit</artifactId>
        <version>7.14.0</version>
        <scope>test</scope>
    </dependency>

    <!-- JUnit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>




Step 3: Create Feature File (Gherkin)

src/test/resources/features/login.feature

Feature: Login Functionality

  Scenario: Valid login
    Given user is on login page
    When user enters valid username and password
    And clicks on login button
    Then user should be redirected to dashboard page




Step 4: Create Step Definition File

src/test/java/stepDefinitions/LoginSteps.java

package stepDefinitions;

import io.cucumber.java.en.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;

public class LoginSteps {
    WebDriver driver;

    @Given("user is on login page")
    public void user_is_on_login_page() {
        driver = new ChromeDriver();
        driver.get("https://example.com/login");
    }

    @When("user enters valid username and password")
    public void user_enters_valid_username_and_password() {
        driver.findElement(By.id("username")).sendKeys("admin");
        driver.findElement(By.id("password")).sendKeys("admin123");
    }

    @When("clicks on login button")
    public void clicks_on_login_button() {
        driver.findElement(By.id("login")).click();
    }

    @Then("user should be redirected to dashboard page")
    public void user_should_be_redirected_to_dashboard_page() {
        String expectedUrl = "https://example.com/dashboard";
        String actualUrl = driver.getCurrentUrl();
        if (!actualUrl.equals(expectedUrl)) {
            throw new AssertionError("Login failed: URL mismatch");
        }
        driver.quit();
    }
}




Step 5: Create Test Runner Class

src/test/java/testRunner/TestRunner.java

package testRunner;

import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;

@RunWith(Cucumber.class)
@CucumberOptions(
    features = "src/test/resources/features",
    glue = "stepDefinitions",
    plugin = {"pretty", "html:target/cucumber-report.html"},
    monochrome = true
)
public class TestRunner {
}



Benefits of Converting to Cucumber BDD

  • Readable Scenarios: Easier for non-developers to understand.

  • Collaboration: Better collaboration between QA, Dev, and Business.

  • Reusable Steps: Step definitions can be reused across features.

  • Reports: HTML reports can be generated for analysis.

How to Share Test Context between Cucumber Step Definitions

Sharing test context between step definitions in Cucumber is a common practice when multiple step definition classes need to share state, such as:

  • WebDriver instance

  • Page Objects

  • Test data


Why Share Context?

Each step definition class is instantiated independently by Cucumber. So if you define a variable in one step class, it won’t be accessible in another unless you explicitly share it.


Solution: Use a Shared Context or Dependency Injection

There are two common ways:

1. Create a Context Class

A simple POJO class to hold shared data (like WebDriver, test data, etc.)

2. Use Constructor Injection in Step Definition Classes

Pass the shared context to all step classes via their constructors.


Example: Sharing WebDriver using TestContext class

Step 1: Create TestContext Class

package utils;

import org.openqa.selenium.WebDriver;

public class TestContext {
    private WebDriver driver;

    public void setDriver(WebDriver driver) {
        this.driver = driver;
    }

    public WebDriver getDriver() {
        return driver;
    }
}




Step 2: Base Step Definition Constructor

package stepDefinitions;

import utils.TestContext;

public class BaseSteps {
    protected TestContext context;

    public BaseSteps(TestContext context) {
        this.context = context;
    }
}




Step 3: Step Definitions Using Shared Context

LoginSteps.java

package stepDefinitions;

import io.cucumber.java.en.Given;
import org.openqa.selenium.chrome.ChromeDriver;
import utils.TestContext;

public class LoginSteps extends BaseSteps {

    public LoginSteps(TestContext context) {
        super(context);
    }

    @Given("User launches the application")
    public void user_launches_the_application() {
        context.setDriver(new ChromeDriver());
        context.getDriver().get("https://example.com");
    }
}




HomeSteps.java

package stepDefinitions;

import io.cucumber.java.en.Then;
import utils.TestContext;

public class HomeSteps extends BaseSteps {

    public HomeSteps(TestContext context) {
        super(context);
    }

    @Then("User should see the homepage")
    public void user_should_see_homepage() {
        String title = context.getDriver().getTitle();
        System.out.println("Title: " + title);
    }
}




Step 4: Create TestContextInitializer to Inject Context

package stepDefinitions;

import io.cucumber.java.Before;
import utils.TestContext;

public class TestContextInitializer {
    private static final TestContext context = new TestContext();

    public static TestContext getContext() {
        return context;
    }

    @Before
    public void beforeScenario() {
        // You can initialize default values here if needed
    }
}




Step 5: Update Step Definitions to Use Shared Context

To use the shared context in your step definitions, make sure to pass the same instance:

public class LoginSteps extends BaseSteps {
    public LoginSteps() {
        super(TestContextInitializer.getContext());
    }
}


Benefits

  • Clean separation of logic and context

  • Easily extensible

  • Promotes reuse of WebDriver, data, and page objects