Page Abstractions enhance the way your tests are written. They make your tests more robust and therefore more reliable.
To see their power let's start with little bit of motivation. Do you often see Graphene (or Selenium Webdriver) tests like this:
UglyGrapheneTests.java
public class TestAutocompleteWidgets extends AbstractGrapheneTest {
private final By firstAutocompleteInput =
By.css("input[type=\"text\"]:nth-of-type(1)");
private final By secondAutocompleteInput =
By.css("input[type=\"text\"]:nth-of-type(2)");
private final By thirdAutocompleteInput =
By.css("input[type=\"text\"]:nth-of-type(3)");
private final By suggestion =
By.className("rf-au-itm");
@Test
public void testFirstAutocomplete() {
WebElement autocomplete = driver.findElement(firstAutocompleteInput);
autocomplete.sendKeys("Ar");
waitForSuggestions();
List<WebElement> suggestions = driver.findElements(suggestion);
assertEquals(suggestions.size(), 2,
"There should be two suggestions!");
assertEquals(suggestions.get(0).getText(), "Arizona",
"The first suggestion is wrong!");
assertEquals(suggestion.get(1).getText(), "Arkansas",
"The second suggestion is wrong!");
}
@Test
public void testSecondAutocomplete() {
//other similar interactions with the other two Autocomplete widgets
}
private waitForSuggestions() {
//some wait method to wait for suggestions becoming visible
}
}
In the previous example you can notice that tests are tightly coupled with the HTML structure of the tested page. So once the structure of the page will change (it can be even slight change in class attribute of some element) tests are affected as well. These small changes can effectively break lot of tests, in other words increases the time spent on the tests maintenance.
There comes Page Objects pattern to improve this.
Basically it is an encapsulation of the tested page structure into one class, which will contain all the page fragments (parts, components, widgets) together with all handy methods which you will find useful while testing the encapsulated page.
Very simple Page Object which results from the previous test example is below:
TestingPage.java
public class TestPage {
@FindBy(css = "input[type=\"text\"]:nth-of-type(1)")
private WebElement firstAutocompleteInput;
@FindBy(css = "input[type=\"text\"]:nth-of-type(2)")
private WebElement secondAutocompleteInput;
@FindBy(css = "input[type=\"text\"]:nth-of-type(3)")
private WebElement thirdAutocompleteInput;
@FindBy(className = "rf-au-itm")
private List<WebElement> suggestions;
//getters for all fields of this Page Object
private waitForSuggestions() {
//some wait method to wait for suggestions becoming visible
}
//other handy methods used with testing of the page
}
The test can be now decoupled from the underlying HTML structure of the tested page. Once the structure change, the only modification will be needed in Page Object. The test would look like below snippet:
NicerGrapheneTests.java
public class TestAutocompleteWidgets extends AbstractGrapheneTest {
private TestingPage testingPage;
@Before
public void initializeTestingPage() {
testingPage = PageFactory.initElements(driver, TestingPage.class);
}
@Test
public void testFirstAutocomplete() {
testingPage.getFirstAutocompleteInput().sendKeys("Ar");
testingPage.waitForSuggestions();
List<WebElement> suggestions = testingPage.getSuggestions();
assertEquals(suggestions.size(), 2,
"There should be two suggestions!");
assertEquals(suggestions.get(0).getText(), "Arizona",
"The first suggestion is wrong!");
assertEquals(suggestion.get(1).getText(), "Arkansas",
"The second suggestion is wrong!");
}
@Test
public void testSecondAutocomplete() {
//other similar interactions with the other two Autocomplete widgets
}
}
To read more about how Graphene helps with utilization of this concept, please follow the child page Page Objects.
Page Objects pattern are well known concept, which greatly improves tests robustness. However, is there more space for improvement ? Consider previous example, where we were testing three autocomplete widgets. Each of the test had to deal with the interaction between driver and that component on its own. Unfortunately not only tests in that one test class, but all the tests which interact with the same autocomplete widget implemented with the same UI framework. It is a huge DRY violation.
Therefore Graphene introduces a new concept, called Page Fragments to improve this.
What are Page Fragments in short ?
-
Page Fragments stands for any part of the tested page, any widget, web component, etc.
-
A concept of encapsulation of these parts into completely reusable pieces across all your tests.
-
Powerful mechanism for creating own page fragments, like Autocomplete (Calendar, Login, etc.) page fragment.
-
A concept which differentiate each fragment by its root element and make other parts referenced from it.
-
A solution which leverages Selenium WebDriver under the hood together with all Graphene killer features.
-
Set of utilities which simplify using of this feature in tests, together with better support for Page Objects pattern.
So we already know that autocomplete widget from the previous example can be encapsulated into one object. As it is part of the page, its fragment, let's call that object Page Fragment. Better than words, let's see an example of such encapsulation below.
AutocompleteFragment.java
public class AutocompleteFragment<T> {
@Root
WebElement root;
@FindBy(css = "input[type='text']")
WebElement inputToWrite;
public static final String CLASS_NAME_SUGGESTION = "rf-au-itm";
public List<Suggestion<T>> getAllSuggestions(SuggestionParser<T> parser) {
List<Suggestion<T>> allSugg = new ArrayList<Suggestion<T>>();
if (areSuggestionsAvailable()) {
WebElement rightSuggList = getRightSuggestionList();
List<WebElement> suggestions =
rightSuggList.findElements(
By.className(CLASS_NAME_SUGGESTION));
for (WebElement suggestion : suggestions) {
allSugg.add(parser.parse(suggestion));
}
}
return allSugg;
}
public List<Suggestion<T>> type(String value, SuggestionParser<T> parser) {
List<Suggestion<T>> suggestions = new ArrayList<Suggestion<T>>();
inputToWrite.sendKeys(value);
try {
waitForSuggestions();
} catch (TimeoutException ex) {
// no suggestions available
return suggestions;
}
suggestions = getAllSuggestions(parser);
return suggestions;
}
//other handy encapsulation of Autocomplete services
}
It is nothing special. The only difference between Page Objects and Page Fragments is the element annotated with the @Root annotation. All other WebElement fields annotated with @FindBy are referenced from that root element. It makes such implementation pretty generic and reusable across all tests which need to interact with the encapsulated Page Fragment.
The @Root annotation is optional, you typically use it when you need to directly invoke methods on it in your fragment's code. Therefore, you do not need to declare such element. Graphene will take care of it. You denote whether it is Page Fragment or Page Object in the way you use it (a particular Page Object is annotated with @Page, a Page Fragment with @FindBy annotation).
To introduce Page Fragments into previous test example, one need to do for example following:
-
Move autocomplete specific methods from TestingPage to the AutocompleteFragment<T> implementation, so they can be reused in other tests for different applications or pages too.
-
Declare Page Fragments into Page Object (TestingPage, preferred option) or directly into the tests (this again couples tests with the structure of the testing page, less preffered).
-
Rewrite Page Object methods so they will interact with the Page Fragments instead of plain WebElements.
Following snippet shows that:
ImprovedTestingPage.java
public class TestPage {
@FindBy(css = "div[class=\"rf-au\"]:nth-of-type(1)")
private AutocompleteFragment<String> autocomplete1;
@FindBy(css = "div[class=\"rf-au\"]:nth-of-type(2)")
private AutocompleteFragment<String> autocomplete2;
@FindBy(css = "div[class=\"rf-au\"]:nth-of-type(3)")
private AutocompleteFragment<String> autocomplete3;
// getters for all fields of this Page Object
// other handy methods used with testing
// of the page now using the methods called from Page Fragments
}
For more information about how Page Fragments are declared, initialized and more, please continue with Page Fragments.