Using the LoadableComponent pattern for better Page Object handling in Selenium
This post was published on January 26, 2016I’m sure that by now, everybody who is at least remotely involved in the creation of automated user interface tests using Selenium WebDriver is aware of the Page Object pattern (read more about it here and here) for introducing modularity and reusability in their automated tests. In this post I will introduce the LoadableComponent pattern, an extension to this pattern that standardizes handling the loading and verifying loading status of these page objects.
Why use the LoadableComponent pattern?
Out of the box, Selenium WebDriver does a good job of determining whether an HTML page has been loaded by using the document.readyState property, which is part of the W3C WebDriver specification. However, this property alone is not always enough to assert that all dynamic content on your page has been fully loaded and that all elements required in your test script are present on the page. This is especially the case when your web page uses a JavaScript-heavy framework such as the popular AngularJS or KnockoutJS frameworks.
So, even when Selenium tells you the page is loaded and continues to execute the next step in your test, this step might fail because the actual element needed for that step is not yet visible, clickable or otherwise ready. You can solve this problem to an extent by using suitable wrapper methods for the standard Selenium API methods, but wouldn’t it be nice to enhance our Page Objects with a generic approach to evaluate page load status? This is where the LoadableComponent pattern comes in.
Introducing the LoadableComponent pattern
To explain the concept of the LoadableComponent pattern, we will again turn to the ParaBank application. LoadableComponent is a base class in Selenium, which means that you can simply define your Page Objects as an extension of the LoadableComponent class. So, for example, we can simply define a LoginPage object as follows:
public class LoginPage extends LoadableComponent<LoginPage> { // class implementation will be explained later }
This does nothing more than defining this class as a LoadableComponent that loads the LoginPage page.
Now, by having our Page Objects extend the LoadableComponent base class, we need to implement two new methods, load() and isLoaded() (note that in C#, these are called ExecuteLoad() and EvaluateLoadedStatus() for some reason). These methods provide the added value of using the LoadableComponent pattern. The load() method contains the code that is executed to navigate to the page, while the isLoaded() method is used to evaluate whether we are on the correct page and whether page loading has finished successfully. Using LoadableComponent, our LoginPage class now looks like this:
public class LoginPage extends LoadableComponent<LoginPage> { private WebDriver driver; public LoginPage(WebDriver driver) { this.driver = driver; driver.get("http://parabank.parasoft.com"); } @Override protected void isLoaded() throws Error { if(!PageLoad.myElementIsClickable(this.driver, By.name("username"))) { throw new Error("LoginPage was not successfully loaded"); } } @Override protected void load() { } }
Note the use of a custom wrapper boolean method myElementIsClickable() to determine whether the page has loaded successfully. It can be used as a generic way to specify which element must be present for any given Page Object in order to consider it fully loaded:
public static boolean myElementIsClickable (WebDriver driver, By by) { try { new WebDriverWait(driver, 10).until(ExpectedConditions.elementToBeClickable(by)); } catch (WebDriverException ex) { return false; } return true; }
Also, for most pages I don’t actually put code inside the body of the load() method. This is because in a typical application, most Page Objects are only accessed by means of navigation via other Page Objects instead of being accessed directly. Even for the LoginPage I have included the navigate() call in the constructor, because in this way we do not need to wait for the timeout in isLoaded() to be exceeded before the navigate() method is called from within load(). The example project I will link to at the end of this post shows you how I implemented the LoadableComponent pattern in my latest project.
So, now that we’ve implemented our Page Objects as LoadableComponents, we can use it in our tests simply by doing this:
new LoginPage(driver).get();
The get() method is implemented as follows:
public T get() { try { isLoaded(); return (T) this; } catch (Error e) { load(); } isLoaded(); return (T) this; }
In plain English: it calls isLoaded() first to see whether the page is loaded. If this is not the case, it calls load() to load the page. Afterwards, it calls isLoaded() again to see if the page is now successfully loaded.
Leveraging LoadableComponents in your tests
To ensure in your tests that your Page Objects are loaded before moving on, you can simply call get() each time a Page Object is instantiated:
@Test public void validLoginTest() { // Load login page LoginPage loginPage = new LoginPage(driver).get(); // Log in using valid credentials HomePage homePage = loginPage.correctLogin("john", "demo").get(); // Load home page and check welcome text Assert.assertEquals("Welcome text is correct","Welcome John Smith", homePage.getWelcomeString()); }
The assertion in isLoaded() will fail if MyElementIsClickable() returns false, leading to the test as a whole to fail:
Enhancing the LoadableComponent class
Finally, another interesting use of the LoadableComponent pattern is writing your own implementation of the class. In that way you can, for example, generate specific logging to the console or to an ExtentReports report in case you use it.
The source code for the original LoadableComponent implementation can be found here. A simple addition is writing the error message that is thrown by isLoaded() (which in turn is the error message thrown by the performed assertion) to the console:
public abstract class CustomLoadableComponent<T extends CustomLoadableComponent<T>> { @SuppressWarnings("unchecked") public T get() { try { isLoaded(); return (T) this; } catch (Error e) { // This is the extra line of code System.out.println("Error encountered during page load: " + e.getMessage()); load(); } isLoaded(); return (T) this; } protected abstract void load(); protected abstract void isLoaded() throws Error; }
Now you can simply define your Page Objects as an extension of the CustomLoadableComponent class:
public class LoginPage extends CustomLoadableComponent<LoginPage> { // class implementation remains the same }
When we run our tests and encounter an error, we see that the error message is written to the console:
A working Eclipse project (created using Maven) containing all of the code samples included in this post can be downloaded here. Just run the tests in the tests package to see for yourself how it works.
"