Full code and additional information can be found in my github repository: Selenium Tumblr Test code
I created some Java classes to test a modern website use-case. For this case, I used Tumblr. Tumblr is a simple, but dynamically updated website (i.e. page elements update, but the webpage doesn’t necessarily reload with each change). So not only do you need to account for the actions of buttons and fields on the webpage, but also information on the page that is dynamically updated. In developing these Selenium Tumblr tests, I used several different design patterns:
PageObjects:
See: Selenium PageObjects
PageObjects are a way to model how you interact with a website. Each PageObject encapsulates services implemented by a particular webpage. By encapsulating functionality into objects, you contain all interaction within one location. If the website’s functionality later changes, then you only need to update the functionality in one place.
BaseObject:
See: Using a Base Page Object
The BaseObject encapsulates utility functions and functionality common to each PageObject needed to interact with the website. The subsequent derived objects, LoginPage and DashboardPage in this case, are then pretty simple to implement. Beyond the constructor, they only need fields defined and functions implemented.
LoadableComponent:
See: Selenium Loadable Component
LoadableComponent is a class that provides a standard way for a webpage to be loaded and verified. By deriving the base object class from LoadableComponent, you ensure that each page gets loaded and verified the same way. The only thing that changes between each PageObject is its URL and Title. By linking PageObjects together, you also gain the ability to sequence navigation events between the PageObjects. This navigation sequencing results in a great reduction of code in both your PageObjects and your tests.
Here is a skeleton of the BasePage, LoginPage, and DashboardPage classes:
public class BasePage> extends LoadableComponent { private String pageURL; private String pageTitle; protected WebDriver driver = null; protected LoadableComponent<?> parent = null; // Overridden LoadableComponent function. Make sure that we're on the // page that we expect to be on. @Override protected void isLoaded() throws Error { try{ Assert.assertTrue("Not on correct page", getPageTitle().equals(driver.getTitle())); }catch(NoSuchElementException ex){ throw new AssertionError(); } } // Overridden LoadableComponent function. First, tell the parent object // to load its page, then load our own page. @Override protected void load() { // if parent present, load it first if (parent != null) parent.get(); // then load page URL driver.get( getPageURL() ); } // getter/setter functions for pageURL and pageTitle // ... } public class LoginPage extends BasePage { ... } public class DashboardPageextends BasePage { ... }
Given that you had to override LoadableComponent’s isLoaded() and load() functions, LoadableComponent calls those functions in its “get()” function:
public T get() { try { isLoaded(); return (T) this; } catch (Error e) { load(); } isLoaded(); return (T) this; }
So when you want to navigate to a PageObject’s webpage, you call LoadableComponent’s get() function. Notice that the first line of BasePage’s load() function first tries to call its parent’s get() function. That way, if a PageObject has a parent object defined (e.g. a DashboardPage object has a LoginPage object as a parent object), the parent’s navigation happens before the child object’s navigation (e.g. LoginPage navigates to its page followed by DashboardPage’s navigating to its page).
Utilities:
The BasePage contains utilities that wait for:
– elements to be visible
– elements to be clickable
– elements to disappear
– pages to be reloaded
– and alert popups to be handled.
These are specific to needs for Tumblr. They are implemented using Selenium’s Wait interface, page source retrieval, and alert exception handling functionality.
Results:
The results of using these patterns, classes, and functions are:
1) BasePage derived PageObjects are simple to implement. You define the fields and functions. LoadableComponent’s load() function may be overridden if additional functionality is needed beyond loading the page (e.g. such as entering username, password, and clicking login button):
// setup page URL & title in constructor setPageURL("http://www.tumblr.com/dashboard"); // BasePage LoadableComponent.load() fxn will load this URL setPageTitle("Tumblr"); // BasePage LoadableComponent.isLoaded() fxn will check current page title against this // setup field definitions @FindBy(id="new_post_label_text") private WebElement newTextButton; /* * optional - uncomment & override if you need additional * functionality after page is loaded @Override protected void load() { super.load(); // first call BasePage.load(); // any other calls you need after page is loaded // e.g. fill in fields, click buttons, etc. } */ // implement functions - using fields defined earlier public void addNewText(String title, String body) { // click new text newTextButton.click(); // et cetera }
2) Writing tests that first need to navigate through several pages (like login or configuration pages) is simple. You can then move on to the functionality that you want to test:
// create webdriver and pages you want. Navigate to last page. WebDriver driver = new FirefoxDriver(); LoginPage loginPage = new LoginPage(driver, null, username, password); DashboardPage dashboardPage = new DashboardPage(driver, loginPage); dashboardPage.get(); // now, call any functions you want to test dashboardPage.addNewText("Some Title", "Some Text"); // cleanup at the end loginPage.logout();