You reach a point when you realize you should be blogging about something. Today that point for me is 3 tweeterings and 2 requests for elaboration. I should just be grateful that smart people are listening to what I say (though it should encourage me to shut up quicker).
So let’s start with the twitter text:
Last night, I lay awake thinking about a problem I have at work (using Selenium for test automation), and not getting paid to lie awake. This morning I came in and posted a practical compromise on my principals that page objects are good, but should be kept as dumb as possible.
So I typed into the void, hoping it would get swallowed by the whale:
I’m coming around to more dynamic page-based selenium tests (instead of dumb page objects with locators and more complex navigation objects)
And the leviathan spat back:
@fijiaaron which you of course will blog about :)
@fijiaaron Hmm, I want to hear more about Se and dynamic page-based tests.
I knew I was in trouble, but like some QAhab, I pursued my albatross with a single minded soliloquy on Twitter:
when I say a “dumb” page object in selenium I think I mean two things: 1-static locators and actors 2-page doesn’t know how to test (assert)
so a smarter selenium page object (in my lingo) is also a test object (can assert) and is instantiated allowing for more complex behaviors.
i like the idea of page objects that aren’t necessarily test objects, just automation objects but it makes testing harder, needs exceptions
At that I was hooked, and knew it. But I didn’t quite know my own mind yet.
Which brings me here, once again dispelling doubt from the minds of you dear readers, precious and few as ye may be.
Looking back at my tweets, I realize that I inadvertantly conflate smart/dumb with dynamic/static. The two are not necessarily similar, and dynamic isn’t necessarily synonymous with “instantiated”.
A dynamic page will be aware of its particular circumstance, and will thus need to be instantiated (or have some complex logic to handle state.) So, for example, a dynamic home page object might be aware of whether a user is logged in or not, and know whether the login (or logout) button should be present.
Practically, the easiest way to do this is to instantiate a page and set the state. I don’t like to do this on the principal that test code shouldn’t need tested. If a page is just a set of accessors for element locators (and perhaps some helper methods to fill out forms, etc.), then not much can go wrong, and thus need tested.
There are two problems with this.
1. You have no way to validate the page. It will still fail (if an element isn’t present for instance) but then you’re digging around your code to find out why your test failed. The easy solution is to sprinkle your page objects with asserts. And that means letting it know about your test framework either by passing the test object to the page or having the page extend your test object. (or building an observer/notifier relationship between your test framework & page object which is again more complexity than I want.)
2. You end up reproducing business logic in the test. This is wasted effort but also error prone. I often make assumptions about functionality while testing that later prove mistaken (or get overridden because my QA tests aren’t treated as a functional spec by developers or business.)
Both of these issues become more apparent when you extend beyond the single page metaphor and start testing complex situations (like account registration and login.)
Previously, I’d liked to have simple (dumb and static) page objects, that described the page. Because philosophically, a page doesn’t know if a user is logged in. That’s application state.
But issues like whether the login link is displayed, or more significantly, if there are dynamic locators (such as a list of cart items that are identified by the order they are displayed) you end up needing state logic for the page to be valuable, or you end up building logic into your tests, and your page objects start looking like nothing better than a GUI map (which is still a decent abstraction in itself) but I’m also thinking about helper navigation objects that know how to do things like login, register, or checkout to help with setup.
A decent compromise is to have dumb page objects, but a smart “site” object that does know about assertions. Your test can then include a site object that does know how to validate (that you’re on the right page, for instance) and your page objects can still be easy to maintain.
I’ve also tried creating a site object that has page objects and helper objects to navigate and check state. The helper objects are then selenium and test aware, and the page objects are just dumb locators.
The question is whether you want a test that looks like:
// now test something
// now test something
The issue of extracting the mechanism of automation (like webdriver) or the issue of locators is another topic. Although I’ve tended to make dumb page objects that have public static strings as locators, rather than locators that know how to be manipulated. Another question is whether you want getters and setters, and in some instances (with dynamically identified locators) this is necessary.
I spent some time in the past creating page elements for buttons, links, textareas, etc. that implement “clickable”, “editable”, etc. interfaces, but decided against that for general use, because Selenium (or whatever framework is used) is the default API, and I don’t want to invalidate someone else’s knowledge with something like that. Webdriver actually does this to a significant degree, but is too awkward for my taste due to it’s generality.
Back to the issue of smart or dumb page objects, I guess I haven’t really answered the question. I’ve really only outlined what I’ve done and a few arguments and clarifications. I have found that using a dynamic language like PHP or Ruby that it is easier to have dynamic page objects, but then it requires getters and setters in order to easily determine IDE hinting, which is something that is inherently more difficult with dynamic languages. And an important part of my goal of an abstraction layer around automated tests is to make the API discoverable. That’s the key to getting users to use it, or else they will go reimplementing stuff in their own tests and you have a maintenance nightmare.