Cucumber Framework Architecture
What Is Cucumber Framework Architecture?
Cucumber framework architecture is the overall design and organization of a Cucumber automation framework. It defines how feature files, runner classes, step definitions, hooks, page objects, API services, utilities, configuration, test data, scenario context, reporting, logging, screenshots, and CI/CD integration work together. A good architecture separates responsibilities into clear layers so the framework remains maintainable as the number of scenarios, contributors, environments, and execution pipelines grows.
The most common mistake in beginner Cucumber projects is placing everything inside step definitions. Selenium locators, WebDriver actions, REST Assured calls, payload creation, assertions, test data, screenshots, logging, and cleanup all end up in the same methods. This may work for a few examples, but it becomes hard to maintain in real projects. Enterprise architecture avoids that problem by assigning every responsibility to the correct layer.
In simple terms, Cucumber framework architecture is the blueprint that defines how business-readable feature files connect to executable automation code without turning the framework into a collection of duplicated scripts.
Why Framework Architecture Is Important
Framework architecture matters because automation suites grow. A project may begin with five scenarios, but it can quickly reach hundreds or thousands of scenarios across UI, API, database, mobile, and integration layers. Without structure, duplicate code increases, step definitions become large, maintenance becomes difficult, debugging becomes harder, reusability decreases, and team collaboration suffers.
With proper architecture, each layer has a clear job. Feature files describe behavior. Step definitions coordinate execution. Page objects handle UI interactions. API service classes handle REST calls. Utilities provide reusable helpers. Configuration stores environment settings. Test data stays outside code. Reports and logs provide visibility. This separation makes the framework easier to change and easier to understand.
A well-designed framework also improves onboarding. New team members can understand where to place code and where to look when debugging. They do not need to search through unrelated files to find a locator, payload builder, or wait method. Architecture gives the project a map.
High-Level Framework Architecture
At a high level, a Cucumber framework starts with feature files. The runner launches Cucumber execution and points to feature and glue locations. Step definitions connect Gherkin steps to Java code. From there, execution flows into UI or API layers depending on the scenario. Utilities, configuration, test data, and scenario context support those layers. Reports, logs, and screenshots capture execution evidence.
Feature Files
-> Cucumber Runner
-> Step Definitions
-> UI Layer / API Layer
-> Utility Components
-> Configuration / Test Data / Scenario Context
-> Selenium / REST Assured
-> Application Under Test
-> Reports + Logs + Screenshots
This layered design keeps Cucumber readable and automation maintainable. The feature file should not know about Selenium locators or REST Assured syntax. The step definition should not build complex JSON payloads. The page object should not decide business rules. Each layer collaborates with the others through clean responsibilities.
Main Components
An enterprise Cucumber framework usually contains feature files, runner classes, step definitions, hooks, page objects, API services, request builders, response validators, utilities, configuration, test data, scenario context, driver management, reporting, logging, screenshots, and CI/CD integration. Each component exists to solve a specific problem.
Cucumber Framework
|-- Feature Files
|-- Runner
|-- Step Definitions
|-- Hooks
|-- Page Objects
|-- API Services
|-- Utilities
|-- Configuration
|-- Test Data
|-- Scenario Context
|-- Reports
|-- Logs
|-- CI/CD
The goal is not to create too many folders for appearance. The goal is to separate code when separation improves clarity, reuse, and maintainability. A small project may have fewer layers. A larger enterprise project usually benefits from stronger separation.
Feature Files Layer
The feature files layer contains business requirements, acceptance criteria, test scenarios, and living documentation. Feature files are written in Gherkin and should be readable by testers, developers, business analysts, and product owners. They should describe what behavior is expected, not how Selenium or REST Assured performs the work.
Feature: Login
Scenario: Valid Login
Given User opens application
When User logs in
Then Dashboard appears
Feature files should stay business-focused. Avoid UI-driven steps such as clicking buttons, entering text fields, and selecting dropdowns unless the UI interaction itself is the behavior under test. The implementation details belong in step definitions, page objects, and service classes. Good feature files make reports easier to understand because reports display the same scenario names and step text.
Runner Layer
The runner layer starts Cucumber execution. It tells Cucumber where feature files are located, where glue code exists, which plugins should run, which tags should be included or excluded, and which reporting formats should be generated. The runner is the entry point for local execution and CI/CD execution.
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/resources/features",
glue = "stepdefinitions"
)
Runner configuration should be clear and predictable. If features or glue paths are wrong, steps may be undefined or hooks may not run. If reporting plugins are missing, execution results may not be published. If tag expressions are wrong, the wrong scenarios may execute. The runner is small but important.
Step Definition Layer
Step definitions connect Gherkin steps to Java methods. They are the bridge between business-readable scenarios and executable automation code. A good step definition coordinates flow but does not contain heavy implementation details. It should call page objects, API services, builders, validators, utilities, or scenario context where appropriate.
loginPage.login(username, password);
Step definitions should remain thin. They should not contain complex Selenium code, REST Assured request construction, long assertions, file parsing, or duplicated waits. Thin steps are easier to read, reuse, debug, and maintain. Fat step definitions are one of the clearest signs of weak framework architecture.
Hooks Layer
Hooks perform common setup and cleanup tasks before and after scenarios. Common responsibilities include browser initialization, browser closure, authentication setup, database cleanup, test data preparation, screenshot capture, logging, and report attachments. Hooks reduce duplicated setup code across scenarios.
@Before
public void beforeScenario() {
// setup
}
@After
public void afterScenario() {
// cleanup
}
Hooks should be focused and predictable. If hooks become too large, they hide important behavior and make debugging difficult. Use tag-based hooks when setup applies only to specific scenarios, such as UI setup for @UI tests or API authentication for @API tests. Hook order should be clear when multiple hooks are used.
UI Layer with Page Object Model
The UI layer usually follows the Page Object Model. Page objects store page locators and page actions. They encapsulate Selenium code so step definitions do not interact directly with WebDriver. Examples include LoginPage, HomePage, DashboardPage, and CustomerPage.
Only page objects or dedicated UI action utilities should directly interact with WebDriver. This keeps UI logic centralized. If a locator changes, the team updates the page object instead of searching through many step definitions. Page objects also make UI actions reusable across scenarios.
A good page object exposes meaningful methods such as login(), createCustomer(), or getDashboardMessage(). It should not expose every internal locator to the step definition. Encapsulation is the main value of the Page Object Model.
API Layer
The API layer stores REST Assured code, service methods, API calls, and reusable API operations. Examples include LoginApi, CustomerApi, and OrderApi. Step definitions should call these service methods instead of building requests directly.
API logic should not be placed inside step definitions. If request construction, headers, authentication, response extraction, and validation are repeated inside steps, maintenance becomes difficult. API service classes centralize endpoint logic and make it easier to reuse API operations across scenarios.
This layer is also useful for hybrid UI and API frameworks. A scenario may create data through an API and then validate behavior through the UI. Clean API services make that setup fast and reliable.
Request Builder Layer
The request builder layer creates reusable request payloads. Examples include CustomerRequestBuilder, LoginRequestBuilder, and OrderRequestBuilder. Builders prevent duplicated JSON construction across tests and make payload creation easier to maintain.
Without request builders, teams often copy JSON strings into multiple step definitions. When the API contract changes, every copied payload must be updated. A builder centralizes that logic. It can also generate default values, override specific fields, and create valid or invalid payloads for positive and negative scenarios.
Response Validation Layer
The response validation layer centralizes assertions and response checks. Examples include CustomerValidator, LoginValidator, and OrderValidator. Validators help reuse common checks and keep step definitions clean.
Centralized validators are useful when multiple scenarios need the same response checks. For example, several customer scenarios may need to verify customer ID, name, status, and audit fields. Placing those assertions in a validator reduces duplication and improves consistency. Validators should produce clear assertion messages so reports are easier to debug.
Utility Layer
The utility layer contains reusable helper classes. Common utilities include WaitUtils, DateUtils, FileUtils, JsonUtils, ScreenshotUtils, ExcelUtils, and RandomDataUtils. Utilities eliminate duplicate helper code and keep page objects, API services, and step definitions focused.
Utilities
|-- WaitUtils
|-- DateUtils
|-- FileUtils
|-- JsonUtils
|-- ScreenshotUtils
|-- ExcelUtils
|-- RandomDataUtils
Utilities should remain genuinely reusable. Do not turn utilities into a dumping ground for unrelated logic. If a helper belongs only to a specific page or service, keep it near that page or service. A good utility layer reduces duplication without hiding business behavior.
Configuration Layer
The configuration layer stores configurable values such as base URL, browser, timeout, environment, API base URI, database details, report path, download path, and execution mode. Configuration values should not be hardcoded in step definitions or page objects.
baseUrl=https://qa.example.com
browser=chrome
timeout=30
Centralized configuration makes the same framework work across QA, staging, UAT, and local environments. It also supports CI/CD, where values may come from properties files, environment variables, command-line parameters, or pipeline secrets. Configuration should be easy to override safely.
Test Data Layer
The test data layer stores external data used by scenarios. Data may be stored in JSON, CSV, Excel, properties files, SQL scripts, or generated dynamically. Keeping data separate from automation logic improves maintainability and supports data-driven testing.
testdata
|-- login.json
|-- customer.json
|-- order.csv
Test data strategy should match the scenario type. Simple examples may use Scenario Outline tables. Complex API payloads may use JSON files or builders. Large data-driven tests may use CSV or Excel. Stable enterprise suites often prefer dynamically created data because it reduces dependency on manually maintained shared records.
Scenario Context
Scenario context is used to share data between steps in the same scenario. For example, a login step may store an access token, and a later customer creation step may use that token. An API step may create a customer ID, and a UI step may verify that customer. Scenario context avoids global variables and keeps data scoped to the scenario.
Login
-> Access Token
-> Scenario Context
-> Customer Creation
-> Customer Update
Scenario context must be designed carefully for parallel execution. Shared static variables can cause data leakage between scenarios. A good context implementation keeps scenario data isolated and predictable. This is essential when tests run in parallel in CI/CD.
Driver Management
Driver management handles browser creation, browser cleanup, thread safety, browser reuse, and driver configuration. A centralized DriverFactory can create Chrome, Firefox, Edge, or remote WebDriver instances based on configuration. It can also support headless mode, grid execution, and browser options.
DriverFactory
-> ChromeDriver
-> FirefoxDriver
-> EdgeDriver
Centralized driver management prevents WebDriver setup from being duplicated across hooks and step definitions. It also makes it easier to change browser configuration, add Selenium Grid, support parallel execution, or update driver creation logic. Thread safety is critical when multiple UI scenarios run at the same time.
Reporting Layer
The reporting layer generates execution reports such as HTML, JSON, JUnit XML, Allure, and Extent Reports. Reports should include execution summary, failed scenario details, screenshots, logs, exception messages, API request and response evidence, timing, and environment information when applicable.
Reporting should not control the test logic. It should observe and document execution. A good reporting layer is centralized so reports can be improved without editing every step definition. CI/CD pipelines should publish reports even when tests fail because failed runs need evidence the most.
Logging Layer
The logging layer records execution details such as scenario start, browser actions, API requests, API responses, warnings, errors, and cleanup activity. Common logging frameworks include Log4j 2, SLF4J, and Logback. Logs complement reports by showing the chronological execution trail.
Logs should be meaningful and safe. Avoid logging passwords, tokens, API keys, session cookies, credit card numbers, or personal data. Use appropriate log levels such as INFO, WARN, ERROR, and DEBUG. Archive logs in CI/CD so remote failures can be investigated after execution.
Project Structure
A common enterprise structure separates Java code from resources and groups framework responsibilities into packages. The exact names can vary, but the structure should make ownership clear. Feature files and test data usually live under test resources, while runners, step definitions, hooks, pages, API services, builders, validators, utilities, configuration, context, and factories live under test Java packages.
src
|-- test
| |-- java
| | |-- runners
| | |-- stepdefinitions
| | |-- hooks
| | |-- pages
| | |-- api
| | |-- builders
| | |-- validators
| | |-- context
| | |-- utils
| | |-- config
| | |-- factory
| |
| |-- resources
| |-- features
| |-- testdata
| |-- schemas
| |-- config.properties
| |-- log4j2.xml
This structure helps teams navigate the framework. When someone needs to update a locator, they go to pages. When they need to change an endpoint call, they go to API services. When they need to add a new feature file, they go to resources. Organization reduces friction.
Execution Flow
The execution flow begins with the runner. The runner loads feature files and glue code. Cucumber executes scenarios and hooks. Step definitions call page objects or API services. The application is exercised through Selenium or REST Assured. Assertions validate outcomes. After execution, hooks perform cleanup and reporting captures results.
Runner
-> Feature File
-> Step Definition
-> Hook Before
-> Page Object / API Service
-> Application
-> Validation
-> Hook After
-> Report
Understanding this flow is important for debugging. If a feature is not found, check runner paths. If a step is undefined, check glue. If setup fails, check hooks. If UI interaction fails, check page objects and driver management. If API validation fails, check services, builders, and validators.
Data Flow
Data flow explains how data moves through the framework. Data may begin in a feature file Examples table, data table, external file, or dynamic generator. Step definitions receive that data and pass it to builders, services, page objects, or validators. API responses may store IDs or tokens in scenario context for later steps.
Feature File
-> Examples
-> Step Definition
-> Request Builder
-> REST Assured
-> API
-> Response Validator
Clean data flow prevents hidden dependencies. Avoid global data that can be changed by another scenario. Avoid hardcoded data that exists only in one environment. Prefer scenario-specific data and explicit context. This makes tests more reliable and easier to run in parallel.
UI Automation Flow
UI automation flow connects feature steps to browser actions through page objects. The feature file describes behavior. The step definition calls a page object method. The page object uses WebDriver to interact with the application. Assertions validate the visible result or application state.
Feature File
-> Step Definition
-> Page Object
-> WebDriver
-> Application
-> Assertion
This flow keeps Selenium details out of Gherkin and step definitions. It also makes page interactions reusable. If a login flow is used in many scenarios, it belongs in a page object or reusable action method, not copied into every step.
API Automation Flow
API automation flow connects feature steps to service calls through API classes. The feature file describes the behavior. Step definitions call API services. API services use REST Assured to send requests. Builders create payloads. Validators check responses. Scenario context stores values needed by later steps.
Feature File
-> Step Definition
-> API Service
-> REST Assured
-> REST API
-> Response Validation
This structure makes API automation reusable and easier to maintain. Endpoint changes belong in service classes. Payload changes belong in builders. Assertion changes belong in validators. Step definitions remain readable.
CI/CD Integration
CI/CD integration allows automation to run automatically in build pipelines. A typical flow starts with a Git commit, then Jenkins or another CI server checks out code, runs Maven or Gradle, executes the Cucumber runner, generates reports, archives artifacts, and notifies the team.
Git
-> Jenkins
-> Maven
-> Runner
-> Cucumber
-> Reports
-> Notification
A framework designed for CI/CD should not depend on local machine assumptions. Paths, browsers, credentials, environments, and report locations should be configurable. Reports and logs should be archived even when tests fail. CI/CD readiness is one of the marks of enterprise architecture.
Common Mistakes
Fat step definitions are the most common mistake. A step definition that builds payloads, calls APIs, performs assertions, captures screenshots, writes logs, and manages cleanup is doing too much. Selenium code everywhere is another mistake. WebDriver interaction should be centralized in page objects or UI utilities. Hardcoded test data is also risky because it makes tests environment-dependent.
Duplicate utility methods create maintenance problems. If every package has its own date helper, JSON parser, wait method, or screenshot helper, behavior becomes inconsistent. No separation of layers is another serious issue. Mixing feature files, Selenium code, API code, configuration, data, and utilities makes the framework difficult to scale.
Best Practices
Keep feature files business-readable and focused on behavior. Keep step definitions thin. Use Page Object Model for UI automation. Use API service classes for API automation. Centralize configuration and test data. Use scenario context for shared data inside a scenario. Create reusable utilities only when they provide real reuse. Separate request builders and validators.
Capture screenshots and logs automatically. Integrate reporting and CI/CD. Use meaningful package names. Avoid global mutable state. Design for parallel execution from the beginning if the suite is expected to grow. Review architecture periodically because frameworks evolve as project needs change.
Enterprise Architecture Diagram
An enterprise architecture can be visualized as several layers working together. Feature files and runner classes start the flow. Step definitions coordinate behavior. Hooks, page objects, and API services handle setup and actions. Utilities, builders, validators, configuration, test data, and context support execution. Selenium and REST Assured interact with the application. Reports, logs, and screenshots provide evidence.
Feature Files
-> Runner Class
-> Step Definitions
-> Hooks / Page Objects / API Services
-> Utilities / Request Builders
-> Response Validators
-> Selenium / REST Assured
-> Application Under Test
-> Reports + Logs + Screenshots
This diagram should be treated as a guide, not a rigid rule. Each project may adjust layers based on size, team, application type, and technology stack. The principle remains the same: each layer should have one clear responsibility.
Layer Responsibilities
Clear layer responsibilities make the framework easier to maintain. Feature files contain business scenarios. Runners start execution. Step definitions connect Gherkin to code. Hooks handle setup and cleanup. Page objects handle UI interactions. API services handle API interactions. Request builders create payloads. Validators centralize assertions. Utilities provide helpers. Configuration stores environment settings. Test data stores inputs. Scenario context shares data between steps. Reports and logs provide visibility.
| Layer | Responsibility |
|---|---|
| Feature Files | Business scenarios |
| Runner | Starts execution |
| Step Definitions | Connect Gherkin to code |
| Hooks | Setup and cleanup |
| Page Objects | UI interactions |
| API Services | API interactions |
| Request Builders | Build request payloads |
| Validators | Centralized assertions |
| Utilities | Reusable helper methods |
| Configuration | Environment settings |
| Test Data | External test data |
| Scenario Context | Share data between steps |
| Reports | Execution reporting |
| Logs | Execution tracing |
Designing for Parallel Execution
Enterprise frameworks often need parallel execution to reduce runtime. Architecture must support this from driver management through scenario context and reporting. WebDriver instances should be thread-safe. Scenario context should be isolated per scenario. Test data should avoid collisions. Reports and screenshots should use unique names. Shared static variables should be avoided unless they are immutable or deliberately synchronized.
Parallel execution problems can be difficult to debug because failures may appear random. A customer created by one scenario may be modified by another. A screenshot may be attached to the wrong report node. A driver instance may be reused by the wrong thread. Good architecture prevents these problems by keeping state isolated and ownership clear.
Designing for Maintainability
Maintainability means the framework can change without excessive effort. Applications change, APIs evolve, UI locators move, data rules shift, and environments are updated. A layered architecture localizes change. Locator changes affect page objects. Endpoint changes affect API services. Payload changes affect request builders. Assertion changes affect validators. Configuration changes affect config files or providers.
Maintainability also depends on naming. Package names, class names, method names, feature names, and scenario names should be meaningful. A future maintainer should understand the structure without asking the original author. Good architecture is readable architecture.
Designing for Team Collaboration
Automation frameworks are rarely maintained by one person forever. Team collaboration requires predictable structure and coding conventions. Developers and testers should know where to add new step definitions, where to place page objects, where test data belongs, how to add API services, and how reports are generated.
A shared architecture reduces merge conflicts and duplicated effort. If everyone follows the same package structure and responsibility boundaries, the framework grows consistently. Code reviews should protect these boundaries. When someone adds Selenium code directly inside a step definition or hardcodes environment data, review should catch it early.
When to Add a New Layer
Do not add layers just to make a project look enterprise-level. Add a layer when it solves a real problem. Request builders are useful when payload creation is repeated or complex. Validators are useful when response assertions are reused. Scenario context is useful when steps need to share data. A driver factory is useful when browser creation must be centralized and configurable.
Too little architecture creates duplication. Too much architecture creates unnecessary complexity. The best architecture is proportional to the project. It should be simple enough to understand and strong enough to scale.
Architecture Review Process
Framework architecture should be reviewed regularly. As the test suite grows, patterns that were acceptable in a small project may become painful. A few duplicated waits may not matter at first, but hundreds of duplicated waits create maintenance risk. A few hardcoded values may seem harmless, but they become blockers when the suite must run across QA, staging, and CI environments.
An architecture review should look for fat step definitions, duplicated locators, repeated API payloads, hardcoded configuration, weak reporting, unclear package names, missing cleanup, shared mutable state, and fragile test data. The review should lead to practical improvements, not abstract refactoring. The goal is to keep the framework easy to use and easy to change.
Dependency Management
Enterprise frameworks depend on several libraries: Cucumber, Selenium, REST Assured, JUnit or TestNG, logging libraries, reporting libraries, JSON utilities, Excel utilities, and build plugins. Dependency versions should be managed carefully. Random version upgrades can break compatibility, while outdated libraries can create security and maintenance problems.
A good framework centralizes dependency versions in Maven or Gradle configuration. Teams should update dependencies deliberately, run regression checks after upgrades, and document major changes. Cucumber adapter versions, Selenium versions, browser drivers, and reporting tool versions should be aligned. Dependency management is part of architecture because it affects framework stability.
Package Naming and Code Navigation
Package naming should make the framework easy to navigate. A new contributor should be able to guess where a class belongs. Runner classes should be under runners. Step definitions should be under step definitions. Page objects should be under pages. API services should be under api or services. Utilities should be under utils only when they are truly reusable.
Unclear package naming slows maintenance. If every helper class goes into a generic common package, the project becomes harder to understand. Package structure should reflect responsibility. Clear naming helps code review, onboarding, debugging, and long-term ownership.
Preventing Architecture Drift
Architecture drift happens when the original framework design slowly weakens as people add quick fixes. A step definition gets a small locator. Then another gets an API call. Then a third gets file parsing. Over time, the framework no longer follows its own design. This is common in fast-moving projects unless code reviews protect the architecture.
To prevent drift, teams should define simple rules and enforce them consistently. Selenium code belongs in page objects. REST Assured code belongs in API services. Test data belongs in data files, builders, or generators. Configuration belongs in configuration providers. Assertions belong in validators when reused. Step definitions coordinate; they do not become automation scripts.
Architecture and Debugging
Good architecture makes debugging easier because failures point to the right layer. If a UI locator fails, the page object is the first place to inspect. If an API payload is wrong, the request builder is the first place to inspect. If a shared value is missing, scenario context is the first place to inspect. If environment values are wrong, the configuration layer is the first place to inspect.
Poor architecture makes debugging slower because responsibilities are mixed. A single step definition may contain data setup, browser code, API code, assertions, and reporting. When it fails, the investigator must untangle everything. Layered architecture narrows the search area and makes root cause analysis faster.
Architecture and Reporting Quality
Reporting quality depends on architecture. If scenario names are clear, reports are clear. If step definitions log meaningful business actions, reports become easier to read. If screenshot utilities and logging utilities are centralized, every failure gets consistent evidence. If API services attach sanitized requests and responses, API failures become easier to debug.
Reports should not be patched into random places. Reporting hooks and utilities should be part of the framework design. This ensures that evidence is captured consistently across UI, API, and hybrid scenarios. A good report is often the visible result of good architecture underneath.
Scaling from Small to Enterprise Frameworks
A small Cucumber project may begin with a runner, a feature file, and a few step definitions. That is acceptable for learning. As soon as the suite grows, the framework should introduce page objects, API services, utilities, configuration, test data management, reporting, and logging. Architecture should scale in response to real needs.
The transition should be deliberate. Do not rewrite everything at once unless the current structure is blocking progress. Move repeated Selenium code into page objects. Move repeated API code into services. Move repeated payloads into builders. Move repeated assertions into validators. Gradual improvement keeps the framework usable while reducing long-term maintenance cost.
Interview-Ready Summary
Cucumber framework architecture organizes an automation framework into well-defined layers, each with a single responsibility. Feature files describe business behavior. Step definitions coordinate execution. Hooks manage setup and cleanup. Page objects contain Selenium UI logic. API service classes contain REST Assured logic. Request builders create payloads, validators centralize assertions, utilities provide helpers, configuration stores environment settings, test data remains external, and scenario context shares data safely between steps.
Reporting, logging, screenshots, and CI/CD integration provide visibility throughout the testing lifecycle. A layered architecture improves scalability, readability, maintainability, debugging, reusability, and collaboration in enterprise automation projects. The strongest frameworks keep business behavior readable while keeping implementation details in the correct technical layers.
Golden Rules
Keep feature files business-focused and step definitions thin. Place Selenium code in page objects and REST Assured code in API service classes. Separate configuration, test data, utilities, request builders, and validators into dedicated layers. Use hooks, scenario context, and centralized driver management to avoid duplication and improve consistency.
Design the framework with modular layers so it can scale, integrate with CI/CD, support reporting and logging, and remain easy to maintain. The practical takeaway is clear: architecture is what allows a Cucumber framework to grow from a few scenarios into a reliable enterprise automation suite.