Tag-Based Hooks in Cucumber JVM
What Are Tag-Based Hooks?
Tag-based hooks in Cucumber JVM are hooks that execute only for scenarios or features matching specific tags. A normal hook without a tag condition can run for every scenario. A tag-based hook runs conditionally. It checks the tags applied in the feature file and executes only when the scenario matches the hook's tag expression. This gives the framework control over which setup and cleanup logic should run for which type of test.
In simple terms, tag-based hooks mean: run this hook only when the scenario has a particular tag. For example, a hook tagged with @UI can launch a browser only for UI scenarios. A hook tagged with @API can initialize Rest Assured only for API scenarios. A hook tagged with @Database can open a database connection only when a scenario needs database access. This prevents unnecessary setup and keeps execution efficient.
Tag-based hooks are part of lifecycle management. They do not replace step definitions. They are not meant to hide business behavior. Their purpose is to apply framework-level preparation or cleanup only when needed. This is especially useful in projects that contain different automation layers, such as Selenium UI tests, API tests, database tests, mobile tests, smoke tests, and regression tests in the same Cucumber suite.
A good tag-based hook strategy makes the framework modular. Each scenario receives only the setup it needs. UI scenarios do not waste time configuring API clients unless required. API scenarios do not launch browsers. Database scenarios do not open unnecessary mobile drivers. Cleanup also becomes more precise because only the resources used by matching scenarios are released.
Why Tag-Based Hooks Are Needed
Not every scenario requires the same setup. A login UI scenario needs a browser and WebDriver. A REST API scenario may need a base URI and authentication token but no browser. A database validation scenario may need a database connection. A smoke scenario may need minimal setup. A full regression scenario may need richer test data or extended reporting. If one generic hook performs every possible setup for every scenario, the framework becomes slow and wasteful.
@Before
public void setup() {
initializeBrowser();
initializeDatabase();
createTestData();
}
With this design, every scenario launches a browser, connects to a database, and creates test data, even if the scenario is a pure API test. That wastes resources. It also makes setup more complex and creates more failure points. An API scenario should not fail because browser setup failed when the API test never needed a browser.
Tag-based hooks solve this problem by narrowing setup to scenario categories. A UI scenario gets UI setup. An API scenario gets API setup. A database scenario gets database setup. This makes execution faster, clearer, and less fragile.
@Before("@UI")
public void setupBrowser() {
initializeBrowser();
}
Only scenarios tagged with @UI run this hook. This is a cleaner design for mixed automation suites.
How Tag-Based Hook Execution Works
Cucumber reads the scenario and its tags before execution. It then evaluates hook tag expressions against those tags. If the expression matches, the hook runs. If the expression does not match, the hook is skipped. After matching hooks execute, the scenario steps run. After the scenario ends, matching tagged @After hooks execute.
Feature File
|
v
Read Scenario Tags
|
v
Match Hook Tags
|
v
Execute Matching Hooks
|
v
Execute Scenario
The tag expression is written inside the hook annotation. The expression can be a single tag, or it can combine tags using and, or, and not. This gives precise control over setup and cleanup. For example, a hook can run only for scenarios that are both @Smoke and @UI. Another hook can run for every scenario except @API.
This matching happens automatically. The feature file does not call the hook directly. The tag controls lifecycle behavior through Cucumber's hook mechanism.
Basic Syntax of Tag-Based Hooks
The syntax is similar to normal hooks, but the annotation includes a tag expression string. A tagged @Before hook runs before matching scenarios. A tagged @After hook runs after matching scenarios.
@Before("@Smoke")
public void smokeSetup() {
}
@After("@Regression")
public void regressionCleanup() {
}
The string inside the annotation is the tag expression. The tag name includes the @ symbol. Forgetting the symbol is a common beginner mistake. "@Smoke" is correct. "Smoke" is not the intended tag expression.
Tagged hooks can also use the order attribute when multiple matching hooks need a specific sequence. This is useful when one tagged setup depends on another, such as loading configuration before initializing a browser.
Basic Tag-Based Hook Example
A basic example uses the @Smoke tag. The feature file marks a scenario as smoke. The hook annotation also uses @Smoke. When the scenario runs, Cucumber sees that the scenario has the required tag and executes the hook before the scenario steps.
@Smoke
Scenario: Login
When the user logs in
Then the dashboard should appear
@Before("@Smoke")
public void smokeSetup() {
System.out.println("Smoke Setup");
}
The execution is:
Smoke Setup
|
v
Login Scenario
If another scenario does not have @Smoke, this hook does not run for that scenario. This conditional behavior is the main value of tag-based hooks.
UI Testing Example
In UI automation, tagged hooks are commonly used to launch browsers only for UI scenarios. A project may contain UI scenarios and API scenarios in the same suite. Launching WebDriver for API-only scenarios is unnecessary and slows execution. The @UI tag solves this cleanly.
@UI
Scenario: Search Product
When the user searches for a product
Then matching products should appear
@Before("@UI")
public void initializeBrowser() {
driver = new ChromeDriver();
}
Only UI scenarios initialize WebDriver. A matching @After("@UI") hook can close the browser after the scenario. This keeps browser lifecycle tied to scenarios that actually use the browser.
@After("@UI")
public void closeBrowser() {
driver.quit();
}
This approach improves performance and reduces false failures. API scenarios are not affected by browser driver issues. UI scenarios get the setup and cleanup they need.
API Testing Example
API scenarios need different setup. They may need Rest Assured configuration, base URI, authentication tokens, default headers, or request specifications. These tasks can be tied to an @API tag.
@API
Scenario: Create User
When the user creation request is submitted
Then the API should return created status
@Before("@API")
public void initializeAPI() {
RestAssured.baseURI = "https://api.example.com";
}
API scenarios avoid unnecessary browser setup, while UI scenarios avoid unnecessary API setup unless they are also tagged with @API. In larger frameworks, the hook may call an API client setup class instead of configuring everything directly.
A tagged API cleanup hook can delete records created by API scenarios. This keeps backend test data controlled without affecting UI-only scenarios that do not create API records.
Database Testing Example
Database setup should also be conditional. Opening a database connection for every scenario wastes resources and can introduce failures unrelated to the current test. A scenario that needs database access can use a tag such as @Database or @DB.
@Database
Scenario: Update Customer
When the customer record is updated
Then the database should reflect the new value
@Before("@Database")
public void connectDatabase() {
database.connect();
}
A matching @After("@Database") hook can disconnect the database. This keeps database lifecycle specific to scenarios that need it. It also makes failures easier to diagnose because database setup does not run for unrelated scenarios.
Tagged Cleanup with @After
Tag-based hooks are not only for setup. They are equally useful for cleanup. A UI scenario that launches a browser should close it. An API scenario that creates backend records should delete them. A database scenario that opens a connection should disconnect. Tagged @After hooks make cleanup conditional and precise.
@After("@UI")
public void closeBrowser() {
driver.quit();
}
Only UI scenarios close the browser because only UI scenarios should have opened one. This avoids null driver errors in API scenarios. It also keeps cleanup aligned with setup. If a tagged @Before("@UI") hook creates a browser, a tagged @After("@UI") hook should release it.
Tagged cleanup is especially useful in mixed suites where different scenario types create different resources. The cleanup method should still delete only data or resources created by the current scenario. Tags decide whether the cleanup category applies; scenario context decides exactly what to clean.
Multiple Tagged Hooks
A project can define multiple tagged hooks. One hook may run for smoke scenarios, another for regression scenarios, another for UI scenarios, and another for API scenarios. Execution depends on the scenario tags. If a scenario has a matching tag, the corresponding hook runs.
@Before("@Smoke")
public void smokeSetup() {
}
@Before("@Regression")
public void regressionSetup() {
}
If a scenario has @Smoke, the smoke setup runs. If it has @Regression, the regression setup runs. If it has both, both hooks can run. This is powerful but requires a clear tag strategy. Too many overlapping tags can make the lifecycle hard to understand.
Scenario with Multiple Tags
A scenario can have more than one tag. For example, a login scenario might be both @Smoke and @UI. In that case, hooks matching either tag may run. A smoke setup hook can prepare smoke-specific reporting, while a UI hook can launch the browser.
@Smoke
@UI
Scenario: Login
When the user logs in
Then dashboard should appear
@Before("@Smoke")
public void smokeSetup() {
}
@Before("@UI")
public void browserSetup() {
}
Both hooks execute because both tags are present. If the order matters, use the order attribute. For example, smoke setup may run before browser setup, or browser setup may run before UI-specific data creation depending on the framework design.
Tag Expressions with AND
The and expression requires all specified tags to be present. This is useful when setup should run only for a precise combination of scenario categories. For example, a hook may run only for scenarios that are both smoke and UI tests.
@Before("@Smoke and @UI")
public void smokeUiSetup() {
}
This hook runs only when the scenario has both @Smoke and @UI. If a scenario has only @Smoke, the hook does not run. If it has only @UI, the hook does not run. This prevents broad setup from running in scenarios that do not need it.
Tag Expressions with OR
The or expression runs the hook when any specified tag is present. This is useful when multiple categories need the same setup. For example, smoke and regression scenarios may both need reporting setup.
@Before("@Smoke or @Regression")
public void setupForTestSuites() {
}
This hook runs for scenarios tagged @Smoke, scenarios tagged @Regression, and scenarios tagged with both. It does not run for scenarios with neither tag. Use or when the same setup applies to more than one category.
Tag Expressions with NOT
The not expression excludes scenarios with a specific tag. This is useful when a hook should run for most scenarios except a certain category. For example, if most scenarios are UI tests but API scenarios should not launch a browser, a hook can use not @API.
@Before("not @API")
public void browserSetup() {
}
This hook runs for every scenario except those tagged @API. Use this pattern carefully. It is convenient, but it can be too broad if the suite later adds mobile, database, or service-only scenarios. In many frameworks, a positive tag such as @UI is clearer than relying on exclusions.
Complex Tag Expressions
Tag expressions can combine and, or, not, and parentheses. This allows precise hook selection. For example, a hook can run for smoke or regression scenarios, but not API scenarios.
@Before("(@Smoke or @Regression) and not @API")
public void setup() {
}
The meaning is: run for smoke or regression scenarios, but exclude API scenarios. Complex expressions are useful, but they should remain readable. If an expression becomes hard to understand, the tag strategy may need simplification. A hook expression should be clear to someone reviewing the framework later.
Feature-Level Tags
Tags can be applied at the feature level. When a feature has a tag, scenarios inside that feature inherit the tag. This is useful when every scenario in the file belongs to the same category. For example, a User Management feature may be tagged @Regression if all scenarios are part of the regression suite.
@Regression
Feature: User Management
Scenario: Add User
When an admin adds a new user
Then the user should be created
The scenario inherits @Regression, so any hook matching @Regression can run. Feature-level tags reduce repetition. Instead of tagging every scenario one by one, the team can tag the whole feature when the category applies to all scenarios.
Use feature-level tags carefully. If only some scenarios require a setup, tag those scenarios individually. Applying a broad feature tag can cause hooks to run for scenarios that do not need them.
Hook Order with Tagged Hooks
Tagged hooks follow the same ordering rules as normal hooks. For @Before hooks, lower order values execute first. For @After hooks, higher order values execute first. Tag matching decides whether the hook applies, and order decides the sequence among matching hooks.
@Before(value = "@Smoke", order = 1)
public void smokeSetup() {
}
@Before(value = "@Smoke", order = 2)
public void browserSetup() {
}
For a smoke scenario, execution is:
Smoke Setup
|
v
Browser Setup
|
v
Scenario
Ordering becomes important when a scenario has multiple tags and several hooks match. Without explicit order, lifecycle behavior can become unpredictable. Use order values whenever sequence matters.
Real-Time Selenium Example
A real UI regression scenario may have both @UI and @Regression. The UI hook initializes the browser. The regression hook loads larger test data. The UI after hook closes the browser. If the scenario has both tags, both setup hooks run.
@UI
@Regression
Scenario: Checkout
When the user completes checkout
Then the order should be confirmed
@Before("@UI")
public void browserSetup() {
}
@Before("@Regression")
public void loadRegressionData() {
}
@After("@UI")
public void closeBrowser() {
}
The execution is browser setup, regression data setup, scenario execution, and browser cleanup. If order matters, add order values. For example, configuration may need to load before both UI and regression setup.
Real-Time API Example
An API smoke scenario may have both @API and @Smoke. The API hook configures the API client. The smoke hook may configure lightweight reporting or smoke-specific data. No browser hook runs unless the scenario also has @UI.
@API
@Smoke
Scenario: Create User
When the create user request is submitted
Then the user should be created
@Before("@API")
public void apiSetup() {
}
@Before("@Smoke")
public void smokeSetup() {
}
This keeps API execution fast and avoids unnecessary WebDriver initialization. A matching API cleanup hook can remove users created during the API scenario.
Common Use Cases for Tag-Based Hooks
UI tests commonly use @Before("@UI") to launch browsers and @After("@UI") to quit them. API tests use @Before("@API") to initialize API clients. Database tests use @Before("@Database") to open database connections and @After("@Database") to disconnect. Mobile tests use @Before("@Mobile") to start Appium. Security tests use @Before("@Security") to load security configuration or tokens.
Tags can also separate execution suites. @Smoke scenarios may run quick setup and minimal data. @Regression scenarios may load broader test data. @E2E scenarios may require multiple services. @NoCleanup or @Cleanup tags can control special cleanup cases, though such tags should be used sparingly and clearly documented.
Real-Time Enterprise Framework
An enterprise Cucumber JVM framework often combines UI, API, and cleanup hooks in one hooks package while delegating actual work to factories and services. The hook class should remain small. It should call driver factories, API client setup, cleanup services, and configuration readers rather than containing all implementation logic directly.
src
├── hooks
│ └── Hooks.java
├── features
│ ├── login.feature
│ └── api.feature
├── pages
├── services
└── utils
public class Hooks {
@Before("@UI")
public void initializeBrowser() {
DriverFactory.initialize();
}
@Before("@API")
public void initializeAPI() {
RestAssured.baseURI = ConfigReader.getApiUrl();
}
@After("@UI")
public void closeBrowser() {
DriverFactory.quit();
}
}
A UI scenario initializes the browser, runs the scenario, and closes the browser. An API scenario initializes API configuration and runs without browser setup. Each scenario gets only the lifecycle support it needs. This is the main value of tag-based hooks in large frameworks.
Designing a Clean Tag Strategy
Tag-based hooks work best when the project has a clean tag strategy. Tags should describe meaningful categories, not random implementation details. A tag such as @UI clearly says the scenario needs browser-based setup. A tag such as @API clearly says API setup applies. A tag such as @Smoke describes execution scope. A tag such as @Database describes a technical dependency. These tags are understandable and reusable.
Problems begin when tags are created without a strategy. Teams may add tags such as @LoginSmokeRegression, @FastUI, @NewSmoke, @Smoke2, or @Temp. Over time, no one knows which tags are official and which are accidental. Hooks may stop running because scenarios use inconsistent tag names. Reports may become messy because the same concept is represented by several tags.
A practical tag strategy should define the main tag groups. For example, layer tags may include @UI, @API, @DB, and @Mobile. Suite tags may include @Smoke, @Regression, and @E2E. Feature tags may identify modules such as @Login, @Checkout, or @UserManagement. Lifecycle hooks should usually depend on layer or suite tags, not overly specific temporary tags.
Documenting tag meaning is important. A short README or framework guide can explain which tags trigger hooks, which tags are used for execution filtering, and which tags are used for reporting. This prevents accidental overlap and keeps the hook lifecycle predictable.
Conditional Cleanup Design
Tagged setup should usually have matching tagged cleanup. If @Before("@UI") initializes the browser, @After("@UI") should quit it. If @Before("@Database") connects to a database, @After("@Database") should disconnect. If @Before("@API") creates API clients, @After("@API") can release API resources or clean records created by API scenarios.
This pairing keeps lifecycle responsibilities balanced. It also prevents cleanup from running where setup never happened. For example, an untagged @After hook that always calls driver.quit() may fail for API scenarios because no driver was created. A tagged @After("@UI") hook avoids that problem by running only for UI scenarios.
Conditional cleanup should still use scenario context. The tag decides whether a cleanup category applies, but context identifies exactly what should be cleaned. A scenario tagged @API may create one user, three orders, or no records at all. The cleanup service should remove only the records registered by that scenario. Tags should not become broad delete rules.
@After("@API")
public void apiCleanup() {
cleanupService.deleteResourcesCreatedByCurrentScenario();
}
This design is safe for parallel execution because each scenario cleans only its own data. It is also easier to debug because cleanup logs can show which tagged cleanup ran and which resource IDs were removed.
Avoiding Overlapping Tagged Hooks
Overlapping tagged hooks can be useful, but they can also create duplicate setup. For example, a scenario tagged @Smoke and @UI may trigger both smoke setup and UI setup. That is fine if the hooks do different things. It becomes a problem if both hooks initialize the browser, load the same data, or start the same report entry.
To avoid overlap, each hook should have a single responsibility. A @UI hook should handle browser lifecycle. A @Smoke hook should handle smoke-specific setup, not browser setup. Shared setup that applies to all scenarios should live in a common hook or setup service. Category-specific setup should live in tagged hooks. This separation reduces duplication.
Order values can control sequence, but they should not hide duplicate responsibility. If two hooks do the same thing and order is used only to prevent one from breaking the other, the design should be simplified. Hook ordering solves sequencing, not unclear ownership.
Review overlapping tags regularly. As suites grow, old hooks may remain even after their purpose has changed. A hook audit can identify duplicate browser setup, repeated configuration loading, unnecessary database connections, or cleanup that runs too broadly.
Troubleshooting Tag-Based Hook Issues
When a tag-based hook does not run, first check the tag spelling. The feature file tag and hook expression must match. @Database and @DB are different tags. Also check that the hook expression includes the @ symbol. Next, check whether the hook class is included in the Cucumber glue path. If Cucumber cannot discover the hook class, no tag expression will help.
If a hook runs unexpectedly, inspect feature-level tags. A scenario may inherit a tag from the feature even if the scenario itself does not show that tag directly. Also inspect complex expressions with and, or, and not. Parentheses matter. A small expression mistake can cause setup to run for more scenarios than intended.
Lifecycle logs are helpful. Log the scenario name, tags, and hook name when tagged hooks run. This makes it clear why a hook executed. For example, logging Running @UI browser setup for Scenario: Login helps diagnose unexpected browser launches. In CI runs, these logs can save time because tag matching is visible without opening every feature file.
@Before("@UI")
public void initializeBrowser(Scenario scenario) {
System.out.println("Running UI setup for: " + scenario.getName());
DriverFactory.initialize();
}
When troubleshooting, also check execution filters. A scenario may be selected for execution because of one tag and then trigger hooks because of another tag. Execution filtering and hook matching are related but not identical. The runner decides which scenarios run; hook expressions decide which lifecycle methods run for those scenarios.
Common Mistakes with Tag-Based Hooks
Forgetting the @ Symbol
The hook expression should include the @ symbol. @Before("@Smoke") is correct. @Before("Smoke") is not the intended tag expression and can cause the hook not to match as expected.
Putting Business Logic Inside Tagged Hooks
Hooks should prepare the environment, not perform business workflows. A @Before("@Smoke") hook should not purchase a product or complete a user journey. Business behavior belongs in Gherkin steps.
Creating Too Many Tags
Excessive tags make maintenance difficult. Tags such as @SmokeRegression, @SmokeUIRegression, and @LoginSmokeRegression create confusion. Prefer simple, composable tags such as @Smoke, @UI, and @Regression.
Duplicate Setup
A normal @Before hook and a tagged @Before hook should not perform the exact same setup. Duplicate setup wastes time and may create conflicts. Centralize shared setup and use tagged hooks only for category-specific setup.
Overusing Negative Expressions
Expressions such as not @API can be useful, but they may become too broad as the suite grows. A positive tag such as @UI is often clearer because it states exactly which scenarios need browser setup.
Best Practices for Tag-Based Hooks
Use tag-based hooks to execute setup and cleanup only when needed. Keep tags meaningful and aligned with test categories such as @UI, @API, @Smoke, @Regression, @Database, and @Mobile. Use tag expressions with and, or, and not for flexible hook selection, but keep expressions readable.
Combine tagged hooks with the order attribute when execution sequence matters. Keep hooks focused on framework responsibilities rather than business workflows. Avoid excessive or overlapping tags that make lifecycle behavior hard to predict. Apply tags at the feature level only when all scenarios in that feature require the same behavior.
Document tag meaning for the team. A tag strategy should be consistent across the project. If one team member uses @DB and another uses @Database, hooks may not run as expected. Consistent naming prevents subtle execution problems.
Interview-Ready Explanation
Tag-based hooks are Cucumber hooks that execute only for scenarios or features matching specified tags. They enable different setup and cleanup logic for different categories of tests, such as UI, API, smoke, regression, database, or mobile scenarios. They improve performance because each scenario receives only the setup it needs.
Cucumber supports tag expressions using and, or, and not for flexible hook selection. Tagged hooks follow the same execution order rules as regular hooks and can use the order attribute. Best practice is to keep tags simple, meaningful, and consistent, avoid business logic inside hooks, and use tagged setup or cleanup only when it is genuinely scenario-category specific.
Summary
Tag-based hooks are a practical Cucumber JVM feature for conditional setup and cleanup. They allow UI scenarios to launch browsers, API scenarios to initialize clients, database scenarios to open connections, and specific suites such as smoke or regression to receive the setup they require. This makes execution faster, cleaner, and more modular.
The golden rules are clear: use tag-based hooks only when setup or cleanup is required, keep tags simple and consistent, use tag expressions for precise control, avoid business logic inside hooks, and combine tags with order when multiple matching hooks must execute in a specific sequence. When used well, tag-based hooks make Cucumber frameworks more maintainable and efficient.