2

What is the best approach to instantiating up objects on creating steps in a Java Page Object Model?

Does anyone know how Cucumber scripts are compiled?

I would imagine if everything is built and complied the 2nd or 3rd option below may be the best approach.

If only the steps related to the test under execution are compiled then I would imagine it would be the first.

I Have the following example:

private LoginPage loginPage = new LoginPage(driver);

@Given("^I have logged in as customer with two stored cards$")
public void iHaveLoggedInAsCustomerWithTwoStoredCards() throws Throwable {
    new HomePage(driver).clickLoginButton();
    loginPage.enterEmail("test@test.com");
    loginPage.enterPassword("Password1");
    loginPage.clickLogin();        
}

@Given("^I have logged in as customer with expired card$")
public void iHaveLoggedInAsCustomerWithExpiredCard() throws Throwable {
    new HomePage(driver).clickLoginButton();
    loginPage.enterEmail("test02@test.com");
    loginPage.enterPassword("Password1");
    loginPage.clickLogin();        
}

@Given("^the user logged in as customer with three stored cards$")
public void theUserLoggedInAsCustomerWithThreeStoredCards() throws Throwable {
    new HomePage(driver).clickLoginButton();
    loginPage.enterEmail("test03@test.com");
    loginPage.enterPassword("Password1");
    loginPage.clickLogin();        
}

All of the above steps (plus more in the same LoginSteps.java class) start with

new HomePage(driver).clickLoginButton();

Is this the best approach, or is it better to create a single instance?

private LoginPage loginPage = new LoginPage(driver);
private HomePage homePage = new HomePage(driver);

@Given("^I have logged in as customer with two stored cards$")
public void iHaveLoggedInAsCustomerWithTwoStoredCards() throws Throwable {
    homePage.clickLoginButton();
    loginPage.enterEmail("test@test.com");
    loginPage.enterPassword("Password1");
    loginPage.clickLogin();        
}

@Given("^I have logged in as customer with expired card$")
public void iHaveLoggedInAsCustomerWithExpiredCard() throws Throwable {
    homePage.clickLoginButton();
    loginPage.enterEmail("test02@test.com");
    loginPage.enterPassword("Password1");
    loginPage.clickLogin();        
}

@Given("^the user logged in as customer with three stored cards$")
public void theUserLoggedInAsCustomerWithThreeStoredCards() throws Throwable {
    homePage.clickLoginButton();
    loginPage.enterEmail("test03@test.com");
    loginPage.enterPassword("Password1");
    loginPage.clickLogin();        
}

OR is it even more efficient to have the instantiation all pages in a 'baseSteps' class which LoginSteps extends?

public class LoginSteps extends BaseSteps {
    @Given("^I have logged in as customer with two stored cards$")
    public void iHaveLoggedInAsCustomerWithTwoStoredCards() throws Throwable {
        homePage.clickLoginButton();
        loginPage.enterEmail("test@test.com");
        loginPage.enterPassword("Password1");
        loginPage.clickLogin();        
    }

    @Given("^I have logged in as customer with expired card$")
    public void iHaveLoggedInAsCustomerWithExpiredCard() throws Throwable {
        homePage.clickLoginButton();
        loginPage.enterEmail("test02@test.com");
        loginPage.enterPassword("Password1");
        loginPage.clickLogin();        
    }

    @Given("^the user logged in as customer with three stored cards$")
    public void theUserLoggedInAsCustomerWithThreeStoredCards() throws Throwable {
        homePage.clickLoginButton();
        loginPage.enterEmail("test03@test.com");
        loginPage.enterPassword("Password1");
        loginPage.clickLogin();        
    }
}

public baseSteps {

    protected LoginPage loginPage = new LoginPage(driver);
    protected HomePage homePage = new HomePage(driver);
}

2 Answers2

1

I would benchmark to see which version is most efficient.

How the Cucumber code is compiled depends on your build tool. I would assume that everything is compiled before it is being used. But a JVM that optimizes itself after some execution may behave different after a while.

I am really interested in hearing why you are thinking about performance at this level tho? Are you expecting to execute this code for hours at a time? Most of my scenarios are short, executed on the second scale and then thrown away.

