Hooks Concept in Cucumber JVM

What Are Hooks in Cucumber?

Hooks in Cucumber are special lifecycle methods that execute automatically at predefined points during test execution. They are not written as steps in the feature file, and they are not matched against Gherkin text like normal step definitions. Instead, Cucumber calls them before or after scenarios, and in some cases before or after each step. In Cucumber JVM, hooks are commonly used to perform setup and cleanup work that should happen consistently across scenarios.

In simple terms, hooks manage the test lifecycle. A @Before hook can launch the browser before a scenario starts. An @After hook can close the browser after the scenario finishes. A @BeforeStep hook can run before every step, and an @AfterStep hook can run after every step. These methods execute automatically, which means the feature file does not need to mention browser launch, driver initialization, screenshot capture, report setup, database connection, or cleanup every time.

This separation is important because Gherkin should describe business behavior, not framework plumbing. A feature file should say that a user logs in, places an order, or verifies a dashboard. It should not repeatedly say that the browser is launched, configuration is read, reports are initialized, or the driver is closed. Hooks keep those technical responsibilities outside the business-readable scenario.

Hooks are different from step definitions. Step definitions are connected to Gherkin steps and represent the behavior described in the scenario. Hooks are connected to the execution lifecycle and represent framework-level setup or teardown. A step definition answers, "What does this business step do?" A hook answers, "What must happen before or after this scenario can run safely?" Understanding this distinction is essential for building clean Cucumber JVM frameworks.

Why Hooks Are Needed

Many automation tasks must happen repeatedly for every scenario or for a specific group of scenarios. A Selenium UI test may need to create a WebDriver instance, maximize the browser, open the application URL, initialize waits, and close the browser after execution. An API test may need to set a base URI, prepare authentication headers, start request logging, and clean up created records. A database test may need to connect before execution and disconnect afterward. A reporting framework may need to start and flush reports around scenarios.

Without hooks, these activities often get written directly into feature files or repeated inside step definitions. That creates duplication and makes scenarios less readable. A login scenario might begin with steps like browser is launched and user opens application. A search scenario might repeat the same setup. A checkout scenario might repeat it again. This violates the DRY principle and turns Gherkin into a mixture of business behavior and technical setup.

Scenario: Login
  Given browser is launched
  And user opens application
  When user logs in
  Then dashboard should appear

Scenario: Search
  Given browser is launched
  And user opens application
  When user searches product
  Then products should appear

The repeated setup steps do not add business value. They only describe technical preparation. With hooks, the setup happens automatically, and the feature file becomes cleaner.

Scenario: Login
  When user logs in
  Then dashboard should appear

Scenario: Search
  When user searches product
  Then products should appear

The browser launch and application opening can happen inside a @Before hook. The browser closure can happen inside an @After hook. This keeps the scenario focused on the behavior being validated.

Basic Hook Execution Flow

The basic Cucumber hook flow is easy to understand. When a scenario starts, Cucumber runs matching @Before hooks. Then it executes the scenario steps. After the scenario completes, Cucumber runs matching @After hooks. This flow happens whether the scenario passes or fails. The @After hook is especially important because cleanup must usually happen even when a scenario fails halfway through.

Start Test
      |
      v
@Before Hook
      |
      v
Scenario Execution
      |
      v
@After Hook
      |
      v
End Test

Step-level hooks add more lifecycle points. A @BeforeStep hook runs before each Gherkin step. An @AfterStep hook runs after each Gherkin step. These are useful for logging, debugging, step screenshots, timing, or detailed reporting. They should be used carefully because they execute very frequently. A scenario with ten steps will trigger a step hook ten times.

Understanding the flow helps avoid surprises. If a browser is initialized in @Before, the step definitions can assume the driver exists. If cleanup is done in @After, the feature file does not need explicit cleanup steps. If screenshots are taken in @AfterStep, the framework may generate many images. Hooks are powerful because they are automatic, but that also means they must be designed intentionally.

Types of Hooks in Cucumber JVM

Cucumber JVM commonly supports four main hook types: @Before, @After, @BeforeStep, and @AfterStep. Depending on the Cucumber version and integration, additional lifecycle options may exist, but these four cover the most common framework needs. Each hook type has a specific role, and using the right one keeps the framework predictable.

@Before runs before every matching scenario. It is used for setup. @After runs after every matching scenario. It is used for cleanup. @BeforeStep runs before each step. It is used for step-level preparation, logging, or debugging. @AfterStep runs after each step. It is used for step-level screenshots, logging, or diagnostics.

