slovvic
slovvic

Reputation: 467

Page Object Pattern understanding

I have problem with understanding this pattern. First I want to test the login in my page for which I have a LoginPage that extends my PageObject after successful authentication , it returns LoginPageReceipt. Now that I have loginPageReceipt I want to retain this for my second page. The second issue that I m thinking is that if first I test login then i want to test next module but I have to be logged in. How should I do this? My second test should not use result of first test and I should not duplicate my code. Here are my classes. How I did it.

 package Init;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.util.concurrent.TimeUnit;

public class FunctionalTest {
    protected static WebDriver driver;
//    private static WebDriverWait driverWait;

    @BeforeClass
    public static void setUp() {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--start-maximized");
        System.setProperty("webdriver.chrome.driver", "src\\main\\resources\\chromedriver.exe");
        driver = new ChromeDriver(options);
        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
//        driverWait = new WebDriverWait(driver, 10);
    }

    @After
    public void cleanUp() {
        driver.manage().deleteAllCookies();
    }

    @AfterClass
    public static void tearDown() {
        driver.close();
    }
}

package Init;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.PageFactory;

public class PageObject {

    protected WebDriver driver;

    public PageObject(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

}
package Login;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import Init.PageObject;

import static org.junit.Assert.assertTrue;

public class LoginPage extends PageObject {

    @FindBy(id = "UserName")
    private WebElement userName;

    @FindBy(id = "Password")
    private WebElement password;

    @FindBy(id = "loginButton")
    private WebElement loginButton;

    public LoginPage(WebDriver driver) {
        super(driver);
        assertTrue(userName.isDisplayed());
        assertTrue(password.isDisplayed());
        assertTrue(loginButton.isDisplayed());
    }

    public void enterUserName(String userName) {
        this.userName.clear();
        this.userName.sendKeys(userName);
    }

    public void enterUserPassword(String password) {
        this.password.clear();
        this.password.sendKeys(password);
    }

    public LoginPageReceipt login() {
        loginButton.click();
        return new LoginPageReceipt(driver);
    }
}

package Contractor;

import Init.PageObject;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

import static org.junit.Assert.assertTrue;

public class ContractorPage extends PageObject {

    @FindBy(id = "moduleContent")
    private WebElement moduleContent;

    public ContractorPage(WebDriver driver) {
        super(driver);
        assertTrue(moduleContent.isDisplayed());
    }
}

package Login;

import Contractor.ContractorPage;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import Init.PageObject;

public class LoginPageReceipt extends PageObject {

    @FindBy(xpath = "//*[@id=\"loginPartial\"]/span[5]")
    private WebElement userNamePanel;

    @FindBy(id = "contractors-menuitem")
    private WebElement goToContractorPage;

    public LoginPageReceipt(WebDriver driver) {
        super(driver);
    }

    public String loginConfirmation() {
        return  userNamePanel.getText();
    }

    public ContractorPage contractorPage() {
        goToContractorPage.click();
        return new ContractorPage(driver);
    }
}

package Tests;

import Login.LoginPage;
import Login.LoginPageReceipt;
import org.junit.Test;
import Init.FunctionalTest;

import static org.junit.Assert.assertEquals;

public class LoginTest extends FunctionalTest {

    private static final String USER_NAME = "xxx";
    private static final String PASSWORD = "xxx";

