Selenium WebDriver – Advanced Page Object Pattern with Page Fragments using Arquillian Graphene

This is second post in Arquillian Graphene series. So, I would request you to read this post first if you have not already to get basic understanding of what Graphene is! Please note that Graphene is a framework / wrapper for Selenium WebDriver. So you do not miss any features of the WebDriver when you use Graphene. 

 

Problem with Page Objects:

Page objects is a well known design pattern, widely accepted by the automation engineers, to create separate class file for each page of the application to group all the elements as properties and their behaviors / business functionalities as methods of the class. But it might not be a great idea always, especially when the page has more / different sets of elements / complex element like a grid / calendar widget / a HTML table etc.

Code Smell – Large Class:

When you try to incorporate all the elements of a page in one single page class, it becomes too big to read, maintain etc. The class might contain too many responsibilities to handle. It should be restructured and broken into smaller classes.

I would expect my page objects to satisfy the Single Responsibility Responsible.

I personally prefer to create multiple levels of abstractions to satisfy that when I create my framework to come up with a robust, reliable, readable tests. I would like to show how It can be achieved using Arquillian Graphene – page fragments concepts.

Why Abstract:

  • Single Responsibility – A class should have one, and only one, reason to change.
  • Separation of concerns
  • Very easy to read code
  • Very easy to maintenance
  • Avoid redundancy

Page Fragments:

Concept wise it is same like Page Objects. Both Page Objects and Page Fragments, encapsulate some elements in the page and behaviors. Main difference between them is – When Page object encapsulates  a specific page, a page fragment encapsulates a component/element/widget in the page. So our page object contains all the page fragments instead of the page elements.

 

advanced-page-object-model

Note:

Our framework should be as shown in the above image.

  • Only page fragments interact with Selenium WebDriver.
  • Page holds all the reusable fragments.
  • Tests interacts with fragments through page object.
  • Test should not access WebDriver directly.
  • Test should be like simple plan English.

Udemy – Selenium WebDriver and Design Patterns Course:

VinsGuru has released a brand new course in Udemy on Selenium WebDriver and Design Patterns. ~8 hours course with all the important design patterns to design better, reusable page object models, test classes etc. Please access the above link which gives you the special discount.  You can also get your money back if you do not like the course within 30 days.

Google Search Page Object:

Lets consider the same google search page object we had created in the previous post.

@Location("https://www.google.com")
public class Google {

    @Drone
    private WebDriver driver;

    @FindBy(name = "q")
    private WebElement searchBox;

    @FindBy(name = "btnG")
    private WebElement searchbutton;

    @FindByJQuery(".rc")
    private List < WebElement > results;

    public void searchFor(String searchQuery) {

        //Search
        searchBox.sendKeys(searchQuery);

        //Graphene gurads the request and waits for the response
        Graphene.guardAjax(this.searchbutton).click();

        //Print the result count
        System.out.println("Result count:" + results.size());

    }
}

The above page object looks OK.

But did I consider all the navigation, elements or behaviors of the page? No!

If I try to add the Google search suggestions , search navigation etc then this class file becomes a very large class which is one of the code smells. It also becomes very difficult to maintain and make it less readable & less reliable.

This is where page fragments come into picture!!

Instead of placing all the elements we see in this page in a single class file, we encapsulate them in a different class file and make it available as part of the main page class.

Google Search Navigation:

Lets consider below section in google search page and see how it can be included in our test.

arq-page-frag-google-02

Create a separate class just like a page object – only consider those elements and their behaviors as part of the class.

public class GoogleSearchNavigation {

    @FindByJQuery("div.hdtb-mitem:contains('All')")
    private WebElement all;

    @FindByJQuery("div.hdtb-mitem:contains('Videos')")
    private WebElement videos;

    @FindByJQuery("div.hdtb-mitem:contains('Images')")
    private WebElement images;

    @FindByJQuery("div.hdtb-mitem:contains('Shopping')")
    private WebElement shopping;

    public void goToVideos() {
        System.out.println("Clicking on Videos");
        Graphene.guardAjax(videos).click();
    }

