Enroll in Selenium Training

I hope you have gone pass through all the previous important tutorials of Selenium Cucumber Framework. In case not, I request you to follow past few chapters before this:

This chapter is all about Sharing Test Context between Cucumber Step Definitions.

Why Sharing Test Context or Scenario Context or Context?

Dividing Cucumber Steps between many classes may be a good idea. It is, however, probably not needed early in a project. The first reasonable division should therefore probably be no division. When you write your first scenario, you will most likely only have just a few steps. The first class with steps is probably small and you can easily find your way around in it.

But a scenario in Cucumber is a series of steps that get executed one after one. Each step in the scenario may have some state which can be required by another step in the scenario. In other way, you can also say that each step depends on previous steps. This means that we must be able to share state between steps.

Also when you outgrow your test steps or feature files, Keeping all the steps in a single Step Definition class quickly becomes impractical, so you use many classes. Now you have a new problem - objects you create in one step class will be needed in the other step classes as well.

In our case as well, till now we just have one scenario which has few steps. So we keeping all the steps in the same Steps file. But sooner or later we will grow the number of scenarios. And eventually, we will grow our steps files as well. SO we have to share the Test Context / Scenario Context / Test State with all the Step Definitions file. This is why Cucumber supports several Dependency Injection (DI) Containers - it simply tells a DI container to instantiate your step definition classes and wire them up correctly. One of the supported DI containers is PicoContainer.

What is PicoContainer?

It’s a tiny library written by Paul Hammant in 2003-2004. It is pretty awesome because it's so little and simple:

  • PicoContainer doesn’t require any configuration
  • PicoContainer doesn't require your classes to use any APIs such as the horrible @Inject - just use constructors
  • PicoContainer really only has a single feature - it instantiates objects

Simply hand it some classes and it will instantiate each one, correctly wired together via their constructors. That’s it. Cucumber scans your classes with step definitions in them, passes them to PicoContainer, then asks it to create new instances for every scenario.

How to Sharing Test Context between Cucumber Step Definitions using PicoContainer

We will be performing below steps to share data state across steps:

  1. Add PicoContainer to the Project
  2. Create a Test Context class which will hold all the objects state
  3. Divide the Steps class into multiple steps classes with logical separation
  4. Write Constructor to share Test Context

Step 1: Add PicoContainer Library to the Maven Project

This is really simple, as we have been using Maven Project, all we need to do is to add the dependencies in to the project POM file. Dependencies information can be taken from Maven Repository - Cucumber PicoContainer.

<dependency>
	    <groupId>info.cukes</groupId>
	    <artifactId>cucumber-picocontainer</artifactId>
	    <version>1.2.5</version>
	    <scope>test</scope>
	</dependency>

Note: Make sure to add these dependencies under <dependencies>Add here</dependencies> tag. Also, it also suggested to use the same version as a cucumber. In my case, it is 1.2.5.

Step 2: Create a Test Context class

Be wise to create this class logically. Just think what all information your Steps file are using and put that information in to this class. In our case our steps file is just using the below information:

  • PageObjects : Provided by PageObjectManager
  • WebDriver: Provided by WebDriverManager
  • Properties: Provided by FileReaderManager

So, the case is simple. We just need the above objects in our Test Context class. But if you re-look at the objects, you would release that our FileReaderManager is already a Singleton Class and to use it we don't need to create an instance of it. It creates its instance by itself. So no need to add FileReaderManager to TestContext class, as this class can be referred directly statically like FileReaderManager.getInstance().

  1. Create a New Package and name it as cucumber, by right click on the src/test/java and select New >> Package. We will keep all the Cucumber Helper classes in the same package moving forward.

  2. Create a New Class file and name it as TestContext by right click on the above-created package and select New >> Class.

TestContext.java

package cucumber;

import managers.PageObjectManager;
import managers.WebDriverManager;

public class TestContext {
	private WebDriverManager webDriverManager;
	private PageObjectManager pageObjectManager;
	
	public TestContext(){
		webDriverManager = new WebDriverManager();
		pageObjectManager = new PageObjectManager(webDriverManager.getDriver());
	}
	
	public WebDriverManager getWebDriverManager() {
		return webDriverManager;
	}
	
	public PageObjectManager getPageObjectManager() {
		return pageObjectManager;
	}

}

There is nothing to explain about the above class, as we just decided to keep only two objects in it. So we kept the initialization in the constructor and created getMethods() for both the objects.

Step 3: Divide the Steps file

I would just divide the steps file as I did the separations between the Page Objects. For every different page, we have a separate PageObject class. So it makes sense to have a separate step definition class for every page as well. May be your application is different and you won't get to agree with my approach. In that case who cares :) Divide your steps accordingly.

Create four New Classes in the stepDefinitions package with the following names:

  • HomePageSteps
  • ProductPageSteps
  • CartPageSteps
  • CheckoutPageSteps

I am sure you won't need my help to do this separation. Just start copying pasting information from steps class into the above-created classes accordingly. You would get the code in the below step.

Step 4: Write Constructor for Step Definition classes to share TestContext

Let's start with the HomePageSteps. The current state of the HomePageSteps class is without Constructor:

