Search code examples
javadependency-injectioncucumberautomated-testsguice

@FindBy WebElements not initialized when using Guice @Inject


I'm currently having some issues with implementing object injections properly. I am writing some Cucumber tests with Page Objects Model. I am getting NullPointerExceptions in the following code:

public class BasePage extends LoadableComponent<BasePage>{
    @Inject
    protected WebDriver driver  //trying to inject a WebDriver instance here

    public BasePage(){
        PageFactory.initElements(this.driver, this);    
    }
    @Override
    protected void isLoaded() throws Error {//something here}
    @Override
    protected void load() {//something as well}

    // some more shared methods   
}

Above is my BasePage class, LoginPage extends BasePage as below

public class LoginPage extends BasePage {
    @FindBy(id="login_username")
    private WebElement userNameField;
    @FindBy(id="login_password")
    private WebElement passWordField;
    @FindBy(id="login_database")
    private WebElement dataBase;

    public void enterValidLoginCredentials() throws FileNotFoundException {
        userNameField.sendKeys(EdgeTestProperties.getProperty("userName"));
        passWordField.sendKeys(EdgeTestProperties.getProperty("password"));
        dataBase.sendKeys(EdgeTestProperties.getProperty("db"));
    }        

    //some more tests here        

}

My step definitions login tests:

@ScenarioScoped
public class LoginSteps {

    private LoginPage login;

    @Inject
    LoginSteps(LoginPage login) {
    this.login = login;
    }

    @Given("^I'm on the login page$")
        public void getLoginPage() throws FileNotFoundException {
        login.get();    
    }

    @When("^I enter valid user credential")
    public void enterValidUserCredential() throws FileNotFoundException {
        login.enterValidLoginCredentials();
    }

In my CucumberModule I used:

public class CucumberModule extends AbstractModule {
    @Override
    protected void configure() {
        requestStaticInjection(BasePage.class);     
    }

    @Provides
    @ScenarioScoped
    WebDriver getWebDriver(){
        WebDriver driver = null;

        try {
            driver =  Browser.loadDriver(EdgeTestProperties.getProperty("browser"));
        } catch (FileNotFoundException e) {
            System.err.print("Failed to load the browser preference, check test properties file....");
            e.printStackTrace();
        }
        driver.manage().timeouts().implicitlyWait(2L, TimeUnit.SECONDS);    

        return driver;
    }
}

When I start running the login test, the @Injection of the driver on BasePage only happens after the base constructor is called (initialization of the WebElements). As a result, none of the WebElements are initialized, so I get NullPointerException. I could get it to work if I remove the super class constructor and change the driver injection point to the LoginPage constructor and pass it back to the BasePage by a method which sets the driver and the initialise WebElements, however that seems wrong. Is there a better way?


Solution

  • You should use Constructor injection.

    This will solve your problem, as the driver will now be available in the constructor.

    protected final Webdriver driver;
    
    @Inject
    public BasePage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(this.driver, this);
    }
    

    You should be aware that, generally speaking, field injection is not preferred. You have discovered one reason; another reason is that it's hard to test. Constructor injections are more verbose, but it also makes it much more clear what's going on, both when reading the code and when trying to write tests.