Answer
Dependency Injection with PicoContainer in Cucumber
Why DI Is Needed
When you split step definitions across multiple classes (LoginSteps, CheckoutSteps, etc.), each class needs access to the same WebDriver instance and shared state. Without DI, you would use static variables — which are not thread-safe and cause issues in parallel execution.
PicoContainer solves this by creating one instance per scenario of each class and injecting it automatically.
Step 1: Add Dependency
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>7.15.0</version>
<scope>test</scope>
</dependency>
Step 2: Create the Shared Context
package context;
import org.openqa.selenium.WebDriver;
public class ScenarioContext {
// Shared driver
public WebDriver driver;
// Shared test data between step classes
public String authToken;
public String orderId;
public String userId;
// Store data dynamically
private Map<String, Object> context = new HashMap<>();
public void set(String key, Object value) {
context.put(key, value);
}
public Object get(String key) {
return context.get(key);
}
}
Step 3: Inject into Step Definitions
public class LoginSteps {
private final ScenarioContext ctx;
// PicoContainer sees this constructor and auto-injects ScenarioContext
public LoginSteps(ScenarioContext ctx) {
this.ctx = ctx;
}
@Given("the user logs in as {string}")
public void login(String username) {
ctx.driver.findElement(By.id("user")).sendKeys(username);
ctx.driver.findElement(By.id("login")).click();
ctx.authToken = getTokenFromCookie(); // store for other steps
}
}
public class CheckoutSteps {
private final ScenarioContext ctx;
public CheckoutSteps(ScenarioContext ctx) {
this.ctx = ctx;
}
@When("the user places an order")
public void placeOrder() {
// Uses same driver instance
// Uses authToken set by LoginSteps
apiClient.createOrder(ctx.authToken, productId);
}
}
public class Hooks {
private final ScenarioContext ctx;
public Hooks(ScenarioContext ctx) {
this.ctx = ctx;
}
@Before
public void setUp() {
ctx.driver = new ChromeDriver(); // one driver per scenario
}
@After
public void tearDown() {
ctx.driver.quit();
// ctx is discarded — fresh context for next scenario
}
}
How PicoContainer Works Internally
1. Scenario starts
2. PicoContainer creates ONE instance of ScenarioContext
3. Finds all step definition classes referenced in the scenario
4. Creates ONE instance of each step class, injecting the SAME ScenarioContext
5. All step classes share the same driver, same state
6. Scenario ends → PicoContainer discards all objects
7. Next scenario → fresh objects created
Without DI vs With DI
// WITHOUT DI (anti-pattern)
public class LoginSteps {
static WebDriver driver; // ← static = not thread-safe
}
// WITH DI (correct)
public class LoginSteps {
private final ScenarioContext ctx; // ← injected = thread-safe
public LoginSteps(ScenarioContext ctx) { this.ctx = ctx; }
}
Other DI Frameworks Supported
| Framework | Dependency |
|---|---|
| PicoContainer | cucumber-picocontainer (simplest) |
| Spring | cucumber-spring |
| Guice | cucumber-guice |
| CDI | cucumber-cdi2 |
PicoContainer requires zero configuration — just add the dependency and use constructors.
