3

One of the most popular patterns in testing based on selenium is page object. Unfortunately, we often need to duplicate code if we use it as is. Consider the following situation:

  • We use UI framework, with common component, say some fancy table
  • Table is quite complicated, has filtering, sorting, searching
  • The table is used on several pages in the app

Is there any existing infrastructure to create more granular component objects instead of page objects either in selenium or in a thrid party lbirary?. I mean, annotations, and related infrastructure?

Konstantin Solomatov
  • 10,252
  • 8
  • 58
  • 88

2 Answers2

6

Appium which is the mobile implementation of selenium webdriver has a concept of widgets which is an extension of pageobjects. There is a Widget class which allows one to search relative to an element including in a web browser. You can check this out in the appium source test section. Have a look in the package io.appium.java_client.pagefactory_tests.widgets. This supports the FindBy annotation and WebElement construct and the PageFactory initialization.

So instead of using

@FindBy(.......)
private WebElement myTable;

you can use

@FindBy(container of the table....)
private Table myTable;

Table class can now have all the relevant variables and methods.

Part of POM.xml

<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-api</artifactId>
    <version>2.53.1</version>
</dependency>
<dependency>
    <groupId>io.appium</groupId>
    <artifactId>java-client</artifactId>
    <version>4.1.2</version>
</dependency>

Test Class --

import io.appium.java_client.pagefactory.AppiumFieldDecorator;

import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.PageFactory;

public class WidgetBrowTest {

    @Test
    public void test() {
        System.setProperty("webdriver.chrome.driver", "E:/Software Testing/Selenium/Jars/chromedriver.exe");

        WebDriver driver = new ChromeDriver();

        driver.get("http://stackoverflow.com/");


        SOHome soHome = new SOHome(driver);
        PageFactory.initElements(new AppiumFieldDecorator(driver), soHome);

        //Below two are from widget - First question in stackoverflow homepage
        System.out.println(soHome.getFirstQues().getQuesTitle());
        System.out.println(soHome.getFirstQues().getQuesTags());

        //Below two are from home page
        System.out.println(soHome.getLogoText());
        System.out.println(soHome.getMenuText());
    }
}

StackOverflow Home -

import java.util.List;
import java.util.stream.Collectors;

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

public class SOHome {

    @FindBy(css="div[id='hlogo'] > a")
    private WebElement logo;

    @FindBy(xpath="//div[@id='hmenus']//li/a")
    private List<WebElement> menuOpt;

    @FindBy(css="div[class='summary']")
    private SOQuesWidget firstQues;

    private WebDriver driver;


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

    public String getLogoText() {
        return logo.getText();
    }

    public List<String> getMenuText() {
        return menuOpt.stream().map(t -> t.getText()).collect(Collectors.toList());
    }

    public SOQuesWidget getFirstQues() {
        return firstQues;
    }
}

Question Widget - First Question

import java.util.List;
import java.util.stream.Collectors;

import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

import io.appium.java_client.pagefactory.Widget;

public class SOQuesWidget extends Widget {

    @FindBy(css="a[class='question-hyperlink']")
    private WebElement quesTitle;

    @FindBy(xpath=".//div[starts-with(@class,'tags')]/a")
    private List<WebElement> quesTags;

    protected SOQuesWidget(WebElement element) {
        super(element);
    }

    public String getQuesTitle() {
        return quesTitle.getText();
    }

    public List<String> getQuesTags() {
        return quesTags.stream().map(t -> t.getText()).collect(Collectors.toList());
    }
}
Grasshopper
  • 8,908
  • 2
  • 17
  • 32
5

Page objects is kind of a misnomer. They don't have to be specifically full pages to follow the page object model. I would create a Table class (page object) that contains all of the locators and methods for the Table object and then include it in the pages/page objects where it appears.

For example, if the home page contains a table object, then the HomePage class would reference the Table class.

JeffC
  • 22,180
  • 5
  • 32
  • 55
  • When I initialize a page, all selectors are relative to the root document. I would like to have them applied relative to some element, so that I can "parse" the table. – Konstantin Solomatov Nov 11 '16 at 00:04
  • Is there no way to uniquely identify the table from inside the table, e.g. the table has an ID or some other way to identify the `TABLE` tag? I don't suppose you can give me a link to a sample page or at least post some of the relevant HTML for the table? Maybe I can help find a way. – JeffC Nov 11 '16 at 02:26
  • This table is a hypothetical example to illustrate situation where I want to have more code reuse. Basically, I have an element, and would like it to work in a way which would allow me to use FindBy annotations relative to it or do it in some other way. – Konstantin Solomatov Nov 11 '16 at 03:59
  • I use this very method on sites that I write automation for, e.g. header, footer, top nav, other links like login/logout, cart links and so forth. This method works fine. If you have a specific case where this doesn't work, post it in your question and I'll take a look. – JeffC Nov 11 '16 at 05:55
  • Imagine, that you have two tables on one page. How to choose one or another? – Konstantin Solomatov Nov 11 '16 at 22:34
  • It depends on the HTML of each of the tables... whether they have unique attributes, e.g. id or class or ??? – JeffC Nov 11 '16 at 22:42
  • @JefC It would work, but it doesn't seem to be really nice solution to me. – Konstantin Solomatov Nov 11 '16 at 23:21