Search code examples
javaseleniumselenium-webdriverpageobjectspage-factory

How to eliminate duplicate WebElement in Page Object Model


I'm using POM framework where I have creates page classes for my application pages. Lets say there are 2 page in my application 1. Event 2. Timeline. So I have create 2 page classes

EventPage.java

public class RCON_D_EventPage 
{

    @FindBy(xpath="//input[@placeholder='Search for entered records']")
    public WebElement eventSearchBox;

    @FindBy(xpath="//button[@class='btn rc-gray-bg rc-dashboard-contact-btn ng-scope']")
    public WebElement eventSearchButton;


    @FindBy(xpath="//p[@ class='rc-found-record no-padding ng-binding ng-scope']")
    public WebElement eventSearchResult;

    @FindBy(xpath="//div/span[@class='ng-scope']")
    public WebElement searchResultNotFound;

    @FindBy(xpath="//li/button[@ng-click='goToFirstPage()']")
    public WebElement nextPageButton;

    @FindBy(xpath="//button[@ng-click='clearFilters()'][1]")
    public WebElement clearFilterButton;


    WebDriver driver;

    public RCON_D_EventPage(WebDriver driver)
    {

        PageFactory.initElements(new AjaxElementLocatorFactory(driver, 10), this);
        this.driver=driver;
    }
    public void enterTextInEventSearchBox(String text)
    {
        eventSearchBox.clear();
        eventSearchBox.sendKeys(text);
    }

    public void clickEventSearchButton()
    {
        eventSearchButton.click();
    }

    public String getEventSearchResult()
    {
        return eventSearchResult.getText();
    }

    public String getNoRecordFoundMessage()
    {
        return searchResultNotFound.getText();
    }
    public void clickNextPageButton()
    {
        nextPageButton.click();
    }
    public void clickClearFilterButton()
    {
        clearFilterButton.click();
    }
}

TimeLinePage.java

public class RCON_D_TimelinePage 
{

    @FindBy(xpath="//input[@placeholder='Search for entered records']")
    public WebElement timelineSearchBox;

    @FindBy(xpath="//button[@class='btn rc-gray-bg rc-dashboard-contact-btn ng-scope']")
    public WebElement searchButton;

    @FindBy(xpath="//p[@class='rc-found-record no-padding ng-binding ng-scope']")
    public WebElement searchResult;

    @FindBy(xpath="//div[@class='row ng-scope']")
    public List<WebElement> totalFoundRecords;

    @FindBy(xpath="//span[text()='No data found']")
    public WebElement noResultMessage;

    @FindBy(xpath="//button[@ng-click='clearFilters()'][1]")
    public WebElement clearFilterButton;

    public RCON_D_TimelinePage(WebDriver driver)
    {

        PageFactory.initElements(new AjaxElementLocatorFactory(driver, 10), this);
        this.driver=driver;
    }

    public void enterTextInSearchBox(String text)
    {
        timelineSearchBox.sendKeys(text);
    }
    public void clickSearchButton()
    {
        searchButton.click();
    }
    public String getSearchResult()
    {
        return searchResult.getText();
    }
    public int getFoundRecordCount()
    {
        return totalFoundRecords.size();
    }

    public String getNoResultFoundMessage()
    {
        return noResultMessage.getText();
    }

    public void clickClearFilterButton()
    {
        clearFilterButton.click();
    }
}

So here in Both page there are some common WebElement e.g. //input[@placeholder='Search for entered records'] or //button[@class='btn rc-gray-bg rc-dashboard-contact-btn ng-scope'] and so on. So it there any way that I can manage this redundancy in Page Object Model ?


Solution

  • You can use composition (is-A, has-A relationship) in this case.

    has-A Relationship

    you can create a Page Class for search and copy all the methods inside this class. And all the other Page class which has this element, you can just create the object of this Page.

    The following example shows has-A relationship.

    class SearchPage{
        @FindBy(xpath="//input[@placeholder='Search for entered records']")
        public WebElement timelineSearchBox;
    
        @FindBy(xpath="//button[@class='btn rc-gray-bg rc-dashboard-contact-btn ng-scope']")
        public WebElement searchButton;
    
        public SearchPage(WebDriver driver){
            PageFactory.initElements(new AjaxElementLocatorFactory(driver, 10), this);
            this.driver = driver;
        }
        public void enterTextInSearchBox(String text){
            timelineSearchBox.sendKeys(text);
        }
        public void clickSearchButton(){
            searchButton.click();
        }
    }
    
    
    public class RCON_D_EventPage{
        SearchPage searchPage;
        public RCON_D_EventPage(WebDriver driver){
    
            PageFactory.initElements(new AjaxElementLocatorFactory(driver, 10), this);
            this.driver=driver;
            searchPage = new SearchPage(driver);
        }
    }
    

    is-A Relationship

    You can achieve the same thing using is-A relationship as well. What I mean is you can extend each and every class which needs search functionality with SearchPage class.

    Personally, I would suggest to use has-A Relationship as it make more sense, in terms of programming rather than is-A as mentioned below.

    public class RCON_D_EventPage extends SearchPage{
    
        public RCON_D_EventPage(WebDriver driver){
            super(driver);
            PageFactory.initElements(new AjaxElementLocatorFactory(driver, 10), this);
            this.driver=driver;
    
        }
    }