[Part of a series of posts on Java Selenium RC patterns I find useful.]
What we Want: Expressive, Succinct Tests
Let’s say I want to test that I can log into an app. I want my Selenium RC Java test code to look something like this, because I want it to read, aloud, the way I would describe actually logging in and checking that everything went mostly OK:
|
1 2 3 4 5 6 7 8 |
@Test public void canLoginToFatFreeCRM() throws Exception { LoginPage loginPage = new LoginPage(); DashBoard homePage = loginPage.login(BrowserDriver.DEFAULT_USERNAME, BrowserDriver.DEFAULT_PASSWORD); assertTrue(homePage.isLoaded()); } |
Several techniques make this possible, but the one we will focus on in this blog post is the notion of a self-verifying PageObject, which is essentially a “testable representation” of an actual web app page we wish to traverse, manipulate, and test. So my LoginPage class, above, allows me succinct manipulation of the corresponding login page in the system under test.
Note that this test knows nothing about Selenium. Indeed, neither does the LoginPage class (below). More on that later. Note that this test does not include any waitUntilPageIsFrickinLoaded() calls, nor (worse) Thread.sleep() code, and very little direct instantiation of PageObjects. The page flow mechanics is encapsulated elsewhere. Note also that as I go from page to page, I don’t explicitly assert that I got there. I shouldn’t have to. My test should be able to take that for granted. It turns out that assertions are being made under the covers about having arrived successfully on intermediate pages, but that code is abstracted away. That’s what this post covers.
The only useful assertion in the test proper is that, once I have logged in with proper credentials, I arrive safely at the homepage (and even this assertion in the test is redundant, and included here only to reveal intention; the DashBoard page is also self-verifying).
Glance-Readability
A test as short and expressive as the one above passes the “glance readability” test. Anyone familiar with automated testing semantics, and the biz language of this CRM domain, ought to be able to grasp what this test does at a single glance.
Suffice to say, we want our Selenium RC tests, as much as they can, to be this succinct. And, again, that requires that they stick to expressing page traversal and manipulation in the local biz-specific language of the web app’s business domain. So, finally, let’s see the under-the-covers page self-verification mechanics.
PageObjects as I Use Them
The selenium-rc-patterns sample Java codebase that illustrates all of the patterns in this series of posts talks to a localhost copy of a Rails project called Fat Free CRM. If you want to learn a bit about this app, you can play with a hosted sample version here (you have to sign up for an account first, which is easy).
This CRM system has discrete web pages like a Dashboard (home page, essentially), and pages for Tasks, Leads, Campaigns, Contacts, Accounts, and Opportunities. For each of these actual, production pages, my selenium-rc-patterns Eclipse project contains a matching PageObject with methods and fields that provide access to the services on that page.
Above, when you ask an instance of a LoginPage to login(username, password), then the TextFields and PageLink type know how to return us a PageObject that should then be cast to a DashboardPage and returned. (More on that in another post.)
Selenium RC developers have been using a PageObject pattern for awhile. The pattern dates back, at least, to the original HTMLUnit, whose API rather strongly encourages you to represent your pages under test as extensions of what it calls an HtmlPage. I don’t use Se 2 yet (might someday, might not), so I don’t use its PageFactory pattern.
Instead, I use my own PageObjects that extend a BasePage (or BasePane), partly so that I can control how and when PageObjects are instantiated, and verify automatically and ambiently on instantiation that Selenium is indeed on that page. Again, that’s much of what keeps the above test code so simple: I’m not explicitly waiting for a page to load, and I’m not asserting all over the place that I’ve successfully arrived on a given page.
So, my LoginPage class has a login() method that accepts a username and password (we’ll cover the return types of these fields and methods in another post that describes how page-flow works):
|
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 |
package bizdomain.pages; import util.BasePane; import util.elements.PageLink; import util.elements.TextField; public class LoginPage extends BasePane { public static final String PAGE_IS_LOADED_CSS = "input[id=authentication_username]"; private TextField userNameField; private TextField passwordField; private PageLink<DashBoard> loginButton; public LoginPage() { super(); userNameField = new TextField("input[id=authentication_username]"); passwordField = new TextField("input[id=authentication_password]"); loginButton = PageLink.create("input[id=authentication_submit]", DashBoard.class); } @Override public String getPageLoadedCssLocator() { return PAGE_IS_LOADED_CSS; } public DashBoard login(String userName, String password) { userNameField.enter(userName); passwordField.enter(password); return loginButton.clickToNewPage(); } } |
Verifying That the Production Page has Been Loaded
The real point of my flavor of PageObject pattern is the self-verifying bit. Let’s dive into that — not later, but now.
Note that each of these PageObjects extends BasePane, and has a PAGE_IS_LOADED_CSS constant:
|
1 2 3 |
package bizdomain.pages; public class LoginPage extends BasePane { public static final String PAGE_IS_LOADED_CSS = "input[id=authentication_username]"; |
In the LoginPage constructor way above up there, you can see we first explicitly call super() on BasePane. Here is BasePane:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public abstract class BasePane { public BasePane() { waitUntilLoaded(); } public final boolean isLoaded() { return BrowserDriver.isElementPresent(getPageLoadedCssLocator()); } public final boolean isVisible() { return BrowserDriver.isElementVisible(getPageLoadedCssLocator()); } public final void waitUntilLoaded() { BrowserDriver.waitForElementVisible(getPageLoadedCssLocator()); } public abstract String getPageLoadedCssLocator(); } |
You can see that this constructor calls waitUntilLoaded(), which makes a static call to a method on our BrowserDriver (the Facade / Decorator that handles all of the actual Selenium calls) in order to loop until our LoginPage (in this case) actually is loaded. The argument supplied is the result of calling getPageLoadedCssLocator().
But wait! That’s an abstract method! So Yes, we have something very like a template method pattern here in waitUntilLoaded(): the concrete implementation of getPageLoadedCssLocator() on LoginPage returns that PAGE_IS_LOADED_CSS constant String.
Deep, deep under the covers, the BrowserDriver.waitForElementIsVisible() method looks like this:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public static void waitForElementVisible(String cssLocator) { for (int second = 0;; second++) { if (second >= Integer.valueOf(BrowserDriver.STANDARD_DHTML_LOAD_WAIT_ TIME) / MS_PER_SECOND) fail("Timeout waiting for element " + cssLocator + " to become visible."); if (isElementVisible(cssLocator)) break; sleepForASecond(); } } public static boolean isElementVisible(String cssLocator) { injectJqueryIfAbsent(); return executeJavascript(JqueryCodeFactory.getVisibilityCode(cssLocator)).equals(TRUE); } |
We’ll discuss the BrowerDriver class at length elsewhere. And we’ll also discuss the injectJqueryIfAbsent() method, which we hope an upcoming Selenium RC release will obviate.
The upshot of the waitForElementVisible() method and the LoginPage and BasePane code above is that the LoginPage object will not successfully finish instantiating until Selenium can successfully verify that a unique element on that page has been loaded. In other words, once a LoginPage instance is loaded in memory, we actually are on the LoginPage, by definition, as long as that CSS element selector syntax is correct. Voila, automatic, ambient page flow assertion.
This is much of how we get our test methods so succinct. The PageObjects take care of verifying for us, at instantiation, that we have safely arrived on their corresponding production app web pages.
Caveat Lector: there is a flaw in my code I have yet to squeeze out: duplication between a BasePane, used above, which I usually use for dynamic changes in the HTML, and a BasePage, which presumes that a real HTTP Request/Response cycle has occurred. I will collapse those two together shortly.
Next: we’ll talk about reusable HTML Element objects, and how the linkish ones know, in my code, how to return the PageObject we wish to traverse to next.
[...] Self-Verifying Page Objects is Patrick Wilson-Welsh’s first post in the series patterns that made up his and Dawn‘s Agile 2010 session. [...]
First off, great post! I love how clean and readable you’ve made the test methods look with this design pattern.
Just a concern… Does the JS executed by calling JqueryCodeFactory.getVisibilityCode() contain logic to wait for the page’s DOM to be fully loaded or is this verified elsewhere?
I am not aware of what strategy you use to select a page element to be referred to by the constant String PAGE_IS_LOADED_CSS (maybe it’s simply chosen arbitrarily) but it’s my understanding that the element could become available before the page’s DOM object is fully loaded.
The concern (which I state in case it’s incorrect) is that one might perform an action on a page element through a selenium command before its event handlers have been properly initialized thus not allowing the page to behave in the expected dynamic manner.
Hi rrusso:
Thanks!
Concerning your concern: Nope. I naively have presumed that jQuery will not attempt a visibility call until DOM is fully loaded. But it shouldn’t matter in my code. I still presume that jQuery will not report that an element is visible in a partially-loaded DOM, which is why my jQuery visibility calls are in a loop that awaits jQuery reporting true. Do you know if that’s correct? Or are you saying that jQuery will report an element is visible before all of that element’s handlers and other guts are available? That would be a problem.
In that case, I would need to check the full availability of the element, instead of just its visibility.
Thanks for the heads up!
–Patrick
Nice post.The only different thing I would do is to move out the locators to a separate file(like properties or xml).Maintenance becomes much more easier.However I agree with the crux of this post about self verifying page.The only roadblock I have hit so far is how to make this pattern more design driven rather than individual driven as when multiple people are writing the tests it becomes that much harder to enforce.
Page Object Model Resources…
The ‘Page Object Model’ is one way to organise the scraping side of automated web testing. The following are a number of useful resources explaining the concept. Wade Catron’s presentation on “Pageness” in Selenium testing at LinkedIn Part 1:…
Testing with page objects…
Page Objects The UI of a DataIgnition application can be divided into separate areas, e.g. the login page, the job dashboard page, the tag creation page. Page objects are test objects that model these areas within the test code…….
Functional testing using page objects…
Introduction Some of the functional testing in DataIgnition is done using automated tests which use a third party “web driver” program (e.g. Selenium) to manipulate a real browser and through that interact with our application’s UI…….
[...] inspiration for this post is Patrick Welsh’s original post as well as code about the Self-Verifying pages in Selenium RC. While the actual pattern is very well explained in Patrick’s post-I thought I might share [...]
[...] Wilson Welsh provides a good example of how applying a simple convention to your Page Objects can further improve readability of your [...]