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
| Hook | Runs | Scope | Static? |
|---|---|---|---|
@BeforeAll | Once before all scenarios | Global | Yes |
@Before | Before each scenario | Per scenario | No |
@BeforeStep | Before each step | Per step | No |
@AfterStep | After each step | Per step | No |
@After | After each scenario | Per scenario | No |
@AfterAll | Once after all scenarios | Global | Yes |
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
| Status | Meaning |
|---|---|
PASSED | All steps passed |
FAILED | One or more steps failed |
SKIPPED | Steps after a failure are skipped |
PENDING | Step threw PendingException (not implemented) |
UNDEFINED | No matching step definition found |
AMBIGUOUS | Multiple 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();
}
}