The most commonly used hooks are @Before and @After. Step-level hooks are useful, but they can add overhead quickly. A good framework starts with scenario-level hooks and introduces step-level hooks only when there is a clear need.

The @Before Hook

The @Before hook runs before every scenario that matches it. If no tag condition is specified, it runs before every scenario in the test run. This hook is commonly used to prepare the test environment. In Selenium automation, it may initialize WebDriver, maximize the browser, configure timeouts, and open the application. In API automation, it may set base URI, authentication, and request defaults. In database testing, it may open a connection or prepare test data.

@Before
public void beforeScenario() {
    System.out.println("Launching Browser");
}

A Selenium setup hook might look like this:

@Before
public void setup() {
    driver = new ChromeDriver();
    driver.manage().window().maximize();
    driver.get("https://example.com");
}

The benefit is that every scenario receives the same baseline setup without repeating setup steps in the feature file. Step definitions can focus on business actions because the framework has already prepared the environment. However, the hook should not become a dumping ground for unrelated work. If setup requires many operations, delegate them to helper classes such as DriverFactory, ConfigReader, ReportManager, or TestDataService.

Another important point is scope. If all scenarios are UI scenarios, launching a browser before every scenario may be fine. If the project contains UI and API scenarios, launching a browser for API-only scenarios wastes time. Tagged hooks can solve that by launching a browser only for scenarios tagged with something like @UI.

The @After Hook

The @After hook runs after a scenario finishes. It runs even when the scenario fails, which makes it the right place for cleanup. Typical uses include closing the browser, quitting WebDriver, deleting temporary test data, disconnecting from a database, closing reports, releasing resources, stopping mock servers, and capturing failure diagnostics.

@After
public void afterScenario() {
    driver.quit();
}

The @After hook is a critical stability tool. If the browser is not closed, many browser processes may remain open after a test run. If test data is not deleted, later scenarios may fail because of duplicate records. If database connections are not closed, resources may leak. Cleanup should not depend on a final Gherkin step because failed scenarios may never reach that step. A hook gives the framework a reliable teardown point.

In real frameworks, @After hooks often inspect scenario status. If a scenario failed, the hook may capture a screenshot, attach logs, save browser console output, or include request and response details in the report. If the scenario passed, it may skip heavy diagnostics and perform only normal cleanup. This keeps reporting useful without producing unnecessary artifacts for every successful test.

The @BeforeStep Hook

The @BeforeStep hook runs before every step in a scenario. It is less common than @Before, but it can be useful for detailed logging or debugging. For example, a framework may log that a step is about to run, start a timer, reset a small piece of step context, or verify that the driver is still available.

@BeforeStep
public void beforeStep() {
    System.out.println("Before Step");
}

If a scenario has three steps, the @BeforeStep hook runs three times. The flow looks like this:

@BeforeStep
Given step

@BeforeStep
When step

@BeforeStep
Then step

Because step hooks run frequently, they should remain lightweight. Starting a browser, connecting to a database, or loading large files before every step would slow the suite dramatically. Step hooks are best for small framework actions that truly need step-level timing.

The @AfterStep Hook

The @AfterStep hook runs after every step. It is commonly used for screenshots, logging, and diagnostic reporting. Some teams capture screenshots after every step to produce visual execution reports. Others capture screenshots only after failed steps or failed scenarios. The best approach depends on reporting needs and execution cost.

@AfterStep
public void afterStep() {
    System.out.println("After Step");
}

The step execution flow becomes:

Given step
      |
      v
@AfterStep

When step
      |
      v
@AfterStep

Then step
      |
      v
@AfterStep

Step screenshots can be useful for debugging visual failures, but they can also generate many files and slow down execution. A suite with hundreds of scenarios and many steps can produce thousands of screenshots. Use @AfterStep carefully and consider limiting heavy diagnostics to failures or specific tags.

Hook Execution Order

When a scenario uses scenario-level and step-level hooks together, the order follows the lifecycle. Cucumber runs @Before first, then for each step it runs @BeforeStep, the step definition, and @AfterStep. After all steps finish or the scenario stops because of failure, Cucumber runs @After.

@Before
  |
  v
@BeforeStep
  |
  v
Given step
  |
  v
@AfterStep
  |
  v
@BeforeStep
  |
  v
When step
  |
  v
@AfterStep
  |
  v
