[Part of a series of posts on Java Selenium RC patterns I find useful.]
In the last post, we looked at self-verifying PageObjects: Java classes that are “testable representations” of a web app page, and that automatically verify for us, on instantiation, that we have indeed arrived on the actual web page we intended to.
So how do we handle page flow? I’ve handled it numerous ways in the past. I don’t like any of them anymore. Ultimately what I wanted, and now have, is something that mimics the way an actual <a href></a> takes you to a new page: classes that represent html links that, when clicked by Selenium, take you (predictably) to new PageObjects or pane objects. So the Selenium RC Java mechanics for page flow are no longer cluttering the test code, nor, in fact, the PageObjects.
Instead, I use what I call ElementObject classes. PageLink and DhtmlLink classes, in particular, behave like little Factories. When instantiated, these objects are told which PageObject to instantiate when Selenium “clicks” on the actual html links those classes represent. Whew! Confused yet? Let’s see some code, starting with a package structure, and let’s explain ElementObjects more generally.
Background: ElementObjects
Above are the ElementObject classes I use, in HTMLUnit style, to represent discrete html element varieties and their behaviors.
(Note: The introductory post in this series tells you how to get all of this code. If you have the code, explore this little object tree, starting with the abstract BaseElement class. Explore how different pages and panes use these element classes.)
Let’s tour a slice of the BaseElement tree briefly. Here is the abstract BaseElement class:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package util.elements; package util.elements; import util.browserdriver.BrowserDriver; public abstract class BaseElement { public String locator; public BaseElement(String locator) { this.locator = locator; } public final boolean isPresent() { return BrowserDriver.isElementPresent(locator); } public final boolean isVisible() { return BrowserDriver.isElementVisible(locator); } } |
So all elements, since they extend BaseElement, have locators (which are all CSS selectors, as discussed in the previous post). The isPresent() method (using the conventional Selenium isElementPresent() method), uses vanilla Selenium to reveal whether any element is indeed present in the browser’s representation of the HTML, rendered (usually) by a real HTTP Request/Response cycle. Similarly, isVisible() reveals whether an element is “visible” after any dynamic call that does NOT involve a real HTTP Request/Reponse cycle. Under the covers, isVisible() uses jQuery to check visibility in the in-memory DOM. The short answer to “Why have both?” is that isVisible() is heavier-weight and slower, but usually works when isPresent() does not. The even shorter answer is that browser manufacturers hate each other, and sometimes ignore the w3c, but they all standardize on jQuery.
So, for example, ClickableElement extends BaseElement, and adds just a predictable smidgeon of clickable-ness:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package util.elements; package util.elements; import util.browserdriver.BrowserDriver; public class ClickableElement extends BaseElement { public ClickableElement(String locator) { super(locator); } public void click() { BrowserDriver.click(locator); } } |
And further down the tree, a CheckBox gives us the ability to check a checkbox, or if it is one of those fancy graphical checkbox simulacra, we can just click() on it using the inherited method in ClickableElement. Either way, we have checkbox-looking things covered.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package util.elements; import util.browserdriver.BrowserDriver; public class CheckBox extends ClickableElement { public CheckBox(String locator) { super(locator); } public void check() { BrowserDriver.check(locator); } } |
Notice all of the static method calls to BrowserDriver.java. We’ll cover him in deep detail later, but he is our Decorator/Facade for Selenium and jQuery calls. It provides singleton access to DefaultSelenium and several of its useful methods, as well as several convenience methods we have found useful.
The point here is that only down at the ElementObject level does our framework “know” about BrowserDriver, and the nuts-and-bolts mechanics of getting Selenium (or whatever frameworks we want) to do stuff to real web page elements.
And of course the most essential point is that in our PageObjects, we can represent every HTML element as one of these types, keeping the test framework code nice and DRY.
In general, this is how the ElementObjects work. Caveat Lector: I don’t have a full, complete set of these in my sample code; I have the ones I have been using all the time. Feel free to extend this package as necessary (and to send me your contributions).
PageLinks and DhtmlLinks
Now the meat of this post: how do we get from page to page?
First let’s look at how a PageLink client uses it for a field on a PageObject. Let’s look at a test for creating a Task in FatFreeCRM:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package task; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.Random; import org.junit.Before; import org.junit.Test; import bizdomain.common.BaseWebTest; import bizdomain.common.Common; import bizdomain.pages.TasksPage; import bizdomain.panes.CreateTaskPane; import bizdomain.panes.DueTodayTaskStrip; public class CreateTaskTest extends BaseWebTest { private TasksPage tasksPage; private String taskName; @Before public void setup() { tasksPage = Common.mainNavTabsSet.tasks.clickToNewPage(); taskName = createRandomTaskName(); } @Test public void canCreateAndCompleteLunchTodayTask() throws Exception { assertTrue(tasksPage.noPendingTasksLabel.isVisible()); CreateTaskPane createTaskPane = tasksPage.createTaskLink.clickToNewContainer(); DueTodayTaskStrip taskStrip = createTaskPane.createTask("Today", "Lunch", taskName); assertFalse(tasksPage.noPendingTasksLabel.isVisible()); String expectedCategory = "Lunch"; assertTrue(taskStrip.taskNameLabel.getText().contains(taskName)); assertEquals(taskStrip.whenLabel.getText(), expectedCategory); taskStrip.clickCompleteBox(); assertFalse(taskStrip.isVisible()); } private String createRandomTaskName() { Random intGenerator = new Random(System.currentTimeMillis()); int randomInt = intGenerator.nextInt(); return "testTask" + randomInt; } } |
Note in the setUp() method that we get to a TasksPage by clicking clickToNewPage() on the tasks field on a MainNavigationTabsSet class, which looks like this:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package bizdomain.panes; import util.BasePane; import util.elements.PageLink; import bizdomain.pages.AccountsPage; import bizdomain.pages.CampaignsPage; import bizdomain.pages.ContactsPage; import bizdomain.pages.DashBoard; import bizdomain.pages.LeadsPage; import bizdomain.pages.OpportunitiesPage; import bizdomain.pages.TasksPage; public class MainNavigationTabsSet extends BasePane { public static final String PANE_IS_LOADED_CSS = "div[id=tabs]"; public PageLink dashBoard; public PageLink tasks; ... public MainNavigationTabsSet() { dashBoard = PageLink.create( PANE_IS_LOADED_CSS + " a:contains('Dashboard')", DashBoard.class); tasks = PageLink.create(PANE_IS_LOADED_CSS + " a:contains('Tasks')", TasksPage.class); ... } @Override public String getPageLoadedCssLocator() { return PANE_IS_LOADED_CSS; } |
|
1 |
} |
Note that tasks field is of type PageLink, parameterized with a class. And notice that when you instantiate a PageLink, you call a PageLink.create() method. Between these two things, we ensure that when we ask Selenium to click on that tasks link, we end up instantiating an actual TasksPage PageObject.
It’s a smidge confusing, perhaps. Bear with me. Here is the PageLink class:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package util.elements; import util.browserdriver.BrowserDriver; public class PageLink<T> extends ClickableElement { private Class<T> clazz; public PageLink(String locator, Class<T> clazz) { super(locator); this.clazz = clazz; } public T clickToNewPage() { try { click(); BrowserDriver.waitForPageToLoad( BrowserDriver.STANDARD_PAGE_LOAD_WAIT_TIME); return clazz.getConstructor().newInstance(); } catch(Exception e) { throw new RuntimeException(e); } } } |
Notice that PageLink extends ClickableElement, so whatever element a PageLink object represents, we can click on it. Yay! Notice also that when we instantiate PageLink, it expects a css locator.
Most critically and cryptically, notice all the Java generics magic, which can be confusing Java syntax to many programmers. The upshot is noisy framework code, but it keeps explicit casting out of the test code. It keeps the test code very simple indeed.
Now notice what happens when a test method calls the clickToNewPage() method: first, we click on ourselves (which will, if you look at click() in ClickableElement, get BrowserDriver to click on the locator we were instantiated with. Then the tricky bits. We call clickToNewPage(), which asks BrowserDriver.waitForPageToLoad() to let us know when the new page is done loading. Because Selenium can tell when a real HTTP Request/Response cycle is done, this will work for real pages with actual, different urls.
Then, aha! Our little Factory method uses a little lightweight Java Reflection to construct and return us an instance of that PageObject whose Class we passed in on instantiation. In MainNavigationTabsSet above, you can see that the tasks PageLink is declared and instantiated with TasksPage.class. So indeed, a self-verifying TasksPage PageObject gets constructed and handed back to test code when that test calls Common.mainNavTabsSet.tasks.clickToNewPage().
So a bit of Java design forethought gives us this ability to click on a tasks link, and proceed without further ceremony to a TasksPage. Our page flow does, in fact, much mirror the HTML and pages in the production code.
Our test code (above) is, again, succinct and expressive, and only a link to a page need know how to get there. Just like in real life.
Similar to PageLink, DhtmlLink provides us a little factory for dynamic html behaviors (clickToNewContainer()), whether they have Ajax-like server conversations, or just change the html in the browser. In this case, because we don’t have a real HTTP Request/Response cycle, we don’t call waitForPageToLoad() on Selenium. Selenium has no idea whether the “page” or “pane” we need is now visible. Our PageObjects take care of that. We just click(). Like PageLink, DhtmlLink uses much of the same Java generics gobbletygook to keep track of concrete Class types to be instantiated at our request. In this case, the base class is BasePane, not BasePage:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package util.elements; import util.BasePane; public class DhtmlLink<T> extends ClickableElement { private Class<T> clazz; public DhtmlLink(String locator, Class<T> clazz) { super(locator); this.clazz = clazz; } public T clickToNewContainer() { try { click(); return clazz.getConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } } |
Next up: what the heck is this BrowserDriver thing, and how might we use it?

Hi Patrick — thanks for putting together these posts; there is some cool stuff in here. I have a question about this part though:
Looking at the LoginPage and PageLink classes, is there an advantage in using the “dot class” object vs. not using it? Compare the code you posted to something like this:
In the LoginPage class:
…
private PageLink loginLink;
…
public LoginPage() {
…
loginLink = new PageLink(“*[id='authentication_submit']);
…
}
…
public HomePage login(String userName, String password) {
… //enter user credentials
return goLoginLink();
}
…
public HomePage goLoginLink() {
loginLink.clickToNewPage();
return new HomePage();
}
…
And in the PageLink class:
…
public void clickToNewPage() {
try {
click();
BrowserDriver.waitForPageToLoad(BrowserDriver.STANDARD_PAGE_LOAD_WAIT_TIME);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
…
Hi, Patrick, thanks for the post.
One question: how would you solve a situation where the same link behaves in different way depending on the context? Example: let’s have a reusable user selector component that may be used in different parts of the application. It lets you search for a user and then click the name to select it. At one place of the application clicking the name link moves you to another page (let’s say “user profile”), at another place it’s just one part of a wizard and when a link is clicked, another dialog is open without loading new page. So once it is PageLink and once it is DhtmlLink.
Hi Lubomir! Thanks for the question. This is a great example of how we are really testing page flows, and app behaviors, in context. The contexts themselves are what we are testing, and their behaviors, as opposed to testing HTML bits and bobs structurally. So, I would not extract all the “duplication” here in order to have only one instance, let’ say, of that link’s selector/locator.
I would treat that link as two different PageLink objects, probably each in its own PageObject or sequence of PageObjects (in the case of the wizard). That way, the biz domain test code would itself reveal that we have (at least) the two contexts to deal with, without us having to parameterize anything.