Hooks & Lifecycle Management

Setup/teardown, ordering, conditional hooks

What Are Hooks?

Hooks are methods that run automatically at specific points in the Cucumber lifecycle — before/after scenarios, before/after steps, or before/after the entire test suite. They handle setup and teardown without cluttering your feature files.

All Hook Types

HookRunsScopeStatic?
@BeforeAllOnce before all scenariosGlobalYes
@BeforeBefore each scenarioPer scenarioNo
@BeforeStepBefore each stepPer stepNo
@AfterStepAfter each stepPer stepNo
@AfterAfter each scenarioPer scenarioNo
@AfterAllOnce after all scenariosGlobalYes

The Execution Lifecycle

Here’s the exact order in which hooks and steps execute:

═══════════════════════════════════════════════
  @BeforeAll         (once — global setup)
═══════════════════════════════════════════════

  ┌─── Scenario 1 ───────────────────────────┐
  │  @Before          (scenario setup)        │
  │                                           │
  │  ┌── Step 1 ──────────────────────────┐  │
  │  │  @BeforeStep                        │  │
  │  │  Given I am on the login page       │  │
  │  │  @AfterStep                         │  │
  │  └─────────────────────────────────────┘  │
  │                                           │
  │  ┌── Step 2 ──────────────────────────┐  │
  │  │  @BeforeStep                        │  │
  │  │  When I enter username "john"       │  │
  │  │  @AfterStep                         │  │
  │  └─────────────────────────────────────┘  │
  │                                           │
  │  ┌── Step 3 ──────────────────────────┐  │
  │  │  @BeforeStep                        │  │
  │  │  Then I should see the dashboard    │  │
  │  │  @AfterStep                         │  │
  │  └─────────────────────────────────────┘  │
  │                                           │
  │  @After            (scenario teardown)    │
  └───────────────────────────────────────────┘

  ┌─── Scenario 2 ───────────────────────────┐
  │  @Before → Steps → @After                │
  └───────────────────────────────────────────┘

═══════════════════════════════════════════════
  @AfterAll          (once — global teardown)
═══════════════════════════════════════════════

@Before and @After

The most commonly used hooks — run before and after each scenario:

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

public class Hooks {

    private WebDriver driver;

    @Before
    public void setUp(Scenario scenario) {
        System.out.println("Starting: " + scenario.getName());
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
    }

    @After
    public void tearDown(Scenario scenario) {
        System.out.println("Finished: " + scenario.getName()
            + " — Status: " + scenario.getStatus());

        if (driver != null) {
            driver.quit();
        }
    }
}

@BeforeAll and @AfterAll

These run once for the entire test suite — perfect for expensive one-time setup:

import io.cucumber.java.BeforeAll;
import io.cucumber.java.AfterAll;

public class GlobalHooks {

    @BeforeAll
    public static void globalSetup() {
        // Runs ONCE before any scenario
        System.out.println("=== Test Suite Starting ===");

        // Load configuration
        ConfigReader.loadProperties("config.properties");

        // Prepare test database
        DatabaseHelper.seedTestData();

        // Start mock server if needed
        MockServer.start();
    }

    @AfterAll
    public static void globalTeardown() {
        // Runs ONCE after all scenarios complete
        System.out.println("=== Test Suite Complete ===");

        // Clean up test data
        DatabaseHelper.cleanTestData();

        // Stop mock server
        MockServer.stop();

        // Generate summary report
        ReportHelper.generateSummary();
    }
}

@BeforeStep and @AfterStep

These run before and after every individual step — useful for logging and debugging:

import io.cucumber.java.BeforeStep;
import io.cucumber.java.AfterStep;
import io.cucumber.java.Scenario;

public class StepHooks {

    @BeforeStep
    public void beforeStep(Scenario scenario) {
        System.out.println("  → Executing step...");
    }

    @AfterStep
    public void afterStep(Scenario scenario) {
        // Log step completion
        System.out.println("  ← Step completed");

        // Capture screenshot after every step (useful for debugging)
        if (DriverManager.getDriver() != null) {
            byte[] screenshot = ((TakesScreenshot) DriverManager.getDriver())
                .getScreenshotAs(OutputType.BYTES);
            scenario.attach(screenshot, "image/png",
                "Step screenshot — " + scenario.getName());
        }
    }
}

