woensdag 17 juni 2009

Introducing the Page class

When you create your first automated tests with WatiN you are happy with the results and how easy it is to create tests (at least that is what I keep hearing). Your happily copying and pasting code to find elements into new tests. While your test suite is growing rapidly, changes are made to the web pages and all of a sudden you are in a test code maintenance nightmare. You have created WET code (!= DRY). The solution: separate your test code in tests classes, page classes and control classes (more on controls in another post). So lets start mopping!

Richard Griffin, James Avery and Ayende Rahien all wrote about the problems they were having and the page model they developed. Bruce McLeod even posted the WatiN Jobsite Sample on CodePlex to show you his page model in action. Thanks to the work of Jeff Brown, WatiN 2.0 beta 1 now comes with its own page model. The basic idea of a page model is that each web page in the web site under test has a page class counterpart in the test suite. These page classes expose elements through properties and actions through methods. The result makes it much easier to create new tests and to maintain your test code when changes happen to your web site.In this post I’ll transform the Hello world example of web test automation, as shown on the WatiN homepage, into a test using such a page class.

First a new class needs to be created inheriting the WatiN.Core.Page class, lets call it GoogleSearchPage. This page class exposes properties for the searchcriteria field and the search button. After re-writing the “Hello world example” using the GoogleSearchPage class, the code looks like this (and of course you would put the GoogleSearchPage class in its own file/assembly in production code, but for this example I kept them together).

image

The Page<> method on the Browser class provides an easy way to instantiate page classes and it does inject the Browser Document into the page class. Because the GoogleSearchPage inherits the new WatiN.Core.Page class, it can access to Document property exposed by the Page class to find elements on the page.

Since the GoogleSearchPage has properties exposing the SearchCriteria textfield and the Search button, all logic to find these controls is held in one place and can be easily reused in other tests. Also notice how the test got much more readable compared to the example on the WatiN website.

Ok, so we have modeled the page with its elements, lets take it one step further. Most web pages offer users one or more actions they can perform on the page. Modeling these actions as methods on your page class would encapsulate even more logic into one place and thus making it easier to maintain when changes happen to the web page. So let me add the method SearchFor to the GoogleSearchPage. Also see how it improves the readability of the test, again.

image

At this point, when I would navigate to http://www.bing.com and use the GoogleSearchPage, nothing would alarm me that the current page can’t be automated with the GoogleSearchPage class. Instead an ElementNotFoundException would be throw when trying to find an input element with the name btnG (in the SearchCriteria property).

The new Page class offers you some ways to validate the current page before accessing elements on the page. The easiest way is to decorate a page class with the new PageAttribute. This attribute exposes a property UrlRegex which accepts a regular expression. This is used to match with the current browsers Url every time the Page.Document property is accessed. WatiN will check if the browser is still on the correct page and if not, throw a meaningful exception.

If the regular expressions are not your thing or you want to check against another aspect of your page (for instance the title), you can also override the Page.VerifyDocumentProperties method and implement your own check and not use the PageAttribute.

Wel that’s it about the new Page class in WatiN 2.0 beta 1. I hope it will help you to better structure your page model. Following our revamped Hello world example of web test automation.

image

Technorati Tags:

3 reacties:

Vinayak Kumbhakern zei

Hi Jeroen,

This is a very helpful addition to WatiN. I have been using this approach based on the blog posts that you have mentioned in here, too.

I have one question though! Would this approach work for HTML popups with controls on it, Dialog Boxes? And how many layers of popups it would support? One more post about how could we use Page class addition to model the HTMLl popups would be great.

Regards,
Vinayak

Jeff Brown zei

A coworker of mine, Kirk Woll, came up with a refinement of the page modeling idea to reduce boilerplate a tad.

Since WatiN manufactures the Page instances itself, it could just as well instantiate its own dynamic subclass of the Page generated based on declarative metadata.

public abstract class GoogleSearchPage : Page
{
[FindBy(Name = "q")]
public abstract TextField SearchCriteria { get; }

[FindBy(Name = "btnG")]
public abstract Button SearchButton { get; }
}

The idea is that WatiN would generate a subclass with an appropriate implementation of each property.

However, the syntax is still pretty awkward to use with abstract all over. With a little thought it's clear we don't even need to generate subclasses; we could populate plain old fields instead.

public class GoogleSearchPage : Page
{
[FindBy(Name = "q")]
public TextField SearchCriteria;

[FindBy(Name = "btnG")]
public Button SearchButton;
}

Fields work fine because WatiN defers the actual lookup of elements until they are accessed for the first time. There is not really much harm in instantiating a bunch of TextField and Button objects to initialize the Page instance up front.

Implementation is really straightforward: create a new FindByAttribute class that is a factory for Constraint objects and add a bit of reflection code in the Page factory method. (Note that if FindByAttribute is not sealed, WatiN users could extend the syntax with new constraint types by subclassing the attribute and overriding its factory method.)

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FindByAttribute : Attribute
{
public string Name { get; set; }

public string Id { get; set; }

public string Class { get; set; }

// etc...

public virtual Constraint CreateConstraint()
{
Constraint constraint = FindBy.Any;

if (Name != null)
constraint &= FindBy.Name(Name);

if (Id != null)
constraint &= FindBy.Id(Id);

// etc...
return constraint;
}
}

It goes without saying that the same enhancement can be applied to Control classes as well.

Jeroen zei

Hi Vinayak,

Yes this technique is also usable for html popups (HtmlDialogs in WatiN). The DialogHandlers in WatiN are already modeled a bit like this, but this new Page model doesn't support Dialog Boxes.