@BeforeStep
  |
  v
Then step
  |
  v
@AfterStep
  |
  v
@After

This order matters when hooks depend on state. For example, screenshots in @AfterStep require the driver to still exist. Therefore, driver.quit() should happen in @After, not earlier. Cleanup that requires scenario-created IDs should run after the step definitions have had a chance to store those IDs.

Hooks Versus Step Definitions

Hooks and step definitions serve different purposes. Step definitions execute because a Gherkin step matches them. They represent business behavior or scenario actions. Hooks execute automatically because Cucumber reaches a lifecycle point. They represent framework setup, cleanup, or diagnostics. Mixing these responsibilities creates confusing automation.

A hook should not perform the main business workflow of a scenario. For example, a @Before hook should not search for a product and complete a purchase just because several scenarios need an order. That hides behavior from the feature file. If order creation is part of the scenario's precondition, it may be expressed as a step or handled through a clear setup service called by a step. Hooks should prepare the environment, not secretly execute the story.

Similarly, step definitions should not close the browser or clean up global resources. A step named logout should not call driver.quit(). Browser teardown belongs in @After. Keeping the boundary clear makes feature files readable and framework behavior predictable.

Real-Time Selenium Hook Example

In Selenium automation, hooks are commonly used to initialize and close WebDriver. A clean design delegates driver creation to a factory class. The hook calls the factory, and step definitions or page objects retrieve the driver through the framework's chosen dependency pattern. This keeps hook methods small and avoids duplicating driver setup code.

@Before
public void setup() {
    DriverFactory.initialize();
}

@After
public void tearDown() {
    DriverFactory.quit();
}

The factory can read browser configuration, create ChromeDriver or EdgeDriver, apply window settings, configure waits, and store the driver for the scenario. The hook does not need to know every detail. It only starts and stops the lifecycle. This is the preferred direction for maintainable frameworks.

With this setup, feature files do not mention browser launch. Page objects do not create drivers themselves. Step definitions do not quit the browser. Each layer has a clean responsibility.

API Automation Hook Example

In API automation, hooks may initialize API configuration before scenarios and clean up created data afterward. A @Before hook may set the base URI, load authentication tokens, configure common headers, or initialize request logging. An @After hook may delete users, orders, or records created during the scenario.

@Before
public void initializeAPI() {
    RestAssured.baseURI = "https://api.example.com";
}

@After
public void cleanup() {
    testDataCleanup.deleteCreatedUsers();
}

As with Selenium, the hook should delegate complex work. Authentication may belong in an AuthService. Cleanup may belong in a CleanupService. Request defaults may belong in an API client class. The hook coordinates lifecycle, while service classes handle implementation.

Database Hook Example

Database scenarios may need connections before execution and disconnection afterward. A hook can open the connection before the scenario and close it after the scenario. This avoids opening and closing connections inside every step definition.

@Before
public void connectDB() {
    database.connect();
}

@After
public void disconnectDB() {
    database.disconnect();
}

Database hooks should be used carefully. Not every scenario needs a database connection. Tagged hooks can restrict database setup to scenarios tagged with @DB. This keeps execution faster and avoids unnecessary resource usage.

Screenshot Handling with Hooks

Screenshots are one of the most common uses of @After hooks in Selenium frameworks. When a scenario fails, the hook can capture the browser state and attach the image to the report. This gives the team immediate visual context for the failure. Screenshots are especially useful for UI failures caused by validation messages, unexpected navigation, missing elements, popups, or layout issues.

@After
public void screenshotOnFailure(Scenario scenario) {
    if (scenario.isFailed()) {
        byte[] screenshot = takeScreenshot();
        scenario.attach(screenshot, "image/png", "failure screenshot");
    }
}

Taking screenshots only on failure is usually more efficient than taking screenshots after every step. However, some teams use @AfterStep screenshots for detailed evidence. That approach should be balanced against storage and execution time. The hook design should match the reporting need.

Data Cleanup with Hooks

Hooks are a natural place for data cleanup because cleanup should happen after a scenario, even if the scenario fails. If a test creates a user, customer, order, booking, uploaded file, or API record, an @After hook can remove it. The framework should track created identifiers during the scenario and pass them to cleanup services.

@After
public void cleanup() {
    cleanupService.deleteScenarioData();
}

The hook should not blindly delete broad data. It should clean only the records created by the current scenario or test run. This is especially important in shared environments and parallel execution. The cleanup service should know exactly which IDs or unique keys belong to the scenario.