Hook Ordering

When you have multiple @Before or @After hooks, use the order parameter to control execution order:

public class OrderedHooks {

    // @Before hooks: lower order runs FIRST
    @Before(order = 1)
    public void loadConfig() {
        System.out.println("1. Loading configuration...");
        ConfigReader.load();
    }

    @Before(order = 2)
    public void startBrowser() {
        System.out.println("2. Starting browser...");
        DriverManager.initDriver();
    }

    @Before(order = 3)
    public void navigateToApp() {
        System.out.println("3. Navigating to application...");
        DriverManager.getDriver().get(ConfigReader.getBaseUrl());
    }

    // @After hooks: lower order runs LAST (reverse!)
    @After(order = 3)
    public void captureEvidence(Scenario scenario) {
        System.out.println("3. Capturing evidence...");
        if (scenario.isFailed()) {
            captureScreenshot(scenario);
        }
    }

    @After(order = 2)
    public void closeBrowser() {
        System.out.println("2. Closing browser...");
        DriverManager.quitDriver();
    }

    @After(order = 1)
    public void logResult(Scenario scenario) {
        System.out.println("1. Logging result: " + scenario.getStatus());
    }
}

Conditional Hooks with Tags

Hooks can be restricted to scenarios with specific tags. This is powerful for running different setup/teardown based on scenario type:

public class ConditionalHooks {

    // Only runs before scenarios tagged @ui
    @Before("@ui")
    public void setupBrowser() {
        DriverManager.initDriver();
    }

    // Only runs after scenarios tagged @ui
    @After("@ui")
    public void closeBrowser() {
        DriverManager.quitDriver();
    }

    // Only runs before scenarios tagged @api
    @Before("@api")
    public void setupApiClient() {
        ApiClient.init(ConfigReader.getBaseApiUrl());
    }

    // Only runs before scenarios tagged @database
    @Before("@database")
    public void seedDatabase() {
        DatabaseHelper.seedTestData();
    }

    // Only runs after scenarios tagged @database
    @After("@database")
    public void cleanDatabase() {
        DatabaseHelper.cleanTestData();
    }

    // Complex tag expression — same syntax as runner tags
    @Before("@smoke and not @wip")
    public void smokeSetup() {
        System.out.println("Smoke test setup...");
    }
}

Tag Expressions in Hooks

@Before("@ui")                        // Scenarios tagged @ui
@Before("@ui and @smoke")             // Scenarios with BOTH tags
@Before("@ui or @api")                // Scenarios with EITHER tag
@Before("not @wip")                   // Scenarios NOT tagged @wip
@Before("(@smoke or @regression) and not @skip")  // Complex expression

Real-World Example: UI vs API Testing

@ui @smoke
Scenario: Login via browser
  Given I am on the login page
  When I login with valid credentials
  Then I should see the dashboard

@api @smoke
Scenario: Login via API
  Given the API is available
  When I send login request with valid credentials
  Then the response status should be 200

The @ui scenario gets browser setup/teardown. The @api scenario gets API client setup. Neither gets the other’s hooks — clean separation.

The Scenario Object

The Scenario object is your window into the currently running scenario. Available in @Before, @After, @BeforeStep, and @AfterStep:

@After
public void afterScenario(Scenario scenario) {
    // Get scenario metadata
    String name = scenario.getName();           // "Successful login"
    String id = scenario.getId();               // Unique ID
    Status status = scenario.getStatus();       // PASSED, FAILED, SKIPPED, etc.
    boolean failed = scenario.isFailed();       // true if any step failed

    // Get tags
    Collection<String> tags = scenario.getSourceTagNames();
    // ["@smoke", "@login", "@regression"]

    // Get the line number in feature file
    String uri = scenario.getUri().toString();
    int line = scenario.getLine();

    // Attach data to the report
    scenario.attach(
        "Some log text".getBytes(),
        "text/plain",
        "Debug Log"
    );

    // Attach screenshot (most common use)
    if (scenario.isFailed()) {
        byte[] screenshot = ((TakesScreenshot) driver)
            .getScreenshotAs(OutputType.BYTES);
        scenario.attach(screenshot, "image/png", "Failure Screenshot");
    }

    // Log to report
    scenario.log("Scenario completed at: " + LocalDateTime.now());
}