    public void goToImages() {
        System.out.println("Clicking on Images");
        Graphene.guardHttp(images).click();
    }

    public void goToShopping() {
        System.out.println("Clicking on Shopping");
        Graphene.guardAjax(shopping).click();
    }

    public void goToAll() {
        System.out.println("Clicking on All");
        Graphene.guardHttp(all).click();
    }
}

Google Search Suggestion:

Lets consider below section in google search page and see how it can be included in our test.

arq-page-frag-google-03

Create a separate class as we did above for navigation part.

public class GoogleSearchSuggestions {

    @FindBy(css = "li div.sbqs_c")
    private List < WebElement > suggesions;

    public int getCount() {
        return suggesions.size();
    }

    public void show() {
        for (WebElement s: suggesions)
            System.out.println(s.getText());
    }

}

I am just trying to print all the suggestions in the above example.

Google Search Result:

Lets include google search result section as well in our test.

I would like to consider only the blue color title and content in black color in my class.

public class GoogleSearchResult {

    @FindBy(css = "h3 a")
    private WebElement resultHeader;

    @FindBy(css = "span.st")
    private WebElement resultText;

    public String getResultHeader() {
        return resultHeader.getText();
    }

    public String getResultText() {
        return resultText.getText();
    }
}

public class GoogleSearchResults {

    @FindByJQuery(".rc")
    private List < GoogleSearchResult > results;

    public int getCount() {
        return results.size();
    }

    public void show() {
        System.out.println("\nResults:\n");
        for (GoogleSearchResult result: results)
            System.out.println(result.getResultHeader());
    }

}

Google Search Widget:

Lets also create a separate class for search text box and button.

arq-02

public class GoogleSearchWidget {

    @FindBy(name = "q")
    private WebElement searchBox;

    @FindBy(name = "btnG")
    private WebElement searchButton;

    public void searchFor(String searchString) {
        searchBox.clear();

        //Google makes ajax calls during search
        int length = searchString.length();
        searchBox.sendKeys(searchString.substring(0, length - 1));
        Graphene.guardAjax(searchBox).sendKeys(searchString.substring(length - 1));
    }

    public void search() {
        Graphene.guardAjax(searchButton).click();
    }

}

Google Search Page Object:

We need to refactor our google search main page object by including these page fragments. Our Page object becomes as shown here.

public class Google {

    @Drone
    private WebDriver driver;

    @FindBy(id = "sbtc")
    private GoogleSearchWidget searchWidget;

    @FindBy(id = "rso")
    private GoogleSearchResults results;

    @FindBy(id = "hdtb-msb")
    private GoogleSearchNavigation navigation;

    @FindBy(css = "div.sbsb_a")
    private GoogleSearchSuggestions suggestions;


    public void goTo() {
        driver.get("https://www.google.com");
    }

    public boolean isAt() {
        return driver.getTitle().equals("Google");
    }

    public GoogleSearchWidget getSearchWidget() {
        return searchWidget;
    }

    public GoogleSearchResults getSearchResults() {
        return results;
    }

    public GoogleSearchNavigation getTopNavigation() {
        return navigation;
    }

    public GoogleSearchSuggestions getSuggestions() {
        return suggestions;
    }
}

If you notice, I still use ‘FindBy‘ annotation. But instead of using plain WebElement, I use the Page fragments classes we have created. Above Google Search page object looks better than how it would have been if we had considered all the elements of the page in one single class. As you see we do NOT use any ‘new‘ keyword to create an instance of the fragments. Graphene makes it easy for us by automatically injecting the instance of the page fragments on the fly and delegates the behaviors of the page to the corresponding page fragment classes.

TestNG Test:

Lets create a TestNG test to test this google search behavior.

@RunAsClient
public class GoogleSearchTest extends Arquillian {

    @Page
    Google google;


    @Test(priority = 1)
    public void launchGoogle() {

        google.goTo();
        Assert.assertTrue(google.isAt());

    }

    @Test(priority = 2)
    public void canGoogleSuggest() {

        google.getSearchWidget().searchFor("Arquillian");
        google.getSuggestions().show();

        Assert.assertEquals(google.getSuggestions().getCount(), 4);

    }

