Page Object Model with JAVA Generics

Hi all, in this article I will explain how to write a general Page class to generate all pages by using the power of JAVA Generics. In this way, you can instantiate your page classes by using JAVA Generics in your tests. You can do instantiations (page object creation) in base Page Class (also you can call this class as Page Generator). Before explaining the details, I want to show you our final aim in an example test class. Then, we will elaborate this solution.

Before starting this article, I want to emphasize that we will go on with our JAVA POM example. ;) If you don’t know this, please check this article first.

In this article, I could not go into full details of Java Generics. But I can suggest you a link. You can learn Java Generics from here.

First, I need to create a Page Class it will be our new Base Class and we generate all page classes in this class so we can also call this class as Page Generator.

package pages;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.lang.reflect.InvocationTargetException;


/**
 * Created by obaskirt on 08-Nov-17.
 */
public class Page {

    public WebDriver driver;
    public WebDriverWait wait;

    //Constructor
    public Page(WebDriver driver, WebDriverWait wait){
        this.driver = driver;
        this.wait = wait;
    }

    //JAVA Generics to Create and return a New Page
    public  <TPage extends BasePage> TPage GetInstance (Class<TPage> pageClass) {
        try {
            return pageClass.getDeclaredConstructor(WebDriver.class, WebDriverWait.class).newInstance(this.driver, this.wait);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

In above code, at Generics part, we get page classes as a parameter and instantiate them or generate them at return statement by using “this.driver” and “this.wait” parameters. Here is the most critical line:

return pageClass.getDeclaredConstructor(WebDriver.class, WebDriverWait.class).newInstance(this.driver, this.wait);

Now, BasePage class should extend Page class because the new base class is now Page class.

package pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.WebDriverWait;

public class BasePage extends Page {

    public BasePage(WebDriver driver, WebDriverWait wait) {
        super(driver, wait);
    }

    //Click Method
    public void click (By elementLocation) {
        driver.findElement(elementLocation).click();
    }

    //Write Text
    public void writeText (By elementLocation, String text) {
        driver.findElement(elementLocation).sendKeys(text);
    }

    //Read Text
    public String readText (By elementLocation) {
        return driver.findElement(elementLocation).getText();
    }
}

HomePage and LoginPage classes will remain same. I didn’t do any changes.

We should instantiate Page Class in our setup method before the starting of test methods.

package tests;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import pages.Page;

public class BaseTest {
    public WebDriver driver;
    public WebDriverWait wait;
    public Page page;

    @BeforeMethod
    public void setup () {
        //Create a Chrome driver. All test classes use this.
        driver = new ChromeDriver();

        //Create a wait. All test classes use this.
        wait = new WebDriverWait(driver,15);

        //Maximize Window
        driver.manage().window().maximize();

        //Instantiate the Page Class
        page = new Page(driver,wait);
    }

    @AfterMethod
    public void teardown () {
        driver.quit();
    }
}

And the final changes should be implemented in LoginTests class. We had two test classes in our POM example and I wanted to change the first one by using JAVA Generics. I didn’t explicitly instantiate the Page Classes, I used JAVA Generics to do this. However, I didn’t change the second test’s code so you can notice the difference easily. ;) Here is the test code:

package tests;

import org.testng.annotations.Test;
import pages.HomePage;
import pages.LoginPage;

public class LoginTests extends BaseTest {

    @Test (priority = 0)
    public void invalidLoginTest_InvalidUserNameInvalidPassword () throws InterruptedException {

        //*************PAGE METHODS WITH JAVA GENERICS********************
        //Open N11 HomePage
        page.GetInstance(HomePage.class).goToN11();

        //Go to LoginPage
        page.GetInstance(HomePage.class).goToLoginPage();

        //Login to N11
        page.GetInstance(LoginPage.class).loginToN11("[email protected]", "11223344");

        //*************ASSERTIONS***********************
        //Thread.sleep(500); //It is better to use explicit wait here.
        wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[text()='E-posta adresiniz veya şifreniz hatalı']")));
        page.GetInstance(LoginPage.class).verifyLoginPassword(("E-posta adresiniz veya şifreniz hatalı"));
    }

    @Test (priority = 1)
    public void invalidLoginTest_EmptyUserEmptyPassword () throws InterruptedException {
        //*************PAGE INSTANTIATIONS*************
        HomePage homePage = new HomePage(driver,wait);
        LoginPage loginPage = new LoginPage(driver,wait);

        //*************PAGE METHODS********************
        homePage.goToN11();
        homePage.goToLoginPage();
        loginPage.loginToN11("","");

        //*************ASSERTIONS***********************
        //Thread.sleep(500); //It is better to use explicit wait here.
        wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[text()='Lütfen e-posta adresinizi girin.']")));
        page.GetInstance(LoginPage.class).verifyLoginUserName("Lütfen e-posta adresinizi girin.");

        wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[text()='Bu alanın doldurulması zorunludur.']")));
        page.GetInstance(LoginPage.class).verifyLoginPassword("Bu alanın doldurulması zorunludur.");
    }

}

When you run the tests, all of them should be passed. ;)

java generics

That’s all. In this article, I tried to explain you Page Generation concept by using JAVA Generics by modifying our POM example.

You can find the project code here: https://github.com/swtestacademy/POMWithGenerics

Selenium Webdriver Tutorial Series

[fusion_widget_area name=”avada-custom-sidebar-seleniumwidget” title_size=”” title_color=”” background_color=”” padding_top=”” padding_right=”” padding_bottom=”” padding_left=”” hide_on_mobile=”small-visibility,medium-visibility,large-visibility” class=”” id=””][/fusion_widget_area]

Thanks.
-Onur

8 thoughts on “Page Object Model with JAVA Generics”