Multiple Hooks

Cucumber allows multiple hooks. A framework may have one hook for reading configuration, another for launching the browser, another for starting reports, and another for cleanup. Multiple hooks can keep each method focused, but they also require controlled ordering when one hook depends on another.

@Before
public void launchBrowser() {
}

@Before
public void readConfiguration() {
}

@Before
public void initializeReports() {
}

When order matters, Cucumber supports the order attribute. Lower order values execute first for @Before hooks. This allows a framework to read configuration before initializing the driver, or start reporting before scenario setup is logged.

@Before(order = 1)
public void readConfiguration() {
}

@Before(order = 2)
public void initializeDriver() {
}

Keep ordering simple. If many hooks depend on many other hooks, the lifecycle becomes difficult to reason about. Prefer a small number of clear hooks that delegate to well-named services.

Tagged Hooks

Tagged hooks run only for scenarios with specific tags. This is extremely useful because not every scenario needs the same setup. UI scenarios may need a browser. API scenarios may need API configuration. Database scenarios may need a database connection. Smoke scenarios may need lightweight setup. End-to-end scenarios may need complete environment preparation.

@Smoke
Scenario: Login
  When user logs in
  Then dashboard should appear
@Before("@Smoke")
public void smokeSetup() {
    System.out.println("Setup for smoke scenario");
}

Tagged hooks reduce unnecessary setup and make execution faster. They also make the lifecycle more precise. A browser should not be launched for a pure API scenario. A database connection should not be opened for a scenario that does not need database access. Tags help the framework apply only the setup and cleanup that the scenario requires.

Use tagged hooks carefully. Too many tag-specific hooks can become confusing. Tags should represent meaningful scenario categories, not arbitrary technical shortcuts. Common examples include @UI, @API, @DB, @Smoke, @Regression, and @Cleanup.

Common Uses of Hooks

Before hooks are commonly used to launch browsers, read configuration, initialize drivers, connect to databases, start reports, create test data, open applications, prepare API clients, and set scenario context. These activities prepare the environment before business steps begin.

After hooks are commonly used to close browsers, clean test data, disconnect databases, close reports, capture screenshots, attach logs, release resources, stop mock services, and clear scenario context. These activities restore the environment and preserve diagnostics after execution.

Step hooks are commonly used for step-level logging, screenshots, timing, and debugging. They should be lightweight because they run before or after every step. Use them only when the value justifies the overhead.

Common Mistakes with Hooks

Writing Business Logic Inside Hooks

A common mistake is placing business workflows inside hooks. For example, a @Before hook should not search for a product, add it to the cart, and purchase it unless the hook is clearly setting up data for a very specific tagged scenario and the behavior is not the subject of the test. Hidden workflows make feature files misleading because important behavior happens without being visible in Gherkin.

Closing the Browser in Step Definitions

Another mistake is closing the browser inside a step definition. A step named logout should perform logout behavior, not terminate WebDriver. Browser teardown belongs in @After. If teardown is scattered across steps, failed scenarios may leave browsers open and later steps may lose the driver unexpectedly.

Using Hooks for Assertions

Assertions should normally live in step definitions, page objects, API assertion helpers, or validation services. Hooks are not a good place for business assertions because they run automatically and are not visible in the scenario. A hook can collect diagnostics, but it should not replace explicit behavior verification.

Putting Too Much Logic in One Hook

Hooks should remain small and readable. A hook with hundreds of lines of setup or cleanup logic becomes difficult to maintain. Delegate complex work to driver factories, configuration readers, report managers, API clients, database utilities, and cleanup services. The hook should coordinate the lifecycle, not contain the entire framework.

Best Practices for Hooks

Use @Before for setup and @After for cleanup. Keep hooks focused on framework responsibilities rather than business behavior. Use tagged hooks when setup or cleanup applies only to specific scenario categories. Control execution order with the order attribute when multiple hooks exist and order truly matters.

Delegate complex work to helper classes or services. A hook should call DriverFactory.initialize(), not contain every driver setup detail. A cleanup hook should call CleanupService.cleanupScenarioData(), not contain many delete queries. This keeps lifecycle code easy to read and easy to change.

Avoid assertions and business workflows inside hooks. Ensure cleanup in @After runs even if the scenario fails. Capture useful diagnostics for failures, but avoid generating excessive artifacts for every successful scenario unless the project requires detailed evidence. Keep hook behavior predictable and documented so the team understands what happens automatically.

