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.
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.
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.
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.
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.
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 🙂