    • Hi Gundeep, it should be explicit wait. We should not use Thread.Sleep in our tests. I will fix that line. I put that line for verification but I forgot the change it with explicit wait. Thank you for your comment.

      Reply
  1. Dear Onur,

    I’m trying to apply page object model and TestNg in one project where as i’m getting below error.Could you please help me on this .

    This issue is occurred while running in TestNg only.

    ** Error Message**
    java.lang.NullPointerException
    at com.test.FlyDubai.test.regression.FlyDubai_test.testSearchRoundTrip(FlyDubai_test.java:40)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:124)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:571)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:707)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:979)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
    at org.testng.TestRunner.privateRun(TestRunner.java:648)
    at org.testng.TestRunner.run(TestRunner.java:505)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
    at org.testng.SuiteRunner.run(SuiteRunner.java:364)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:84)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1187)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1116)
    at org.testng.TestNG.runSuites(TestNG.java:1028)
    at org.testng.TestNG.run(TestNG.java:996)
    **Error Message**

    ***********************************
    2)Test Class

    package com.test.FlyDubai.test.regression;

    import java.util.List;
    import java.util.concurrent.TimeUnit;

    import org.openqa.selenium.By;
    import org.openqa.selenium.JavascriptExecutor;
    import org.openqa.selenium.Keys;
    import org.openqa.selenium.StaleElementReferenceException;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.firefox.FirefoxDriver;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    import org.openqa.selenium.support.ui.Select;
    import org.openqa.selenium.support.ui.WebDriverWait;
    import org.testng.annotations.AfterMethod;
    import org.testng.annotations.BeforeMethod;
    import org.testng.annotations.Test;

    import com.google.common.annotations.VisibleForTesting;
    import com.test.FlyDubai.UI.FlyDubai;
    import com.test.FlyDubai.config.TestConfiguration;

    import java.util.List;
    import java.util.concurrent.TimeUnit;

    public class FlyDubai_test{
    WebDriver driver;
    FlyDubai pagesrc;

    @BeforeMethod
    public void setUp()
    {
    driver=TestConfiguration.getDriverInstance();
    FlyDubai pagesrc=new FlyDubai(driver);
    }
    @Test
    public void testSearchRoundTrip()
    {
    pagesrc.selectDestfromList();
    pagesrc.selectDeptDate();
    pagesrc.selectArrivalDate();
    pagesrc.selectButton().click();
    }
    @AfterMethod

    public void tearDown()
    {
    driver.close();
    }

    }

    ********************

    3)Page Object Class

    package com.test.FlyDubai.UI;

    import java.util.List;

    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    import org.openqa.selenium.support.ui.Select;
    import org.openqa.selenium.support.ui.WebDriverWait;

    public class FlyDubai {

    public WebDriver driver;
    public WebDriverWait wait;

    public FlyDubai(WebDriver driver)
    {
    this.driver=driver;
    }
    public void selectDestfromList()
    {
    WebElement seDest =driver.findElement(By.id(“airport-destination”));
    seDest.sendKeys(“AD”);
    ListDrpList=driver.findElements(By.xpath(“.//*[@id=’tab-flight-booking’]/form/div[1]/div/span/span[3]/div/ul/li”));
    for ( WebElement a:DrpList)
    {
    System.out.println(“Get the drop downlist”+a.getText());
    DrpList.get(3).click();
    break;
    }
    }
    public void selectDeptDate()
    {
    driver.findElement(By.id(“departureDate”)).click();
    wait.until(ExpectedConditions.visibilityOf(driver.findElements(By.cssSelector(“.pika-table”)).get(0)));
    driver.findElements(By.cssSelector(“button[data-pika-day=’11’][data-pika-month=’0′][data-pika-year=’2018′]”)).get(0).click();
    }
    public void selectArrivalDate()
    {
    WebElement ArrDate=driver.findElement(By.id(“return-date”));
    ArrDate.click();
    wait.until(ExpectedConditions.visibilityOf(driver.findElements(By.cssSelector(“.pika-table”)).get(1)));
    driver.findElements(By.cssSelector(“button[data-pika-day=’18’][data-pika-month=’0′][data-pika-year=’2018′]”)).get(1).click();

    }

    public void OnewayRadio()
    {
    WebElement Oneway=driver.findElement(By.cssSelector(“label[for=return-trip-0]”));
    Oneway.click();
    }

    public void getAdult()
    {
    WebElement adlt =driver.findElement(By.id(“select-adults-0”));
    Select adtlist=new Select(adlt);
    adtlist.selectByIndex(1);
    }

    public void getChild()
    {
    WebElement chd =driver.findElement(By.id(“select-children-0”));
    Select chdlist=new Select(chd);
    chdlist.selectByIndex(1);
    }
    public void getInf()
    {
    WebElement inf =driver.findElement(By.id(“select-infants-0”));
    Select inflist=new Select(inf);
    inflist.selectByIndex(1);
    }
    public WebElement selectButton()

    {
    WebElement seButton=driver.findElement(By.id(“find-flights”));
    return seButton;
    }

    }

    *****************************************

    Reply
  2. Hi Onir,

    My question is similar to Kripesh’s above. When i run my scripts indvidually it works well. but when i use via Testng, it throws null pointer exception. the only change that i have done is :
    instead of using a “@BeforeMethod” annotation in my basetest.java , i have used “@BeforeTest” .
    i have called all my test classes from testng.xml.

    please help. Thanks.

    Reply
  3. I used this approach in my project. If I run my testcases using Java Generics, it runs fine and testcases get executed properly. However if I use Page methods, the testcases fails with the error message.

    FAILED CONFIGURATIONS @BEFOREMETHOD setup()

    https://stackoverflow.com/questions/72437339/failed-configuration-beforemethod-setup-selenium-java-testng

    Here is the complete code that I wanted to run using page methods. You can view the error logs as well.

    Reply

Leave a Comment

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