    @Test(priority = 3)
    public void canGoogleShowResult() {

        google.getSearchWidget().searchFor("Test Automation Guru");
        google.getSearchWidget().search();
        google.getSearchResults().show();

        Assert.assertEquals(google.getSearchResults().getCount(), 8);

    }

    @Test(priority = 4)
    public void canGoogleNavigate() {

        //Navigate - assert : TBD
        google.getTopNavigation().goToVideos();
        google.getTopNavigation().goToShopping();
        google.getTopNavigation().goToImages();
        google.getTopNavigation().goToAll();

    }

}

When I run this test, I get the output as shown here.

 

arq-03

If you had noticed, I have not used any implicit / explicit  wait statements, or Thread.sleep(). Graphene has the request guards which know how long to wait for the HTTP/AJAX requests made by the application. We do not need to bang our heads against the wall for the page synchronization!!  Graphene will just take care!

Summary:

Like Page objects, Page fragments encapsulate certain components of the page and makes the test robust, reliable and readable. When a particular section of the page/class (like header / footer etc) gets repeated in other pages, We can create a class for it [page fragment] and include/inherit in all the page objects. Now, in case of any application change, we know where to update to make our test work!

Page Fragment does not have to be a single level of abstraction – We can also have multi level of abstraction using Page Fragments. [In the above example, GoogleSearchResults contains the list of GoogleSearchResult]

In Selenium WebDriver, whether a text box or a table, everything is a WebElement. But by using Graphene, we can create a reusable, application independent, Table – a page fragment for table object containing all the table specific methods like

  • getRowCount()
  • getColumnCount()
  • getCellData(row, column) etc

In your page objects, simply use FindBy annotation as you would do normally to find the table – the rest will be taken care by Graphene!!

 

Happy testing 🙂

Share This:

21 thoughts on “Selenium WebDriver – Advanced Page Object Pattern with Page Fragments using Arquillian Graphene

  1. Can you please create a video of creating a framework with few tests using TestNG, Maven, Arquillan Graphene, its popular extensions and maybe make it datadriven for an angular JS application taking care of page synchronization to understand it better and embed the YouTube video on this page itself and code links too.
    I am automating an angular application and these sync issues kill the stability of the suite.

    Thank you
    Rahul Rana

  2. This is great article.
    Graphene.guardAjax is not working in my system for current version of Graphene(2.3.2).

    1. I use 2.3.2 which works great. Not sure if you are using the corresponding drone version.

      version.org.jboss.arquillian=.1.1.15.Final
      version.org.jboss.arquillian.drone=.2.4.5
      graphene-webdriver=2.3.2

  3. I am actually delighted to glance at this weblog posts
    which consists of plenty of valuable information

  4. What is @FindByJQuery ??
    It is a new type of Selector or Advanced CSS or we need to download extra Jar files for @FindByJQuery

  5. This is great article and idea. Just quick question in the Google class.
    @FindBy(id = “sbtc”)
    private GoogleSearchWidget searchWidget;
    @FindBy(id = “rso”)
    private GoogleSearchResults results;
    @FindBy(id = “hdtb-msb”)
    private GoogleSearchNavigation navigation;
    @FindBy(css = “div.sbsb_a”)
    private GoogleSearchSuggestions suggestions;

    How did you get id for those? those are not WebElement. Those are class objects not WebElement objects.

  6. Thanks for getting back to me so quickly. I understand you identified element as page components.
    but not sure how you get page component id. For example, GoogleSearchWidget searchWidget id: “sbtc” and GoogleSearchResults id = “rso”. Just curious whether these are real ids or you mocked or made them up.

      1. I think what is causing the confusion to Chong is that the ids used seem to be no longer available for all of the components (e.g. GoogleSearchWidget searchWidget is no longer identified by id “sbtc”, but GoogleSearchResults results is still identified as “rso”). Probably by the time you wrote this code they were the correct ids, but if you run this code without updating the locators, the execution would fail.

  7. Hi Vinoth,

    Can i use Graphene alone without Drone? I would like to have the driver control to myself and Use the auto page injection of Graphene alone?

    Is that possible?

Leave a Reply

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

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