Real-Time Framework Example

A clean Cucumber JVM framework often places hook classes in a dedicated hooks package. Step definitions stay in their own package. Page objects manage UI interactions. Utilities manage driver creation, configuration, reporting, screenshots, and cleanup. This structure separates lifecycle management from business steps.

src
 ├── hooks
 │     └── Hooks.java
 ├── stepdefinitions
 │     └── LoginSteps.java
 ├── pages
 │     └── LoginPage.java
 └── utils
       └── DriverFactory.java
public class Hooks {

    @Before
    public void setup() {
        DriverFactory.initialize();
    }

    @After
    public void tearDown() {
        DriverFactory.quit();
    }
}

The execution is straightforward. Before the scenario runs, Cucumber calls the setup hook and initializes the driver. The scenario steps execute and perform business actions. After the scenario completes, Cucumber calls the teardown hook and closes the driver. The feature file stays focused on the behavior, and the framework lifecycle is managed consistently.

Where Hook Classes Should Be Placed

Hook classes should be placed in a package that is included in the Cucumber glue configuration. If the hooks package is outside the configured glue path, Cucumber will not discover the hook methods, and setup or cleanup will not run. This is a common issue in new Cucumber JVM projects. The hook code may be correct, but the runner or configuration does not scan the package where the hook class lives.

A clear package structure prevents this problem. Many teams keep hooks under a package such as hooks, support, or testsupport. The Cucumber runner then includes that package along with step definitions. For example, if step definitions are in stepdefinitions and hooks are in hooks, both packages must be covered by glue. This keeps lifecycle code discoverable without mixing it into step classes.

Hook classes should also avoid depending on random static state spread across the framework. If a hook needs the driver, scenario context, cleanup service, or report manager, those dependencies should be provided through a consistent framework pattern. Some projects use dependency injection. Some use a scenario context object. Some use factories or managers. The exact approach can vary, but the lifecycle should be predictable and thread-safe, especially when scenarios run in parallel.

Using the Scenario Object in Hooks

Cucumber hooks can receive the Scenario object in many Cucumber JVM setups. This object provides useful information about the current scenario, such as the scenario name, status, tags, and attachment support. It is commonly used in @After hooks to capture screenshots only when a scenario fails or to attach logs and diagnostic files to the report.

@After
public void afterScenario(Scenario scenario) {
    if (scenario.isFailed()) {
        byte[] screenshot = takeScreenshot();
        scenario.attach(screenshot, "image/png", "failure screenshot");
    }
}

The Scenario object helps hooks make smart decisions without putting reporting logic into feature files. A failed UI scenario may need a screenshot. A failed API scenario may need request and response details. A database scenario may need query logs. The hook can check tags or scenario state and attach the right evidence. This makes failure analysis faster while keeping Gherkin clean.

However, the Scenario object should not be used to hide business assertions. It is useful for lifecycle metadata, reporting, and diagnostics. Assertions should still be visible through step definitions and validation helpers. The hook can enrich reports, but it should not secretly decide whether business behavior passed or failed unless the failure is related to setup or cleanup itself.

Interview-Ready Explanation

Hooks are lifecycle methods in Cucumber that execute automatically before or after scenarios or steps. Common hooks include @Before, @After, @BeforeStep, and @AfterStep. They are primarily used for setup and cleanup tasks such as browser initialization, configuration loading, database connections, reporting, screenshots, resource release, and test data cleanup.

Hooks help eliminate duplicate setup code and keep feature files focused on business behavior. They are different from step definitions because hooks are not tied to Gherkin steps. The best practice is to keep hooks lightweight, delegate complex work to helper classes, use tagged hooks for scenario-specific setup, control order when needed, and avoid business logic or assertions inside hooks.

Summary

Hooks are an essential Cucumber JVM concept for managing the test execution lifecycle. They allow common setup and cleanup actions to run automatically without cluttering feature files. @Before prepares the scenario, @After cleans up after it, and step hooks provide optional step-level diagnostics. Tagged hooks and ordered hooks give additional control when different scenarios require different lifecycle behavior.

The golden rules are simple: hooks manage the test lifecycle, not business behavior; use @Before for setup and @After for cleanup; keep hooks small and framework-focused; use tagged hooks for scenario-specific setup; and never replace step definitions with hidden hook logic. When hooks are designed well, Cucumber frameworks become cleaner, more reusable, and easier to maintain.