SlowLoadableComponent Pattern in Page Object Model

Hi all, in the previous article, I explained how to implement loadablecomponent pattern and in this article, I will explain how to implement the slowloadablecomponent pattern. The loadablecomponent pattern is convenient for the pages which load not so slowly. However, when a site loads very slow, then we should consider using slowloadablecomponent rather than loadablecomponent. Slowloadablecomponent verifies the page is loaded until the given maximum timeout.

In order to implement this pattern, first we need to extend our page classes with SlowLoadableComponent class as shown below:

public class HomePage extends SlowLoadableComponent<HomePage> {
...
...
}

public class LoginPage extends SlowLoadableComponent<LoginPage> {
...
...
}

SlowLoadableComponent class implementation is shown below. It extends LoadableComponent class and implements the get() method. By using SlowLoadableComponent, we wait the page is loaded until given timeout period. We should implement load() and isLoaded() methods in page classes and use get() method to obtain loaded page class. load() and isLoaded() method definitions are in the LoadableComponent class.

It extends the LoadableComponent class:

public abstract class SlowLoadableComponent<T extends LoadableComponent<T>>

It has two constructor parameters:

private final Clock clock;
private final long timeOutInSeconds;

public SlowLoadableComponent(Clock clock, int timeOutInSeconds) { 
     this.clock = clock; 
     this.timeOutInSeconds = timeOutInSeconds; 
}

It implements the get() method by using given timeout period. The full implementation is shown below:

public abstract class SlowLoadableComponent<T extends LoadableComponent<T>> extends LoadableComponent<T> {
    private final Clock clock;
    private final long timeOutInSeconds;

    public SlowLoadableComponent(Clock clock, int timeOutInSeconds) {
        this.clock = clock;
        this.timeOutInSeconds = (long)timeOutInSeconds;
    }

    public T get() {
        try {
            this.isLoaded();
            return this;
        } catch (Error var5) {
            this.load();
            long end = this.clock.laterBy(TimeUnit.SECONDS.toMillis(this.timeOutInSeconds));

            while(this.clock.isNowBefore(end)) {
                try {
                    this.isLoaded();
                    return this;
                } catch (Error var4) {
                    this.isError();
                    this.waitFor();
                }
            }

            this.isLoaded();
            return this;
        }
    }

    protected void isError() throws Error {
    }

    protected long sleepFor() {
        return 200L;
    }

    private void waitFor() {
        try {
            Thread.sleep(this.sleepFor());
        } catch (InterruptedException var2) {
            throw new AssertionError(var2);
        }
    }
}

Now, I want to share with you the project architecture:

I will go on with page and test implementations.

BasePage Class:

I declared common methods such as click, sendText, getText, etc. in BasePage Class.

package pages;

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

public class BasePage {
    private WebDriver driver;

    //Constructor
    public BasePage (WebDriver driver){
        this.driver = driver;
    }

    //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 Class:

Here, I extended SlowLoadableComponent class and I implemented load() and isLoaded() methods which belong to LoadableComponent abstract class. SlowLoadableComponent class extents LoadableComponent class so it also has these methods and we need to implement them. I also instantiate BasePage class’s object in the constructor to use its methods via basePage object.  The most critical part is to set the clock and timeout period of page load. I did this in the constructor with below code:

super(new SystemClock(), 20);

package pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.LoadableComponent;
import org.openqa.selenium.support.ui.SlowLoadableComponent;
import org.openqa.selenium.support.ui.SystemClock;
import org.openqa.selenium.support.ui.WebDriverWait;

import static org.testng.AssertJUnit.assertTrue;

public class HomePage extends SlowLoadableComponent<HomePage> {

    //*********Page Variables*********
    private String baseURL = "https://www.n11.com/";
    private WebDriver driver;
    private WebDriverWait wait;
    private BasePage basePage;

    //*********Constructor*********
    public HomePage (WebDriver driver) {
        super(new SystemClock(), 20);
        this.driver = driver;
        this.wait = new WebDriverWait(driver, 10);
        basePage = new BasePage(driver);
    }

    //*********Web Elements*********
    By signInButtonBy = By.className("btnSignIn");

    //*********Override LoadableComponent Methods*********
    //We need to go to the page at load method
    @Override
    protected void load() {
        this.driver.get(baseURL);
    }

    //We need to check that the page has been loaded.
    @Override
    protected void isLoaded() throws Error {
        try{
            driver.findElement(signInButtonBy);
        } catch(Exception e){
            System.out.println("Home page has not been loaded yet!");
            throw new Error(e.getMessage());
        }
    }

    //*********Page Methods*********
    //Go to LoginPage
    public LoginPage goToLoginPage (){
        basePage.click(signInButtonBy);
        return new LoginPage(this.driver, this);
    }

}

LoginPage Class:

  • First, I extended SlowLoadableComponent class.
  • Set values to clock and timeout period in the constructor by using super keyword.
  • Instantiated BasePage class to use its methods.
  • Set Parent with parent parameter of the constructor. We can also use LoadableComponent<?> instead of LoadableComponent<HomePage> to make it more generic.
  • Then, I implemented load() and isLoaded() methods.
  • Finally, I added page classes which are related to the login operations.
package pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.*;
import org.testng.Assert;

import static org.testng.AssertJUnit.assertTrue;

public class LoginPage extends SlowLoadableComponent<LoginPage> {

    //*********Page Variables*********
    private WebDriver driver;
    private WebDriverWait wait;
    private BasePage page;
    private LoadableComponent<HomePage> parent;
    private final String loginURL = "https://www.n11.com/giris-yap";