Thomas Sundberg
  • 4,098
  • 3
  • 18
  • 25
  • I will give that a go and see if I can find any difference. The reasoning for this is because I am going to be running these on a continual basis and I need them to be as efficient as possible. Thanks for your help. – Andrew Evans Jan 11 '17 at 13:57
1

Are your Cucumber tests running against the GUI? If so, it won't make a difference whether you create a single instance or multiple instances of a given Page Object. This kind of tests are slow by nature, and such a tweak will be imperceptible.

Whatever the case may be, the priority should always be a good design. As well as simplifying the future maintenance of the code, a good design will allow you to make improvements to the system performance. However, this is not true the other way around.

Cucumber and the Page Object Model fit together really well. But they should be arranged in separate layers, with Cucumber on top of the Page Object Model, acting as a client of the latter. Watching your code examples I don't understand why the Page Objects are created as part of the step definitions code. Why doesn't the clickLoginButton method itself return an instance of LoginPage?

In my opinion the Page Object Model should be supported by two basic principles:
1. API. Every Page Object should define an API with the methods corresponding to the actions a user can perform on that page.
2. Navigation. Every action should take the user to either the same page or a different page. So every method should return either the Page Object itself or a different Page Object.

The first one is fulfilled by your code, but not the second one, or at least it's not clear enough.

I would think of HomePage and LoginPage classes as something like this (without going into too much detail):

public class HomePage
{
    public HomePage(WebDriver driver) {
        // ...
    }

    public LoginPage clickLoginButton() {
        perform click login button
        return new LoginPage(this.driver);
    }
}

public class LoginPage
{
    public LoginPage(WebDriver driver) {
        // ...
    }

    public LoginPage enterEmail(String email) {
        perform enter email
        return this;
    }

    public LoginPage enterPassword(String password) {
        perform enter password
        return this;
    }

    public NextPage clickLogin() {
        perform click login
        return new NextPage();
    }
}

As for the Cucumber layer, I would think of it as follows (again, without going into too much detail):

private static final String PASSWORD = "Password1";
private static final String EMAIL_CUSTOMER_WITH_TWO_STORED_CARDS = "test@test.com";
private static final String EMAIL_CUSTOMER_WITH_EXPIRED_CARD = "test02@test.com";
private static final String EMAIL_CUSTOMER_WITH_THREE_STORED_CARDS = "test03@test.com";

@Given("^I have logged in as customer with two stored cards$")
public void iHaveLoggedInAsCustomerWithTwoStoredCards() throws Throwable {
    this.performLogin(EMAIL_CUSTOMER_WITH_TWO_STORED_CARDS);
}

@Given("^I have logged in as customer with expired card$")
public void iHaveLoggedInAsCustomerWithExpiredCard() throws Throwable {
    this.performLogin(EMAIL_CUSTOMER_WITH_EXPIRED_CARD);
}

@Given("^the user logged in as customer with three stored cards$")
public void theUserLoggedInAsCustomerWithThreeStoredCards() throws Throwable {
    this.performLogin(EMAIL_CUSTOMER_WITH_THREE_STORED_CARDS);
}

private void performLogin(String email) {
    nextPage = homePage.clickLoginButton()
        .enterEmail(email)
        .enterPassword(PASSWORD)
        .clickLogin();
}

See the (optional) method chaining in the last method, as a consequence of the Page Objects returned by the methods of the Page Object Model.

I have deliberately omitted the declaration of the variables homePage, loginPage and nextPage. LoginSteps does not share variables with the rest of Steps classes, even if you make them extend from a common BaseSteps class, unless you declared them as static:

public BaseSteps {
    protected static HomePage homePage;
    protected static LoginPage loginPage;
    protected static NextPage nextPage;
    ...
}

Alternatively you could consider integrating a dependency injection framework, but I haven't tried myself.

Keeping the variables as private instance attributes won't work (even though they are not shared with other Steps classes) because Cucumber instantiates the Steps class for each test scenario making use of a step definition contained in the class.

Finally, there should be a hook somewhere in the Cucumber layer to initialize the entry point to the Page Object Model.

@Before
public void setUp() {
    homePage = new HomePage(driver);
}

That should be the only Page Object explicitly created from the Cucumber layer.

Lazarus
  • 11
  • 2