Component encapsulating nested elements in Selenium POM

How to go about designing a class confirming to Selenium Page Object Model for a page similar to the one below? The page has containers having details about the post like question title, summary, tags etc. Refer to stackoverflow for a detailed view.

The conventional technique would be to use WebElement for any specified post container, say the first one, and a List of WebElement for a collection of all the post containers.

SOClassicPageObject.java
@FindBy(css="div[id^='question-summary-']")
private WebElement firstQues;

@FindBy(css="div[id^='question-summary-']")
private List<WebElement> questions;

To get the text of the post title one could write the following code.

firstQues.findElement(By.cssSelector("a[class='question-hyperlink']")).getText();

The use of WebElement to model the post container does not convey that there are nested elements present. Maybe a custom object like PostContainer instead of WebElement would be more relevant.

How to modify the existing Page Object Model framework to accommodate the custom object? No need to worry. There is already an existing mechanism available in the Appium framework which can be used seamlessly with Selenium.

The custom object is created by extending the Widget class. Widget is an existing class in Appium, extending the Page Object Model.  This object encapsulates the nested elements as well as the logic of accessing their properties and behaviors. The locator paths need to be relative to the locator of the container.

SOQuesWidget.java
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();
    }

    //Other methods for accessing properties and behaviors of nested elements.
}

How to use this extended Widget in the existing PageObject class? The PageObject can also handle the normal WebElement declarations, like the ‘logo’ field.

SOHomePageObject.java
public class SOHomePageObject {

    @FindBy(css="span[class='-img _glyph']")
    private WebElement logo;
    @FindBy(css="div[id^='question-summary-']")
    private SOQuesWidget firstQues;

    @FindBy(css="div[id^='question-summary-']")
    private List<SOQuesWidget> questions;

    private WebDriver driver;

    public SOHomePageObject(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(new AppiumFieldDecorator(driver), this);
    }

    //Methods for accessing properties and behaviors.
}

The WebElement has been changed to SOQuesWidget for the post container. To instantiate this Page Object we need the AppiumFieldDecorator in the PageFactory.

The PageObject can be instantiated the usual way and the nested element properties and behaviors can be accessed. To run the test, add the path to chromedriver (line 11).

SOWidgetTest.java
SOHomePageObject soHome = new SOHomePageObject(driver);
soHome.getFirstQues().getQuesTitle());

The Maven dependency for importing the Appium jar is as below.

pom.xml
<dependency>
    <groupId>io.appium</groupId>
    <artifactId>java-client</artifactId>
    <version>6.1.0</version>
</dependency>

The complete source code can be found at this location https://github.com/grasshopper7/component. The above setup works on the stackoverflow home page on the date of writing off the article. Any modification in the concerned page may affect the existing code.

In that case, use the static html (tables.html) as the page to test with. Each row of this table is mapped as a Widget. Refer to PersonDetailsWidget.java and PersonDetailsPO.java. The test is contained in PersonDetailsWidgetTest.java. To run the test, add the path to chromedriver (line 11) and path to the html (line 15). 

Leave a Reply

Your email address will not be published. Required fields are marked *