Selenium WebDriver – How To Inject Page Object Dependencies Using Guice

Overview:

In this article, we will see the use of Dependency Injection using Guice lib and how it helps us to come up with more readable, reusable page objects and tests.

Dependency Injection:

Lets this consider this simple Page Object for Google page.

public class Google {

    private final WebDriver driver = new ChromeDriver();

    public void goTo() {
        this.driver.get("https://wwww.google.com");
    }

}

Assuming chrome driver server system properties have been set already, there is nothing wrong here. The code will just work fine.

Google google = new Google();
google.goTo();

But what will happen if we need to make the test work for a different browser? We have to modify the class, right? Because it is tightly coupled. How many classes will we modify like this?

Lets see if we could modify slightly as shown below.

public class Google {

    private final WebDriver driver;

    public Google(WebDriver driver) {
        this.driver = driver;
    }

    public void goTo() {
        this.driver.get("https://wwww.google.com");
    }

}

In this above class, we do not want to create the new instance of the WebDriver. But it is a dependency required for this class to function as expected. So, We have designed the class in such a way that this dependency will be injected by the test / whoever creates the instance of this class. This approach makes the classes loosely coupled, but the dependencies have to be injected by the invoking classes. If it is a page object, the testng/junit tests should take care of creating the WebDriver instance and pass it to the constructor of this class.

Google google = new Google(driver); //constructor injection
google.goTo();

We could also inject the dependency by using setters method.

Google google = new Google();
google.setDriver(driver); //setter injection
google.goTo();

The Google page object has the hard coded URL. But in reality, we need to run our automated tests in Dev / QA / Staging / PROD environments. So we can not hard code the URL in our page objects. So even the URL should be injected.

public class Google {

    private final WebDriver driver;
    private final String URL;

    public Google(WebDriver driver, String URL) {
        this.driver = driver;
        this.URL = URL;
    }

    public void goTo() {
        this.driver.get(URL);
    }
}

Hm… It looks better now, rt?  Yeah, the page object might look better. What about the junit/testng test class which is going to use it? The test class has to take care of all the dependencies required for this page object. With all the driver instance creation logic, fetching the url from some property files etc, the test class will look ugly for sure!!!

As part of our functional test automation, we might have created tons of page objects with a lot of other dependencies.  Creating & maintaining the life cycle of the driver, page objects, utility classes like property file readers, reporting utility, logger etc make the framework very difficult to maintain.

Creating instances are painful sometimes. For ex: WebDriver.  If you are not using a Factory Pattern as mentioned in this article and you are directly creating a WebDriver instance in the Test class/Page object itself, It will make your class bloated, less readable and difficult to maintain.

How can we have an automated dependency injection in the framework? How can we write less number of code & achieve whatever we wanted to have?

There is a library from Google which could help us here.

Guice (pronounced as ‘juice’)

Guice is a Dependency Injection framework and most of the Java developers use it in their applications. Lets see how we can take advantage of Guice in Functional Test automation frameworks.

This article might not be able to cover all the features of Guice. I would suggest you to spend some time here in reading more about Guice.

Guice – Binding:

Guice can directly inject the instance of a class with default constructor when you use @Inject annotation.

For ex:

Google google = new Google(driver, url);

can be replaced by simply using

@Inject
Google google;

I use a Factory Pattern for a WebDriver instance creation. I need to map the ChromeDriverManager.class to the DriverManager.class. When you have constructor with arguments or try to map a concrete class to an interface/abstract class, then we need to do few configurations as shown here.

public class DriverModule extends AbstractModule {

    @Override
    protected void configure() {

        bind(DriverManager.class)
            .to(ChromeDriverManager.class)
            .in(Scopes.SINGLETON);

    }

}

Now Guice can inject an instance of ChromeDriverManager.class into the driverManager variable.

@Inject
DriverManager driverManager;

If I had to code to get a required instance, I could use @Provides method. For ex, I might not be really interested in DriverManager.class. But I need that to get the WebDriver instance.

public class DriverModule extends AbstractModule {