    //*********Constructor*********
    public LoginPage (WebDriver driver, LoadableComponent<HomePage> parent) {
         super(new SystemClock(), 20);
         this.driver = driver;
         this.wait = new WebDriverWait(driver,10);
         page = new BasePage(this.driver);
         this.parent = parent;
    }

    //*********Web Elements*********
    By usernameBy = By.id("email");
    By passwordBy = By.id("password");
    By loginButtonBy = By.id("loginButton");
    By errorMessageUsernameBy = By.cssSelector("#loginForm .error:nth-of-type(1) .errorMessage");
    By errorMessagePasswordBy = By.cssSelector("#loginForm .error:nth-of-type(2) .errorText");

    //*********Override LoadableComponent Methods*********
    //We need to go to the page at load method
    @Override
    protected void load() {
        parent.get().goToLoginPage();
    }

    //We need to check that the page has been loaded.
    @Override
    protected void isLoaded() throws Error {
        try{
            driver.findElement(usernameBy);
            driver.findElement(passwordBy);
            driver.findElement(loginButtonBy);
        } catch(Exception e){
            System.out.println("Login page has not been loaded yet!");
            throw new Error(e.getMessage());
        }
    }

    //*********Page Methods*********
    public void loginToN11 (String username, String password){
        //Enter Username(Email)
        page.writeText(usernameBy,username);
        //Enter Password
        page.writeText(passwordBy, password);
        //Click Login Button
        page.click(usernameBy); //In order to click right, this line needed. Site related.
        page.click(loginButtonBy);
    }

    //Verify Username Condition
    public void verifyLoginUserName (String expectedText) {
        wait.until(ExpectedConditions.visibilityOfElementLocated(errorMessageUsernameBy));
        Assert.assertEquals(page.readText(errorMessageUsernameBy), expectedText);
    }

    //Verify Password Condition
    public void verifyLoginPassword (String expectedText) {
        wait.until(ExpectedConditions.visibilityOfElementLocated(errorMessagePasswordBy));
        Assert.assertEquals(page.readText(errorMessagePasswordBy), expectedText);
    }
}

BaseTest Class:

I implemented class level setup and teardown.

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;

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

    @BeforeClass(description = "Class Level Setup!")
    public void classLevelSetup () {
        //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();
    }

    @AfterClass (description = "Class Level Teardown!")
    public void classLevelTeardown () {
        driver.quit();
    }

}

LoginTests Class:

For method level setup, I used @BeforeMethod annotation and instantiated pages and get the login page via login.get() line. When we call login.get() method, it first, load the parent class which is HomePage and then load the LoginPage class.

This method, firsts call’s LoginPage class’s load() method. In this method, parent.get() part calls HomePage class’s load() method and gets the HomePage class then calls its goToLoginPage() method to load LoginPage(). It is shown below:

@Override
protected void load() {
parent.get().goToLoginPage();
}

In @Test blocks we can only call LoginTest page’s methods to fulfill login operation and test scenarios. In this way, we don’t need to explicity call HomePage class’s methods to go/open/load LoginPage.

package tests;

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

public class LoginTests extends BaseTest {

    //Test Data
    private String wrongUsername = "[email protected]";
    private String wrongPassword = "11122233444";
    private HomePage homePage;
    private LoginPage loginPage;

    @BeforeMethod(description = "Method Level Setup!")
    public void methodLevelSetup() {
        //*************PAGE INSTANTIATIONS*************
        homePage = new HomePage(driver);
        loginPage = new LoginPage(driver, homePage);

        //*************PAGE METHODS********************
        //Open N11 HomePage and Go to Login Page
        loginPage.get();
    }

    @Test (priority = 2, description="Invalid Login Scenario with wrong username and password.")
    public void invalidLoginTest_InvalidUserNameInvalidPassword () {
        //*************PAGE METHODS********************
        loginPage.loginToN11(wrongUsername, wrongPassword);

        //*************ASSERTIONS***********************
        loginPage.verifyLoginPassword(("E-posta adresiniz veya şifreniz hatalı"));
    }

    @Test (priority = 1, description="Invalid Login Scenario with empty username and password.")
    public void invalidLoginTest_EmptyUserEmptyPassword ()  {
        //*************PAGE INSTANTIATIONS*************
        loginPage.loginToN11("","");

        //*************ASSERTIONS***********************
        loginPage.verifyLoginUserName("Lütfen e-posta adresinizi girin.");
        loginPage.verifyLoginPassword("Bu alanın doldurulması zorunludur.");
    }
}

I do not want to share here ExtentManager and TestListener classes. They are not related to the SlowLoadable Component pattern. You can find the project here: https://github.com/swtestacademy/SlowLoadableComponentExample

[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 Baskirt

4 thoughts on “SlowLoadableComponent Pattern in Page Object Model”

  1. I am having issues implementing slowloablecomponent in my framework. When i extend it in my pages, i am required to pass in Clock and seconds to the constructor but new i pass in a new Clock instances, i am required to implement the unimplemented methods. Could you give me some advice on how to proceed further

    Reply
    • Just extend your pages with SlowLoadableComponent class and instantiate your BasePage class in each specific page class that I did in this article.

      public class LoginPage extends SlowLoadableComponent

      //*********Constructor*********
      public HomePage (WebDriver driver) {
      super(new SystemClock(), 20);
      this.driver = driver;
      this.wait = new WebDriverWait(driver, 10);
      basePage = new BasePage(driver);
      }

      Reply
  2. hi,

    Which version of Selenium support dependency is used here? I am not finding SystemClock class in selenium.support.ui.

    Thanks

    Reply

Leave a Comment

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