Scenario Status Values

StatusMeaning
PASSEDAll steps passed
FAILEDOne or more steps failed
SKIPPEDSteps after a failure are skipped
PENDINGStep threw PendingException (not implemented)
UNDEFINEDNo matching step definition found
AMBIGUOUSMultiple step definitions match

Screenshot on Failure — Complete Setup

The most essential hook pattern — capturing evidence when tests fail:

import io.cucumber.java.After;
import io.cucumber.java.Before;
import io.cucumber.java.Scenario;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;

public class Hooks {

    private static ThreadLocal<WebDriver> driverThread = new ThreadLocal<>();

    @Before("@ui")
    public void setupBrowser(Scenario scenario) {
        WebDriver driver = new ChromeDriver();
        driver.manage().window().maximize();
        driverThread.set(driver);
        scenario.log("Browser started for: " + scenario.getName());
    }

    @After("@ui")
    public void teardownBrowser(Scenario scenario) {
        WebDriver driver = driverThread.get();

        if (driver != null) {
            // Capture screenshot on failure
            if (scenario.isFailed()) {
                try {
                    byte[] screenshot = ((TakesScreenshot) driver)
                        .getScreenshotAs(OutputType.BYTES);
                    scenario.attach(screenshot, "image/png",
                        "FAILED — " + scenario.getName());

                    // Also log the current URL for debugging
                    scenario.log("Failed at URL: " + driver.getCurrentUrl());

                    // Log the page source for debugging
                    String pageSource = driver.getPageSource();
                    scenario.attach(pageSource.getBytes(), "text/html",
                        "Page Source on Failure");

                } catch (WebDriverException e) {
                    scenario.log("Could not capture screenshot: " + e.getMessage());
                }
            }

            driver.quit();
            driverThread.remove();
        }
    }

    public static WebDriver getDriver() {
        return driverThread.get();
    }
}

Complete Hooks Example

Here’s a production-ready hooks class combining all the concepts:

package com.gyancode.cucumber.hooks;

import io.cucumber.java.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.time.Duration;
import java.time.LocalDateTime;

public class Hooks {

    private static final ThreadLocal<WebDriver> driver = new ThreadLocal<>();

    // ─── Global Setup (once) ────────────────────────────
    @BeforeAll
    public static void suiteSetup() {
        System.out.println("═══ Suite started at: "
            + LocalDateTime.now() + " ═══");
        ConfigReader.loadProperties();
    }

    // ─── Per-Scenario Setup ─────────────────────────────
    @Before(value = "@ui", order = 1)
    public void initBrowser(Scenario scenario) {
        ChromeOptions options = new ChromeOptions();
        if (ConfigReader.isHeadless()) {
            options.addArguments("--headless=new");
        }
        WebDriver webDriver = new ChromeDriver(options);
        webDriver.manage().window().maximize();
        webDriver.manage().timeouts()
            .implicitlyWait(Duration.ofSeconds(10));
        driver.set(webDriver);
    }

    @Before(value = "@ui", order = 2)
    public void navigateToApp() {
        getDriver().get(ConfigReader.getBaseUrl());
    }

    // ─── Per-Scenario Teardown ──────────────────────────
    @After(value = "@ui", order = 2)
    public void captureOnFailure(Scenario scenario) {
        if (scenario.isFailed() && getDriver() != null) {
            try {
                byte[] screenshot = ((TakesScreenshot) getDriver())
                    .getScreenshotAs(OutputType.BYTES);
                scenario.attach(screenshot, "image/png",
                    scenario.getName() + " — failure");
                scenario.log("URL: " + getDriver().getCurrentUrl());
            } catch (Exception e) {
                scenario.log("Screenshot failed: " + e.getMessage());
            }
        }
    }

    @After(value = "@ui", order = 1)
    public void closeBrowser() {
        if (getDriver() != null) {
            getDriver().quit();
            driver.remove();
        }
    }

    // ─── Global Teardown (once) ─────────────────────────
    @AfterAll
    public static void suiteTeardown() {
        System.out.println("═══ Suite finished at: "
            + LocalDateTime.now() + " ═══");
    }

    // ─── Helper ─────────────────────────────────────────
    public static WebDriver getDriver() {
        return driver.get();
    }
}

Summary