    @Override
    protected void configure() {

        bind(DriverManager.class)
            .to(ChromeDriverManager.class)
            .in(Scopes.SINGLETON);

    }
    @Provides
    public WebDriver getDriver(DriverManager driverManager) {
        return driverManager.getDriver();
    }

}

Now Guice can inject an instance of ChromeDriver directly into the driver variable by hiding a lot of code for the creation logic.

@Inject
WebDriver driver;

If you need different browser instances in the same test for some reason, Guice can inject that too!!

@Inject @Chrome
WebDriver chrome;

@Inject @Firefox
WebDriver firefox;

Here @Chrome and @Firefox are custom annotations for Guice to understand what to inject. More information is here.

Not only the WebDriver instance. We might be interested in Actions, JavascriptExecutor,Logger etc. So Guice can inject these instances too from the WebDriver instance we create. It can even the inject the value of a property from your property files.

Guice – Usage In Test Framework:

Lets assume that We keep all our application url, user credentials, any other input parameters in a property file as shown here.

guice001

We could also maintain 1 property file for each environment where we run our automated tests.

ufts-04

As I use Google search functionality for example, I design page objects as shown here by using Single Responsibility principle.

guice002

GoogleSearchWidget class will look like this. WebDriver instance will be injected by Guice. So, I add an @Inject in the constructor.

public class GoogleSearchWidget {

    @FindBy(name = "q")
    WebElement searchBox;

    @FindBy(name = "btnG")
    WebElement searchButton;

    @Inject
    public GoogleSearchWidget(WebDriver driver) {
        PageFactory.initElements(driver, this);
    }

    public void searchFor(String txt) {
        searchBox.sendKeys(txt);
        searchButton.click();
    }

}

GoogleSearchResult class will look like this. I again add an @Inject in the constructor.

public class GoogleSearchResult {

    @FindBy(className = "rc")
    private List<WebElement> results;

    @Inject
    public GoogleSearchResult(WebDriver driver) {
        PageFactory.initElements(driver, this);
    }

    public int getCount() {
        return results.size();
    }

    public void displayResult() {
        results.stream()
            .map(WebElement::getText)
            .forEach(System.out::println);
    }
}

Google class should have the instance of these above 2 classes and few other instances like JavascriptExecutor, Actions etc. To get the property value, We need to use @Named(property-key). So the private String URL in the below class will have the value of the ‘application.url’ which is ‘https://google.com’.

public class Google {

    private final WebDriver driver;

    @Inject
    @Named("application.url")
    private String URL;

    @Inject
    private GoogleSearchWidget searchBox;

    @Inject
    private GoogleSearchResult searchResult;

    @Inject
    private Actions actions;

    @Inject
    private JavascriptExecutor jsExecutor;

    @Inject
    public Google(WebDriver driver) {
        this.driver = driver;
    }

    public void goTo() {
        this.driver.get(this.URL);
    }

    public GoogleSearchWidget getSearchWidget() {
        return searchBox;
    }

    public GoogleSearchResult getResults() {
        return searchResult;
    }

    public Object execute(String script) {
        return jsExecutor.executeScript(script);
    }

}

Now It is time for us to modify the config module class as shown below. GoogleSearchResults / GoogleSearchWidget does not need any config. But the JavascriptExecutor/Actions are created from WebDriver instance. So, we need to help guice how it has to create the instance.

public class DriverModule extends AbstractModule {

    @Override
    protected void configure() {

        //DriverManager config
        bind(DriverManager.class)
            .to(ChromeDriverManager.class)
            .in(Scopes.SINGLETON);

        //My test input properties
        try {
            Properties props = new Properties();
            props.load(new FileInputStream("uat.properties"));
            Names.bindProperties(binder(), props);
        } catch (IOException e) {
            //skip
        }

    }

    @Provides
    public WebDriver getDriver(DriverManager driverManager) {
        return driverManager.getDriver();
    }

    @Provides
    public Actions getActions(WebDriver driver) {
        return new Actions(driver);
    }

