Javascript Page Object Pattern for Sahi

May 26, 2012

I've been reading about the virtues of the Page Object design pattern and am intrigued by the benefits it could offer. I found examples of it's use in many of the popular automation tools like Selenium, Watir and WatiN, but very little information about a Javascript implementation, or it being used in Sahi (my current automation tool of choice). Thus I was forced to have a go at it myself.

First off, a Page Object is a design pattern intended to create a model of a web page in code via a object or class. If you've written even a bit of automation code, you'll recognize the amount of chaff created to accommodate your ultimate test goal; clicking through ui elements, repetitive steps, etc.... a Page Object attempts to abstract those clicks and steps and allow for more elegant and maintainable test code. Therefore, for each page in your web application, you'd have a corresponding object that would provide the services offered by that page to your test.

An example

Here is my take at a Page Object pattern for Sahi, written in Javascript (though Sahi does use a "$" in front of it's vars to differentiate them from page Javascript vars). In my example, I've created three objects; a home page, a search results page and a shared, common object page (that might exist on multiple pages). You can copy this code and run it as is in Sahi Pro (the open source version currently has a bug that will make this fail. Email me and I'll explain a workaround).

// page object via object literal...
var GoogleHome = {
    // you need the $ if passing to Sahi methods...
    $url : "http://google.com",
    $textbox : "q",
    $submitButton : "Google Search",
    $title : "Google",

    isPage : function() {
        \_assertEqual(this.$title, \_title());
    },
    visit : function() {
        \_navigateTo(this.$url);
        this.isPage(); // verify we're on the right page...
    },
    searchFor : function($searchText) {
        \_setValue(\_textbox(this.$textbox), $searchText);
        \_click(\_submit(this.$submitButton));
    }
};

// results page object...
var GoogleResults = {
    $title : new RegExp(".\*Google"),
    SearchFilter : new SearchFilter, // include common page element...

    getFirstResult : function() {
        \_alert(\_getText(\_link(0, \_in(\_div("ires")))));
    }
};

// share a common page element between page objects via constructor...
function SearchFilter() {
    // this. makes the method public...
    this.filterBy = function($filterName) {
        \_click(\_link($filterName, \_in(\_div("leftnav"))));
    }
};
// test...
GoogleHome.visit();
GoogleHome.searchFor("javascript page object pattern");

GoogleResults.getFirstResult();
GoogleResults.SearchFilter.filterBy("Images");

As you can see, in addition to the cleaner testing code, Page Objects also offer a bit of encapsulation, not to mention the ability to define a domain language for your tests.

I decided to go with object literals for the page objects as they take care of instantiation (no need to use new to create an object) and they are easy to use and read. One downside is they can't currently take advantage of inheritance but they seem to do the trick nonetheless.

Any items shared across multiple pages (nav bar, sidebar, etc...) should be broken out into their own objects and instantiated in the various page objects that make use of them (like SeachFilter in my example).

As I'm new to this concept, please do feel free to email me and let me know how this code might be improved. I'd be happy to have the input.

UPDATE: I've since added an updated and expanded example with working code:

Don't Drive Angry...

May 22, 2012

My last couple of iterations have been challenging, to say the least. It's early on in a new project with some big changes and we took on some stories that were, quite frankly, too large. And while they probably could/should have been broken down into smaller chunks, the team decided to take them on. Not surprisingly (in glorious hindsight), we all had a turn dealing with their frustrating vastness, and passed on the displeasure with each handoff. Luckily (sarcasm), with QA partaking in each facet of the process, and ultimately being the caboose, I got to enjoy the punishment of each facet--ba, dev and qa--end to end, as we each took a turn driving the angry train. Good times...

The last couple of retrospectives have been--shall we also say--less than enjoyable. There's a reason finger pointing is NOT an olympic sport.

But the worst part was that I also got upset with myself. During said turmoil, and due to the number of changes (or so I thought), a number of my scripts started to fail... but because of the pressure to get these titanic stories done, I let them continue failing while I got caught up. Of course ONE of these scripts was failing because of a bug... but I wouldn't find this out until an iteration later. Sadness...

The moral of the story isn't really don't drive angry, though this too isn't bad advice... it's to NOT let your automation fail... no matter what! We write automation to find bugs faster and to police change. Letting tests fail defeats both of these tasks and diminishes faith in the tests themselves. This is just foolishness... the sky may be falling but that's not a reason to leave the skylight open! Bad QA; no biscuit.

Accessing Browser Objects Using Sahi

Apr 22, 2012

Sahi is a great automation tool for testing web applications. While Similar to Selenium, Sahi offers many notable advantages over Selenium, including some that were deal breakers in my current situation (IE support for instance).

Sahi also offers some ways to access page objects that are just flat out awesome. I'll discuss a few here, specifically strings, indexes and it's built-in Browser Accessor API _under_.

Consider the following HTML table...

Last Name First Name
Deng Luol
Rose Derrick
Gibson Taj

Suppose you had a story that called for you to click a link from the table above. Simple enough... in Sahi you could write something like this:

_click(_link("Deng")); // opens Luol's Wikipedia page

Yup. This code does indeed open Lu's Wikipedia page as expected. It's using the link text to access the page's object (not an xpath). Handy for sure but what if you had other sites to test that contained different data in this same table (hopefully not any Miami Heat Players)? Or maybe these links are commonly deleted by other scripts you're running? The code above would fail.

No problem. Sahi indexes all page objects which allows you to access the "nth something" on the page. In this example, we could just click the first link. And in case there are other links elsewhere above, we'll be more specific and make use of one of Sahi's fantastic relational accessors, _under_. So...

_click(_link(0, _under(_tableHeader("Last Name"))));

Lu's Wikipedia page again appears! What's happening here is we're clicking the first link (indexes are zero-based a thus start with zero) under the table header "Last Name". Thanks to the indexing of the links, we could also iterate through all of the links via a simple for loop.

But now maybe we'd like to take this up a notch and inject some randomness that will help broaden our testing scope and perhaps improve our chances of finding a bug (the purpose of testing after all). And maybe we'll also make it a function to help keep our automation more readable. So perhaps something like...

function clickRandomPlayerLink() {
    // _count's index is NOT zero-based; starts at 1
    var $numPlayers = _count("_cell", "lastname")-1;
    // get random number between 0-$numPlayers (2)
    var $playerIndex = _random($numPlayers);
    _click(_link($playerIndex, _under(_tableHeader("Last Name"))));
}

This function uses Sahi's _count method to count the number of cells that have an id of lastname. (Note: because the results of _count are NOT zero-based (starts with 1) it returns "3" so we just subtract 1. We then use Sahi's method _random to get a random number between 0 and $numPlayers (2). Now we call the same code as in our previous example to click one of the player's links at random, under the table header "Last Name".

And now any one of my Bulls player's Wikipedia pages is displayed when running the script! These are just a few of the ways Sahi lets you get at page objects... there's a bunch more including _leftOf and_rightOf`, new to Sahi Pro 4.2.

It Takes a Troop of Monkeys...

Mar 29, 2012

I doubt they think it takes a whole troop!In the recent uTest blog post, Gerald Weinberg hit an old nerve of mine...

"To me, the biggest weakness [in the way companies test software] is not considering software testing anything but a (barely) necessary evil. Testing is seen as something that could be done by a troop of monkeys, so serious testers are treated like third-class individuals. The lack of means of acquiring testing skills arises from this attitude, as do most of the other poor practices in the testing business. You treat people as if they are stupid, then they will wind up acting stupid."

This truth is something that I've dealt with my entire career... and has been on my mind lately.

Can Not Reproduce

Mar 20, 2012

This is my favorite xkcd comic and one (arguably) appropriate for this site...

Bret Victor - Inventing on Principle

Mar 11, 2012

I found this video via Slashdot and after watching it, read some of the comments... which hilariously missed the scope of the presentation. I post it here because it reminds me... that if you abolish boxes, there's no need to think outside of them.

Testing James Bach

Feb 10, 2012

I'm watching a talk by James Bach (who can impressively talk over heavy nose-breathers) in which he claims he has a google alert for blogs with the terms exploratory testing. I am now testing this claim.