    @Test
    public void login() {
        FunctionalTest.driver.get("xxx");

        LoginPage loginPage = new LoginPage(FunctionalTest.driver);
        loginPage.enterUserName(USER_NAME);
        loginPage.enterUserPassword(PASSWORD);
        LoginPageReceipt loginPageReceipt = loginPage.login();

        assertEquals("Użytkownik: " + USER_NAME + " | Wyloguj", loginPageReceipt.loginConfirmation());
    }

}

package Tests;

import Contractor.ContractorPage;
import Init.FunctionalTest;
import Login.LoginPage;
import Login.LoginPageReceipt;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class ContractorTest extends FunctionalTest {

    private static final String USER_NAME = "xxx";
    private static final String PASSWORD = "xxx";

    @Test
    public void contractorPageTest() {
        FunctionalTest.driver.get("xxx");

        LoginPage loginPage = new LoginPage(FunctionalTest.driver);
        loginPage.enterUserName(USER_NAME);
        loginPage.enterUserPassword(PASSWORD);
        LoginPageReceipt loginPageReceipt = loginPage.login();

        assertEquals("Użytkownik: " + USER_NAME + " | Wyloguj", loginPageReceipt.loginConfirmation());

        ContractorPage contractorPage = loginPageReceipt.contractorPage();
    }
}

Upvotes: 3

Views: 1117

Answers (2)

Julian
Julian

Reputation: 1685

One of the great things about the page object model is that it's a general guideline, not a rigid system. Everyone has preferences for how they model their selenium projects.

To answer your direct questions, it's completely valid to have a page object's method which triggers a new page to load in the browser, to return a new page object representing that page.

In terms of each individual test, unless you're trying to preserve stateful information in the web app across one test to another (Which to me is generally a bad idea), then yes you will need every test to repeat this login procedure. But it doesn't have to be duplicate code, encapsulate that routine in a method that each test can call as a first-order-of-business. Additionally, performing the procedure isn't the same as testing the procedure, your test of the login page should assert that things are correct, additional tests that require logging in as an intermediary step should skip these assertions.

Page objects additionally don't need to each encapsulate an entire page. You want to be thoughtful about your design in relation to the app you're testing. Not all apps are automated equally, so selenium projects should not expect to be universal either.

If your app is a collection of static pages that don't statefully change much, you may want one page object per web page. But what if you have a single page app that is nothing but javascript making large regions of the page appear and disappear very frequently... Perhaps your page objects would better encapsulate regions of that single page, representing components or frames that come and go but are within themselves consistent.

From a conceptual point of view, you want your page objects to hide all the raw selenium so your tests don't need to know or care and provide a concise public API that your tests can invoke to perform actions on that page.

Whether your page object's methods encapsulate small bite-sized actions on the page such as separate methods for each field in a form, or larger workflows such as or one method to fill the entire form and submit it is up to you. That decision should be made with the design of the app in mind, and your goal is to make something that isn't only reliable, but also easy to create new content with and easy to maintain existing content with.

Edit:

Here's an example of a theoretical login page object:

public class LoginPage {
    private final WebDriver driver;

    private final String emailField = "#email";
    private final String passwordField = "#password";
    private final String submitButton = "#submit";

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

    // These are our bite sized methods right here
    public LoginPage enterEmail(String email) {
        driver.findElement(By.cssSelector(emailField)).sendKeys(email);
        return this;
    }

    public LoginPage enterPassword(String password) {
        driver.findElement(By.cssSelector(passwordField)).sendKeys(password);
        return this;
    }

    public void submit() {
        driver.findElement(By.cssSelector(submitButton)).click();
    }

    // This method represents an entire workflow, 
    // containing multiple bite-sized chunks.
    public void performLogin(String email, String password) {
        enterEmail(email);
        enterPassword(password);
        submit();
    }
}

To use, your test could do something like:

WebDriver driver = new ChromeDriver(options);
LoginPage loginPage = new LoginPage(driver);

//One way:
loginPage.enterEmail("[email protected]").enterPassword("12345").submit();

// Another way:
loginPage.performLogin("[email protected]", "12345");

And if you know exactly where you'll be redirected to, your submit and performLogin methods can return an instance of your next page object.

Upvotes: 2

vins
vins

Reputation: 15400

The other answer is good and clarifies your question. I would like to suggest a framework in this answer.

enter image description here

Your test class should not know anything about selenium. It should interact only with Page objects. Page Objects interact with browser through page fragments. Page fragments deals with webdriver.

As you are using selenium + java, Take a look at Arquillian - Graphene. It is a set of libs which helps you to create better framework in Java + Selenium. You can add the libs & simply choose not to use as well. Basically it does not mess up with your existing script.

Advantages:

  • You do not need page factory. Everything is getting injected at run time for you. Including the driver.
  • AJAX handling
  • JQuery selector
  • AngularJS selector

There are many more.

For ex: In your case,

public class LoginPage  {
...
...
...
}


public class LoginPageReceipt {
...
...
...
} 



public class ContractorPage  {
...
...
...
}

Test class

public class LoginTest{

    @Page
    LoginPage loginPage;

    @Page
    LoginPageReceipt loginPageReceipt;

    @Test
    public void login() {
        loginPage.enterUserName(USER_NAME);
        loginPage.enterUserPassword(PASSWORD);
        loginPage.login();
        assertEquals("Użytkownik: " + USER_NAME + " | Wyloguj", loginPageReceipt.loginConfirmation());
    }

}

Check here for example.

Upvotes: 0

Related Questions