PicoContainer and Spring Concept in Cucumber
What Are PicoContainer and Spring?
PicoContainer and Spring are dependency injection frameworks used to create, manage, and provide objects to Cucumber step definitions and other automation framework classes. In a Cucumber framework that uses Selenium for UI testing and REST Assured for API testing, many classes need shared objects. Step definitions may need page objects. Page objects may need WebDriver. API services may need request builders, authentication helpers, and configuration values. Multiple step classes may need the same scenario context. PicoContainer and Spring help manage this object graph without manual object creation scattered throughout the codebase.
Instead of writing new LoginPage(), new CustomerService(), or new ScenarioContext() inside every step definition, a dependency injection framework creates those objects and injects them where they are required. The consuming class receives a ready-to-use dependency through its constructor, field, or setter depending on the chosen style. Constructor injection is usually preferred because it makes dependencies explicit and keeps required collaborators visible.
In simple terms, PicoContainer and Spring allow Cucumber to automatically share page objects, services, WebDriver-related objects, scenario context, and utilities across step definitions without every class manually creating its own copy. This leads to cleaner code, better object lifecycle management, reduced duplication, and more reliable scenario execution.
Why Dependency Injection Frameworks Are Needed
A real Cucumber project rarely has only one step definition class. It usually has separate classes for login, customer management, order processing, payment, reports, API setup, cleanup, and validation. Each class may need access to related page objects, API services, context data, configuration values, and helper classes. If every class creates its own dependencies, the framework quickly becomes inconsistent.
LoginSteps
CustomerSteps
OrderSteps
PaymentSteps
Each of these classes may require WebDriver, page objects, services, and scenario data. Without dependency injection, each class may create its own instances. One class may create a new context object and store a token in it, while another class creates a different context object and cannot see that token. One class may create a page object with one driver, while another class creates a page object with a different driver. This type of object inconsistency causes confusing failures.
Dependency injection frameworks solve this problem by managing object creation centrally. They decide which object should be created, how it should be constructed, and how long it should live. In Cucumber, the most useful lifecycle is often scenario scope, where each scenario gets its own fresh set of objects. That keeps scenarios independent and helps parallel execution remain stable.
Without Dependency Injection
Without a dependency injection framework, step definitions often create their dependencies directly. This may look simple in the beginning, especially when a project is small. A login step class needs a login page, so the developer creates the page inside the class. Later, another step class needs the same page and creates another copy. Then the page constructor changes, and every direct creation must be updated.
public class LoginSteps {
LoginPage page = new LoginPage();
}
The problem is not only duplication. Direct object creation creates tight coupling. LoginSteps now depends on a concrete LoginPage constructor. It also knows too much about how the page object is built. If the page object needs WebDriver, wait utilities, logging, configuration, or scenario context, the step definition must provide those too. The step class becomes responsible for both business mapping and object construction.
This design becomes harder to test, harder to refactor, and harder to scale. It also increases the chance that two step classes use different instances when they should share one scenario-scoped object. In larger frameworks, direct object creation inside step definitions is one of the main reasons code becomes messy.
With Dependency Injection
With dependency injection, the class receives what it needs from outside. The step definition does not create the page object. The DI framework creates it and passes it through the constructor. The class is still dependent on LoginPage, but it no longer controls how that dependency is created.
public class LoginSteps {
private final LoginPage page;
public LoginSteps(LoginPage page) {
this.page = page;
}
}
This approach makes the code cleaner. The constructor clearly shows what the step definition needs. The DI framework becomes responsible for object creation. If LoginPage later needs WebDriver or another helper, the container can resolve that dependency. The step definition remains focused on scenario behavior.
Dependency injection also improves consistency. When the same scenario context is injected into multiple step definition classes, they can share scenario-specific values safely. When page objects are injected, they can use the same driver instance for the scenario. When services are injected, setup and validation logic can reuse the same configured clients.
Object Management Flow
The object management flow in Cucumber usually starts when the scenario begins. Cucumber identifies the step classes needed for execution. The dependency injection framework creates those classes and resolves their dependencies. If a step definition needs a page object, the container creates the page object. If the page object needs a driver manager, the container resolves that too. The scenario then executes using the object graph prepared by the framework.
Cucumber
-> DI Framework
-> Create Objects
-> Inject Objects
-> Step Definitions
This means developers can focus on test behavior instead of manual object construction. The benefit becomes more visible when a scenario touches many layers. A step class may call a business service. That service may call an API client. The API client may use configuration and authentication helpers. The DI framework can wire this collaboration cleanly.
What Is PicoContainer?
PicoContainer is a lightweight dependency injection framework. In Cucumber JVM projects, it is popular because it is simple, fast, and focused. It does not bring a large ecosystem or heavy configuration model. It mainly helps Cucumber create objects and inject dependencies through constructors. For many automation frameworks, that is exactly what is needed.
PicoContainer works well when the project is a standalone Cucumber automation framework and does not already use Spring. It is especially useful for sharing scenario context, page objects, services, and helper classes across multiple step definition classes. It keeps setup small while still solving the core object-management problem.
Teams often choose PicoContainer because the learning curve is lower. Testers who are not full-time application developers can understand constructor injection quickly. They can see the required dependencies in the constructor and trust Cucumber with PicoContainer to provide them during scenario execution.
PicoContainer Architecture
In a PicoContainer-based Cucumber framework, the feature file is executed through the runner. Cucumber uses PicoContainer to create step definition instances. Those step definitions receive page objects, services, and context classes through constructor injection. Page objects can receive WebDriver or driver managers depending on the framework design.
Feature File
-> Runner
-> PicoContainer
-> Step Definitions
-> Page Objects
-> WebDriver
This architecture is intentionally simple. The container does not need complex annotations for every object in many cases. Constructor parameters express the dependency graph. If LoginSteps requires LoginPage, PicoContainer can supply it. If CustomerSteps requires ScenarioContext, PicoContainer can supply the same context instance for that scenario.
PicoContainer Object Flow
PicoContainer usually creates objects for a scenario, injects them into step definitions, lets the scenario execute, and then discards the objects when the scenario finishes. This lifecycle is a good match for Cucumber because scenarios should be independent. Data created in one scenario should not leak into the next scenario unless the framework deliberately stores it elsewhere.
Scenario Starts
-> Create Objects
-> Inject Objects
-> Execute Scenario
-> Destroy Objects
Scenario-level object management helps avoid shared-state problems. For example, one scenario may create a customer ID and store it in scenario context. Another scenario should not accidentally read that ID. By creating a fresh context per scenario, PicoContainer helps keep tests isolated.
Example with PicoContainer
A common PicoContainer example is injecting a page object into a step class. The step class declares what it needs through the constructor. Cucumber and PicoContainer handle the object creation. The step definition can use the page object directly.
public class LoginSteps {
private final LoginPage loginPage;
public LoginSteps(LoginPage loginPage) {
this.loginPage = loginPage;
}
}
If the page object itself needs another dependency, such as WebDriver, it can also declare that in its constructor. The container builds the chain. This lets classes stay focused on their own responsibilities. Step definitions map Gherkin steps. Page objects handle UI behavior. Driver management remains centralized.
PicoContainer Benefits
PicoContainer is lightweight, requires minimal configuration, starts quickly, and is easy to understand. It is suitable for many Selenium and REST Assured automation projects because it solves the core problem without adding a large framework. It encourages constructor injection, which makes dependencies visible and improves code readability.
It also supports scenario-level object sharing. Multiple step definition classes can receive the same scenario context instance during one scenario. This is one of the most useful features in BDD automation because scenarios often span several step classes. A login token created in one step can be stored in context and used by a later customer or order step.
What Is Spring?
Spring is a comprehensive Java framework that includes dependency injection, inversion of control, configuration management, web development support, security, data access, transaction management, and many enterprise features. In Cucumber automation, Spring is mainly used for its dependency injection container, but it can also be useful when tests need access to Spring profiles, beans, configuration, or application-level clients.
Spring is more powerful than PicoContainer, but it is also heavier. It makes sense when the project already uses Spring or Spring Boot, when test code needs existing application beans, or when the automation framework requires advanced dependency management. It may be unnecessary for a small standalone automation suite that only needs simple constructor injection.
Spring Architecture
In a Spring-based Cucumber framework, Cucumber integrates with a Spring context. The Spring container manages beans, injects dependencies, and controls object lifecycle according to configuration. Step definitions, page objects, services, clients, utilities, and context classes may be defined as Spring-managed components.
Feature File
-> Runner
-> Spring Context
-> Beans
-> Step Definitions
-> Page Objects
-> Application
This architecture is useful when the automation framework needs the broader Spring ecosystem. For example, tests may use Spring profiles for QA and UAT, inject configured REST clients, load property files through Spring, or reuse service beans from a test support module. Spring can manage this complexity in a structured way.
Spring Bean Concept
A Spring bean is an object managed by the Spring container. Instead of the test class creating the object manually, Spring creates it, configures it, injects its dependencies, and provides it wherever required. A page object, API service, configuration component, context object, or helper class can be a bean if the framework is designed that way.
LoginPage
-> Spring Bean
-> Injected Into LoginSteps
The bean concept is important because Spring does not merely inject objects. It manages them. It can apply scopes, configuration, profiles, lifecycle callbacks, and other framework behavior. This makes Spring powerful, but it also means the team must understand the container well enough to avoid confusing configuration.
Spring Context
The Spring context is the container responsible for creating objects, managing lifecycles, resolving dependencies, and providing beans to consuming classes. When Cucumber integrates with Spring, step definitions can use Spring-managed dependencies. The Spring context becomes the central object-management system for the test framework.
Spring Context
-> Create Bean
-> Inject Bean
-> Reuse Bean
The context can load configuration from annotations, Java configuration classes, property files, profiles, or Spring Boot settings. This is useful in enterprise test frameworks where environments, credentials, service clients, and test configuration need careful management. However, this power should be used intentionally. Overconfigured Spring test contexts can slow startup and make debugging harder.
Spring Object Flow
During scenario execution, Cucumber works with the Spring integration to access the Spring context. Required step definitions and support classes are created as beans or receive bean dependencies. If scenario scope is configured, Spring can provide fresh scenario-specific objects so tests remain independent.
Scenario Starts
-> Spring Context
-> Create Beans
-> Inject Beans
-> Execute Scenario
Spring's object flow is more configurable than PicoContainer's simple constructor-based approach. This can be helpful for large projects, but it also demands discipline. Teams should define scopes clearly and avoid sharing mutable scenario data as singleton beans.
PicoContainer vs Spring
PicoContainer and Spring both support dependency injection, but they are designed for different levels of complexity. PicoContainer is small, simple, and fast. Spring is large, feature-rich, and enterprise-oriented. PicoContainer focuses mainly on object creation and injection. Spring provides DI plus a broad ecosystem for configuration, profiles, data access, web applications, security, and more.
| PicoContainer | Spring |
|---|---|
| Lightweight | Full-featured framework |
| Simple configuration | More configuration options |
| Fast startup | Larger runtime footprint |
| Good for standalone Cucumber projects | Good for Spring or Spring Boot projects |
| Focused on dependency injection | DI plus many enterprise features |
| Easy learning curve | Broader learning curve |
The best choice depends on the project. If the test framework only needs simple object sharing, PicoContainer is often enough. If the test framework needs Spring beans, profiles, advanced configuration, or integration with a Spring Boot application, Spring is a natural choice.
Sharing Objects Between Step Definitions
Sharing objects between step definitions is one of the strongest reasons to use a DI framework in Cucumber. A scenario may start with login steps, then move to customer steps, then verify an order. These steps may live in different Java classes, but they still need to share scenario-specific data such as an access token, user role, customer ID, order ID, or API response.
Scenario Context
-> Token
-> Injected Into
-> CustomerSteps
Without DI, teams often use static variables to share this data. That approach is risky because static state can leak across scenarios and fail during parallel execution. With PicoContainer or Spring, a scenario context object can be injected into multiple step classes for the same scenario. Each scenario gets its own context instance, keeping data isolated.
Page Object Injection
Page objects are commonly injected into step definitions. A page object represents a page or component and usually needs WebDriver to interact with the browser. The DI framework can create the page object, inject WebDriver or driver-related dependencies, and then inject the page object into the step class.
LoginSteps
-> LoginPage
-> WebDriver
This keeps Selenium setup out of step definitions. The step definition can call meaningful methods such as loginPage.loginAs(user) or dashboardPage.openProfile(). The details of locators, waits, and driver interaction remain in the page object layer. DI simply supplies the objects needed for that collaboration.
Service Injection
Service injection is useful in frameworks that have business services or API services. A CustomerSteps class may depend on CustomerService. That service may depend on CustomerApi, JsonUtils, ScenarioContext, and configuration values. The DI container can wire this chain cleanly.
CustomerSteps
-> CustomerService
-> CustomerApi
This design keeps step definitions thin and avoids REST Assured code inside Gherkin mappings. Services coordinate workflows, API clients send requests, validators check responses, and utilities handle technical support. PicoContainer or Spring connects the objects without forcing each class to manually construct its dependencies.
Driver Injection
WebDriver injection centralizes browser management. Instead of calling new ChromeDriver() in step definitions or page objects, the framework can create the driver through a driver factory or driver manager. The resulting WebDriver instance can be injected where needed, usually with scenario scope or thread-safe handling.
DriverFactory
-> WebDriver
-> Injected Into Page Objects
This makes browser configuration easier to manage. Local Chrome execution, headless mode, remote Selenium Grid execution, browser options, timeouts, and cleanup can all be controlled in one place. Page objects do not need to understand the details of browser startup.
Driver lifecycle is critical. A scenario should normally get its own driver instance, and the framework should quit that driver after execution. If drivers are shared globally, parallel scenarios can interfere with each other. DI helps by making driver ownership and lifecycle clearer.
Scenario Context Injection
Scenario context is a shared object used to store values during one scenario. It may hold authentication tokens, IDs created during setup, response objects, generated test data, selected user roles, temporary file paths, or other scenario-level information. Both PicoContainer and Spring can inject the same scenario context object into multiple step classes.
Scenario Context
-> Customer ID
-> Order ID
-> Access Token
The important rule is scope. Scenario context should be fresh for every scenario. It should not be implemented as a global static map unless the team has a very specific reason and strong controls. Scenario-scoped context makes the framework safer, especially when tests run in parallel CI pipelines.
Enterprise Framework Flow
In an enterprise Cucumber framework, the DI framework usually sits between the Cucumber runner and the framework layers. Cucumber executes the feature file. The runner starts execution. PicoContainer or Spring creates step definitions and injects required objects. Step definitions call business services, page objects, API services, utilities, reports, and logs. The application is tested through UI and API layers.
Feature File
-> Runner
-> PicoContainer / Spring
-> Step Definitions
-> Business Services
-> Page Objects / API Services
-> Utilities
-> Application
This flow keeps object creation out of test logic. It also makes the framework easier to extend. When a new service, page object, or context class is needed, it can be added to the dependency graph. The consuming class receives it without duplicating construction logic.
Object Lifecycle in Cucumber DI
Object lifecycle defines how long a managed object exists. In Cucumber, scenario-level lifecycle is usually the safest default for mutable test objects. A scenario starts, objects are created, dependencies are injected, the scenario executes, and objects are discarded afterward. This prevents one scenario from accidentally affecting another.
Scenario Starts
-> Objects Created
-> Scenario Executes
-> Objects Destroyed
Some objects may safely live longer, such as immutable configuration or stateless utilities. Mutable scenario data, WebDriver, API response holders, and generated test data should usually be scenario-scoped. The framework should distinguish between reusable stateless services and scenario-specific state. This distinction is essential for reliable execution.
When to Use PicoContainer
PicoContainer is a strong choice when the automation framework is primarily a Cucumber test project and does not need the full Spring ecosystem. It is also useful when the team wants a lightweight dependency injection solution with minimal configuration. If the main need is sharing page objects, services, and scenario context across step definitions, PicoContainer is often a practical fit.
It is also a good learning tool for teams new to dependency injection. Constructor injection is easy to see and understand. The framework remains simple. The team can focus on clean step definitions and scenario-scoped state without managing a heavy application context.
When to Use Spring
Spring is a better fit when the application under test or the automation framework already uses Spring or Spring Boot. It is also useful when tests need existing beans, profiles, application configuration, dependency scanning, REST clients, or more advanced lifecycle features. In such projects, using Spring for Cucumber tests can keep automation aligned with the existing technology stack.
Spring should not be chosen only because it sounds more advanced. It brings power, but also configuration and startup overhead. For a small standalone Cucumber suite, Spring may be unnecessary. For an enterprise application where Spring is already central, it may be the most consistent option.
Common Mistake: Creating Objects with New
One common mistake is continuing to create shared framework objects manually after introducing DI. A step definition may use constructor injection for one dependency but still create page objects or services with new inside methods. This mixed style weakens the architecture because object creation is no longer centralized.
new LoginPage();
new CustomerService();
new ScenarioContext();
Some local objects can still be created directly, but shared dependencies should usually be managed by the DI framework. Page objects, services, scenario context, driver-related objects, and reporting helpers are better handled consistently. Consistency makes the framework easier to understand and maintain.
Common Mistake: Static Shared Objects
Static shared objects are another frequent problem. A framework may define public static WebDriver driver, public static String token, or public static ScenarioContext context because it seems easy to access those values from anywhere. This shortcut becomes dangerous when scenarios run in parallel or when one scenario leaves behind state that affects another scenario.
public static WebDriver driver;
public static String accessToken;
PicoContainer and Spring provide a cleaner alternative. Inject the objects where needed and scope mutable data to the scenario. This improves test independence and reduces random failures that are difficult to reproduce.
Common Mistake: Mixing Object Creation Approaches
A framework becomes confusing when some classes are created by PicoContainer or Spring, some are created manually, some are stored statically, and some are hidden inside utility classes. Developers then struggle to understand who owns each object and how long it lives. Bugs appear when the wrong instance is used.
The better approach is to define a clear rule. Shared framework components should be managed through DI. Scenario-specific state should be scenario-scoped. Stateless utilities can be static or injected depending on project style. Direct object creation should be reserved for simple values or objects with no framework lifecycle concern.
Common Mistake: Sharing State Between Scenarios
BDD scenarios should be independent. A scenario should not depend on data stored by a previous scenario. If the DI framework is misconfigured and scenario data is stored in a singleton or static object, values can leak between scenarios. This creates order-dependent tests. A test may pass when run alone but fail when run after another scenario.
To avoid this, keep scenario context and WebDriver scenario-scoped. Clean up resources after each scenario. Be careful with Spring bean scopes. Avoid placing mutable test data in singleton beans. Object lifecycle should match the purpose of the object.
Common Mistake: Choosing Spring Without Need
Spring is powerful, but power is not always necessary. Some teams choose Spring because it is familiar or considered enterprise-standard, even when the framework only needs basic constructor injection. This can add configuration, dependencies, startup time, and debugging complexity without providing real value.
PicoContainer may be better when the automation project is standalone and lightweight. Spring may be better when the test framework benefits from Spring profiles, beans, configuration, or application integration. The decision should be based on project needs, not naming prestige.
Best Practices
Use dependency injection for shared objects that appear across step definitions and framework layers. Prefer constructor injection because it makes required dependencies visible. Keep objects scenario-scoped when they hold scenario-specific state. Inject page objects, services, API clients, scenario context, and driver-related components. Avoid manual object creation inside step definitions when DI is available.
Keep step definitions free of object creation logic. A step definition should express test behavior and delegate work to pages, services, helpers, or validators. Use PicoContainer for lightweight Cucumber frameworks and Spring when the project already uses the Spring ecosystem or requires advanced features. Avoid static shared mutable objects and design for parallel execution from the beginning.
PicoContainer vs Spring Decision Guide
A simple decision guide helps teams choose correctly. Choose PicoContainer when the framework is mainly a Cucumber automation project, the team wants simple constructor injection, no Spring application context is needed, and fast setup is important. Choose Spring when the application under test is Spring-based, existing beans must be reused, Spring profiles are important, or the project needs richer configuration management.
The decision can also consider team experience. If the team already understands Spring deeply, Spring integration may be natural. If the team is mostly automation-focused and wants a small DI solution, PicoContainer may be easier. The right tool is the one that solves the current problem with the least unnecessary complexity.
Dependency Injection and Framework Maintainability
Dependency injection improves maintainability because object construction is no longer duplicated everywhere. When a page object constructor changes, the consuming step class does not need to know every construction detail. When a service needs a new dependency, the container can resolve it. When scenario context needs to be shared, it can be injected rather than accessed globally.
Maintainability also improves because dependencies are visible. A constructor tells reviewers what a class needs. If the constructor grows too large, the class may be doing too much. DI makes this design smell easier to see. In this way, dependency injection not only wires objects, but also exposes architecture quality.
Dependency Injection and Parallel Execution
Parallel execution requires strict object isolation. Each scenario should have its own browser session, scenario context, test data references, response holders, and report attachments. DI frameworks help by creating scenario-specific object graphs. This prevents one thread from accidentally using another scenario's data.
However, DI alone does not automatically make a framework thread-safe. The framework must still avoid static mutable state, shared file names, singleton WebDriver instances, and unsafe global caches. PicoContainer or Spring provides the object-management foundation, but the team must design scopes and shared resources carefully.
Dependency Injection and API Automation
In REST Assured automation, DI can inject API services, request builders, authentication helpers, configuration readers, response validators, and scenario context. This keeps API steps readable. A step definition can say that a customer is created through the API while the service handles endpoint paths, headers, payloads, and response parsing.
This design is especially useful for hybrid scenarios. A test may create data through an API, use the UI to perform an action, and validate the final state through another API. Dependency injection helps all layers share the correct context and configured services during the scenario.
Dependency Injection and UI Automation
In Selenium automation, DI can inject page objects, WebDriver, wait helpers, JavaScript helpers, and scenario context. This keeps UI step definitions clean and reduces repeated setup code. Page objects receive what they need and focus on page-level behavior.
DI also supports better page object design. A page object constructor can require WebDriver, making the dependency explicit. If the framework later changes driver management for Selenium Grid or headless execution, the driver creation layer can change without rewriting every step definition.
Dependency Injection and Reporting
Reports become more consistent when reporting helpers and scenario objects are injected cleanly. Hooks can receive screenshot utilities, driver managers, logging helpers, or scenario context. API services can attach sanitized request and response information. Validators can provide clear assertion details. DI helps connect these components without scattering construction logic.
This matters because automation reports are often read outside the automation team. Developers, testers, business analysts, and release managers may rely on report evidence. A clean DI-based framework can produce consistent evidence because the same helpers and scenario context are used across the suite.
Code Review Checklist
When reviewing a Cucumber framework that uses PicoContainer or Spring, check whether step definitions use constructor injection for required dependencies. Check whether page objects, services, and scenario context are injected consistently. Check whether mutable scenario data is scenario-scoped. Check whether WebDriver is managed centrally. Check whether static shared state has been avoided.
Also check whether the selected DI framework is appropriate. If the framework uses Spring, confirm that Spring provides real value and is not only adding configuration overhead. If the framework uses PicoContainer, confirm that the simplicity still meets project needs. The review should focus on clarity, lifecycle safety, and maintainability.
Enterprise Architecture
In enterprise architecture, PicoContainer or Spring provides the object-management layer between Cucumber execution and framework components. Feature files describe behavior. The runner starts execution. The DI framework creates and injects step definitions. Step definitions call page objects, API services, utilities, validators, reports, and logs. The DI framework supplies all required dependencies to keep the layers connected but loosely coupled.
Feature Files
-> Runner
-> PicoContainer / Spring
-> Step Definitions
-> Page Objects
-> API Services
-> Utilities
-> Reports
This architecture is scalable because each layer has a clear role. Cucumber does not need to know how every object is built. Step definitions do not need to create all collaborators. Page objects do not need to create drivers. API services do not need to create configuration readers manually. The DI container handles wiring so the framework can grow cleanly.
Interview-Ready Summary
PicoContainer and Spring are dependency injection frameworks used in Cucumber automation to manage object creation, dependency injection, and object lifecycle. They automatically provide shared objects such as page objects, services, WebDriver-related components, utilities, and scenario context to step definitions. This reduces duplicate object creation, improves loose coupling, and keeps step definitions cleaner.
PicoContainer is lightweight, easy to configure, fast, and well suited for standalone Cucumber Selenium or REST Assured automation projects. Spring is a full-featured enterprise framework and is a strong choice when the application or automation framework already uses Spring or Spring Boot. Both frameworks support scalable Cucumber architecture when used with proper scenario scoping, constructor injection, and disciplined object management.
Golden Rules
Use a dependency injection framework instead of manually creating shared framework objects in step definitions. Prefer constructor injection for page objects, services, scenario context, and other required dependencies. Use PicoContainer when a lightweight Cucumber-focused DI solution is enough. Use Spring when the project benefits from the Spring ecosystem, Spring Boot configuration, profiles, or existing beans.
Keep scenario-specific objects scoped to individual scenarios to prevent state leakage. Avoid static mutable state for WebDriver, tokens, IDs, and scenario context. Allow the DI framework to manage object creation so step definitions remain clean, modular, and maintainable. The practical takeaway is clear: PicoContainer and Spring help Cucumber frameworks move from manual object creation to controlled, scalable dependency management.