public class HomePageSteps {
	WebDriver driver;
	HomePage homePage;
	PageObjectManager pageObjectManager;	
	WebDriverManager webDriverManager;
	
	
	@Given("^user is on Home Page$")
	public void user_is_on_Home_Page(){
		webDriverManager = new WebDriverManager();
		driver = webDriverManager.getDriver();
		pageObjectManager = new PageObjectManager(driver);
		homePage = pageObjectManager.getHomePage();
		homePage.navigateTo_HomePage();	
	}
	
	@When("^he search for \"([^\"]*)\"$")
	public void he_search_for(String product)  {
		homePage.perform_Search(product);
	}

}

I hope now you would understand, what I was referring to. You need WebDriverManager & PageObjectManager in every step file, otherwise, you need to create objects for both classes using new operator again and again, which is Kill Bill.

Now with just adding Constructor to HomePageSteps file and pass TestContext as a Parameter to constructor would take all the pain. Within the TestContext object we have everything available which is required for the test. So now let's see how the new HomePageSteps class would look.

HomePageSteps.java

package stepDefinitions;

import cucumber.TestContext;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.When;
import pageObjects.HomePage;

public class HomePageSteps {
	
	TestContext testContext;
	HomePage homePage;
	
	public HomePageSteps(TestContext context) {
		testContext = context;
		homePage = testContext.getPageObjectManager().getHomePage();
	}
	
	@Given("^user is on Home Page$")
	public void user_is_on_Home_Page(){
		homePage.navigateTo_HomePage();	
	}

	@When("^he search for \"([^\"]*)\"$")
	public void he_search_for(String product)  {
		homePage.perform_Search(product);
	}

}

Isn't this looks simple and lovely. Did you relies how much code we just reduced from the class.

Using PicoContainer to share state between steps in a scenario is easy and non-intrusive. All you need is a constructor that requires an object that PicoContainer can create and inject. PicoContainer is invisible. Add a dependency to cucumber-picocontainer and make sure that the constructors for the step classes require an instance of the same class.

Now you can start copying the code for rest of the step classes.

ProductPage.java

package stepDefinitions;

import cucumber.TestContext;
import cucumber.api.java.en.When;
import pageObjects.ProductListingPage;

public class ProductPageSteps {
	
	TestContext testContext;
	ProductListingPage productListingPage;
	
	public ProductPageSteps(TestContext context) {
		testContext = context;
		productListingPage = testContext.getPageObjectManager().getProductListingPage();
	}

	@When("^choose to buy the first item$")
	public void choose_to_buy_the_first_item() {
		productListingPage.select_Product(0);
		productListingPage.clickOn_AddToCart();		
	}
}

CartPageSteps.java

package stepDefinitions;

import cucumber.TestContext;
import cucumber.api.java.en.When;
import pageObjects.CartPage;

public class CartPageSteps {
	
	TestContext testContext;
	CartPage cartPage;
	
	public CartPageSteps(TestContext context) {
		testContext = context;
		cartPage = testContext.getPageObjectManager().getCartPage();
	}
	
	@When("^moves to checkout from mini cart$")
	public void moves_to_checkout_from_mini_cart(){
		cartPage.clickOn_Cart();
		cartPage.clickOn_ContinueToCheckout();	
	}

}

CheckoutPageSteps.java

package stepDefinitions;

import cucumber.TestContext;
import cucumber.api.java.en.When;
import pageObjects.CheckoutPage;

public class CheckoutPageSteps {
	TestContext testContext;
	CheckoutPage checkoutPage;
	
	public CheckoutPageSteps(TestContext context) {
		testContext = context;
		checkoutPage = testContext.getPageObjectManager().getCheckoutPage();
	}
	
	@When("^enter personal details on checkout page$")
	public void enter_personal_details_on_checkout_page(){
		checkoutPage.fill_PersonalDetails();	
	}
	
	@When("^select same delivery address$")
	public void select_same_delivery_address(){
		checkoutPage.check_ShipToDifferentAddress(false);
	}
	
	@When("^select payment method as \"([^\"]*)\" payment$")
	public void select_payment_method_as_payment(String arg1){
		checkoutPage.select_PaymentMethod("CheckPayment");
	}

	@When("^place the order$")
	public void place_the_order() {
		checkoutPage.check_TermsAndCondition(true);
		checkoutPage.clickOn_PlaceOrder();		
		testContext.getWebDriverManager().quitDriver();
	}	

}

Run the Cucumber Test

Run as JUnit

Now we are all set to run the Cucumber test. Right, Click on TestRunner class and Click Run As  >> JUnit TestCucumber will run the script the same way it runs in Selenium WebDriver and the result will be shown in the left-hand side project explorer window in JUnit tab.

Project Explorer

Sharing Test Context between Cucumber Step Definitions

Note: Delete the Steps class from stepDefinitions package, as it is no longer required.

Design WebDriver Manager
Design WebDriver Manager
Previous Article
How to use Hooks in Selenium Cucumber Framework
How to use Hooks in Selenium Cucumber Framework
Next Article
Lakshay Sharma
I’M LAKSHAY SHARMA AND I’M A FULL-STACK TEST AUTOMATION ENGINEER. Have passed 16 years playing with automation in mammoth projects like O2 (UK), Sprint (US), TD Bank (CA), Canadian Tire (CA), NHS (UK) & ASOS(UK). Currently, I am working with RABO Bank as a Chapter Lead QA. I am passionate about designing Automation Frameworks that follow OOPS concepts and Design patterns.
Reviewers
Virender Singh's Photo
Virender Singh

Similar Articles

Feedback