    @Provides
    public JavascriptExecutor getExecutor(WebDriver driver) {
        return (JavascriptExecutor)(driver);
    }
}

That is it. We are good to use these page objects and properties in our tests. I use Testng framework which has support for including Guice module.

@Guice(modules = {
    DriverModule.class
})
public class GuiceTest {

    @Inject
    Google google;


    @Test(dataProvider = "google-test")
    public void f(String txt, String color) throws InterruptedException {

        google.goTo();
        
        //change the color of the google page
        google.execute("document.body.style.backgroundColor='" + color + "';");

       //do search and show results
        google.getSearchWidget().searchFor(txt);
        google.getResults().displayResult();

    }

    @DataProvider(name = "google-test")
    public static Object[][] getData() {
        return new Object[][] {
            {
                "guru",
                "blue"
            }, {
                "guice",
                "green"
            }
        };
    }

}

Now my test class looks neat and clean. If I need any object in my test/page objects, I could just use @Inject.

Summary:

By injecting all the dependencies automatically, Guice can increase our productivity. It makes our code readable, reusable. Due to these benefits, The code becomes easy to maintain as well.

For advanced usage of Guice with WebDriver, check this article.

Happy Testing and Subscribe 🙂

 

Share This:

21 thoughts on “Selenium WebDriver – How To Inject Page Object Dependencies Using Guice

  1. This class GoogleSearchResult has to be corrected.
    Instead of:
    private List results;
    It should be
    private List results;

    Otherwise pretty good stream will not work 🙂

    1. Thanks. Corrected. It was already there – but it was not escaped properly – so it started looking as you have it in your comment!

  2. Nice Article !

    I have one question about the Different Browser as in your DriverModule class you have ChromeDriverManager.class in mapped in the configure method. So What is the better way for passing the Browsers?

      1. Thanks for the Info but my questions is more about the Passing of the Browser from the command line or textNg.xml file with the Guice implementation. As the Driver is initialised by Guice.

        Could you please share some idea like how we can do that from TestNG.xml and Guice. Right now from the article you shared we are hardcoding the Browser and its different from Inject.

  3. where does ‘bind’ method comes from?

    public class DriverModule extends AbstractModule {

    @Override
    protected void configure() {

    bind(DriverManager.class)
    .to(ChromeDriverManager.class)
    .in(Scopes.SINGLETON);
    }

    }

  4. Great article, thanks for sharing! I’ve got one question, though. The WebElements in the GoogleSearchResult class are initialized by PageFactory.initElements in the Constructor. Wouldn’t they be initialized before the actual results are shown while being injected into the Google class, thus causing an exception?

    1. WebElement in the GoogleSearchResult are proxy objects of selenium. They should not cause any issues. Guice @Inject is basically creating a new GoogleSearchResult().

  5. Thanks for the article. I was wondering how Guice is providing the same webdriver instance to all the pages. In the getDriver method, it clearly says new ChromeDriver. And what I understand is, Guice default behaviour is to always inject a new instance. I know the code works fine, but just understand how the same instance of webdriver is always injected.

  6. @Inject
    @Named(“application.url”)
    private String URL;

    How did this magically work for you? What is the prerequisite to use this annotation? Where should be this property file, its name and how did Guice found it? Cared to cover that too?
    Error: 1) No implementation for java.lang.String annotated with @com.google.inject.name.Named(“application.url”) was bound.

  7. Hello, very interesting article. I have a question: according to good practises, every test should start has own webDriver (https://www.selenium.dev/documentation/test_practices/_print/#pg-a725d846f871bf74490745badf015de3).
    I’ve tried to achieve that, but I know how to do it. By using scope Singleton every test has the same webDriver.
    Is that possibility to create new driver for each test? I mean situation when there are multiple tests in one class.
    Help please! 🙂

    1. People often think best practices are rules!! but actually not. It might change depends on the team or requirements. However, I will agree that each test creating own webdriver gives you flexibility like parallel execution. The idea of this post was to demonstrate Guice dependency injection. But you can always use that idea and modify as you like. @Provides method can create a new instance of the WebDriver and inject it.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.