woensdag 12 november 2008

Supporting custom elements with WatiN, Part II

12 Nov 2008: Updated section about creating an extension method.

In part I of this series I explained why you should create wrappers and how you could create a basic wrapper for your custom or third party controls. In this post I will show you

  • how to make use of the deferred find execution
  • how to use extension methods to make it all look integrated with WatiN

What is Deferred Find Execution

Deferred Find Execution in WatiN means that the search for an element in the current web page is done the first time you get a property value, set a property value or call a method on that element. An example:

using(IE ie = new IE())
{
TextField name = ie.TextField("name");

ie.GoTo(new Uri(Util.HtmlTestBaseURI, "EnterYourName.htm"));
name.TypeText("Jeroen");
}

The first line will open a new Internet Explorer instance showing you an empty page (about:blank). Next we set the name variable to point to an input element of type text with the id 'name'. If WatiN did an immediate lookup it would throw an ElementNotFound exception cause these elements aren't on the page shown in the browser. But WatiN doesn't throw an exception, it actually returns an instance of TextField for which the find isn't executed yet. Then the browser navigates to a page with this name element on it. When calling TypeText on the TextField instance, WatiN will search for (and find) the input element with id 'name' before executing the TypeText action. So finding of the name element is deferred until the code actually needs a reference to that element.

WatiN heavily relies on this behavior so it seems like a good idea to play with the rules.

Implementing deferred find execution

To make this work for the DateTimeElement an element finder class needs to be passed to the base class of DateTimeElement. Lets replace the current constructor, which accepts a Table instance, with one that calls the base constructor accepting an INativeElementFinder instance. For readability I created a private method CreateElementFinder which does the actual creation of the INativeElementFinder instance. The base constructor also needs a DomContainer instance which can be provided by our IE instance in our test code. Here is the reviced code for DateTimeElement:

public class DateTimeElement : Table
{
public DateTimeElement(DomContainer domContainer, string id) :
base(domContainer, CreateElementFinder(domContainer, id)) { }

public void TypeDateTime(DateTime value)
{
TextField(new Regex("^date$")).TypeText(value.ToString("dd-MM-yyyy"));
TextField(new Regex("^time$")).TypeText(value.ToString("hh:mm:ss"));
}

private static INativeElementFinder CreateElementFinder(DomContainer domContainer,
string id)
{
return domContainer.NativeBrowser.CreateElementFinder(ElementTags,
Find.ById(id),
domContainer);
}
}

And here is how the code in our test looks like:

DateTimeElement element = new DateTimeElement(ie, "datetime2");
element.TypeDateTime(new DateTime(2008, 11, 1, 12, 00, 00));

All in all a small code change for a big change in behavior.

Introducing CustomElementFinderHelper

In the above code I choose to create the ElementFinder instance inside the DateTimeElement class. I did this because my focus was on introducing deferred find execution (always make small steps). But if we are going to create more wrappers for our custom or third party controls, this CreateElementFinder method seems to be needed in all of them. So lets do some refactoring and move this method into a new class which will be passed into the constructor of DateTimeElement. Here is the new class CustomElementFinderHelper:

public class CustomElementFinderHelper
{
private readonly DomContainer _domContainer;
private readonly BaseConstraint _constraint;

public CustomElementFinderHelper(DomContainer domContainer,
BaseConstraint constraint)
{
_domContainer = domContainer;
_constraint = constraint;
}

public DomContainer DomContainer
{
get { return _domContainer; }
}

public INativeElementFinder CreateElementFinder(ArrayList supportedTags)
{
return _domContainer.NativeBrowser.CreateElementFinder(supportedTags,
_constraint,
_domContainer);
}
}

And this is our cleaned up DateTimeElement code:

public class DateTimeElement : Table
{
public DateTimeElement(CustomElementFinderHelper helper) :
base(helper.DomContainer, helper.CreateElementFinder(ElementTags)) { }

public void TypeDateTime(DateTime value)
{
TextField(new Regex("^date$")).TypeText(value.ToString("dd-MM-yyyy"));
TextField(new Regex("^time$")).TypeText(value.ToString("hh:mm:ss"));
}
}

And to top things of, the extension method

Our last refactoring has turned our testing code into an unreadable mess:

CustomElementFinderHelper helper = new CustomElementFinderHelper(ie, Find.ById
("datetime2"));
DateTimeElement element = new DateTimeElement(helper);
element.TypeDateTime(new DateTime(2008, 11, 1, 12, 00, 00));

The intention is completely lost and there is stuff we need to do over and over again if we use a DateTimeElement in our test automation. Again refactoring to a method will help, but wouldn't it be nice if we could use the same syntax in our tests as we use to find for instance a TextField. So we need to extend the IE class in some way.

One option would be to create a new sub class of the IE class, add a new method to find the DateTimeElement and use the sub class in all our tests.

A new and simpeler solution to get the same intellisense support and readable code in your tests, is the use of an extension method. We do have several options for our extension. We could use the IE class to extend. But a better candidate would be the DomContainer (inheritted by IE) so that this extension method is also available on the HtmlDialog class (for html popup dialogs). With our current implementation of CustomElementFinderHelper extending DomContainer seems to be the logical choice. So lets create a static DateTimeElementExtensions class with an extension method on DomContainer:

public static DateTimeElement DateTimeElement(this DomContainer domContainer,
string id)
{
CustomElementFinderHelper helper = new CustomElementFinderHelper(domContainer,
Find.ById(id));
return new DateTimeElement(helper);
}

And thanks to the magic of extension methods the code in our test now looks like:

ie.DateTimeElement("datetime2").TypeDateTime(new DateTime(2008, 11, 1, 12, 00, 00));

This refactoring cleans up the code big time and makes it look like DateTimeElement is part of the WatiN API itself. Ain't that sweat.... :-)

If your still using C# 2.0, first go to your manager and (again) explain why you need to move to C#3.0 (and it's a breeze!). If the answer (again) is NO than remove the this keyword from the method signature and you can call it like this:

DateTimeElementExtensions.DateTimeElement(ie, "datetime2").TypeDateTime(new
DateTime(2008, 11, 1, 12, 00,00));

Almost there

And now you want to access a DateTimeElement as a child from a Frame, Div or Table element..... That is a no go with the current solution.

In part III I will show you how we can fix this by making some changes to the CustomElementFinderHelper and again the magic of extension methods. But this time on one of WatiN's interfaces.

vrijdag 7 november 2008

Supporting custom elements with WatiN, part I

When using WatiN it is easy to automate simple actions on standard elements in the browser. But when you try to automate (complex) controls of control vendors like Telerik, Infragistics or even your own custom controls, things get to start ugly quickly. Wouldn't it be nice if we could automate these controls in the same way we do automate a TextField ?

In this blog post and the next one, I while show you how.

The html code to automate

Lets start small and simple. The following HTML snippet is created by a (fictitious) custom date time control. For one of our (again fictitious) tests we need to set the values of the date and time input elements.

<table id="datetime2">
    <tr>
        <td>
            <input type="hidden" id="original_date" value="01-01-2007" />
            <input type="text" id="date" value="01-01-2007" />
        </td>
        <td>
            <input type="hidden" id="original_time" value="10:00:00" />
            <input type="text" id="time" value="10:00:00" />
        </td>
    </tr>
</table>

As you can see the html for this date time control contains two input fields, one for the date and one for the time. There are also two hidden input elements containing the original values of the input fields.

The simple way

To automate setting the date and time the following code will do the job nicely.

IE ie = new IE("http://www.examples.watin.net/datetime.html");

ie.Table("datetime2").TextField(new Regex("^date$")).TypeText("01-11-2008");
ie.Table("datetime2").TextField(new Regex("^time$")).TypeText("12:00:00");

So what's wrong with this code you might wonder. It basically comes down to being code and logic that isn't reusable. To name two issues:

  • To get the correct TextField for the date or time, you need to remember (or find out the hard way) the correct regular expression. Otherwise you might end up setting the value of the hidden input element which will give you a nice exception (and then you remember... and I can tell).
  • The formatting of the date and time needs to be in this specific format for the control to work.

We could refactor this into a method, but still. WatiN offers you all kinds of objects that wrap html elements so it feels more natural for this date time control to be wrapped as well.

Basic wrapping

So lets create a wrapper. I will use Table as the base for our new element class since Table seems to be the container element for the control.

public class DateTimeElement : Table
{
    public DateTimeElement(Table table) : base(table) { }

    public void TypeDateTime(DateTime value)
    {
        TextField(new Regex("^date$")).TypeText(value.ToString("dd-MM-yyyy"));
        TextField(new Regex("^time$")).TypeText(value.ToString("hh:mm:ss"));
    }
}

We can use this new DateTimeElement class in our tests like this:

DateTimeElement element = new DateTimeElement(ie.Table("datetime2"));
element.TypeDateTime(new DateTime(2008, 11, 1, 12, 00, 00));

In my opinion the readability and intention of this code is way better then our first approach. And it also encapsulate the knowledge of the regular expression and the specific formatting of the date and time.

So are we happy now. Not completely. I think the syntax of creating the new DateTimeElement can be improved cause it feels weird to pass in a table element instance into the constructor. Another, not visible, drawback of passing in an element is that it doesn't use WatiN's deferred execution to find the table element on the page. In more complicated wrappers this might lead to unexpected behavior. For now I will spare you the details.

Stay tuned

In part II I will show you how you can get deferred find execution by providing a small helper class and making some changes to the DateTimeElement constructor.

To top things off I will use extension methods (C# 3.0 only) to give you this end result in you test code:

ie.DateTimeElement("datetime2").TypeDateTime(new DateTime(2008, 11, 1, 12, 00, 00));

Stay tuned....

Tags van Technorati:

vrijdag 31 oktober 2008

Using HttpWatch with WatiN

Combine WatiN and HttpWatch (free edition) and keep track of the performance statistics of your web site. I think it is a cool idea and will try this out for sure. Following a link with example code on how to combine both API's.

http://blog.httpwatch.com/2008/10/30/using-httpwatch-with-watin/

woensdag 15 oktober 2008

Released WatiN 1.3!

I'm happy to announce the release of WatiN 1.3!

In this release support for lamdba expressions and Linq is added (for the .Net 2.0 version and higher). New helper methods are added to the Find class, two new DailogHandlers have been added and many many other changes.

This is the last 1.X release (so no FireFox support in this release) and will also be the last release supporting .Net 1.1.

Many thanks to all who contributed to this release!

For the full release notes visit http://watin.sourceforge.net/releasenotes-1-3-0-4000.html

vrijdag 12 september 2008

Extend WatiN with Extension methods

Just a quick pointer to an article by Richard Allen. He is showing how easy it is to extend WatiN with extension methods and gives you some extensions he found valuable to use. Have a look at http://richallen.blogspot.com/2008/08/watin-ajax-and-some-extension-methods.html.

I've done some nice stuff with extension methods as well to add support for your own controls (or third party controls) "to" the WatiN API. My blog post about it is still in draft version but will be posted soon.

Another great blog post by Richard is about writing a framework to wrap WatiN and make your (functional) test code more fluent and domain specific. He calls it the WatiN Controller Framework but is looking for an other name (what about WaCoFra, kidding). For does who are interested, follow this link http://richallen.blogspot.com/2008/08/watin-controller-framework-part-i.html

donderdag 24 juli 2008

WatiN in WatirPodcast episode 4

Last week I did a Skype interview with Zeljko Filipin the host of WatirPodcast. Episode 4 is all about WatiN, how it compares to Watir, when and why I started the project, who should use WatiN and many more things are discussed. A big thanks to Zeljko for the invitation.

So go check it out at http://watirpodcast.com/4/. I hope you like it.

donderdag 17 juli 2008

WatiN and Vista tip

Since the release of Vista people experienced (and report) problems with running WatiN test on a Vista machine. A common way to get it working was to turn of the new User Account Control (UAC) feature in Vista.

James Kovacs found a (lot) easier way to make your tests work and still have UAC enabled: Just add the domain of the web site under test to the trusted sites list of Internet Explorer. This will turn Protected mode off for this site and your WatiN tests will run properly. Thanks James for sharing this info!

For more info visit http://www.jameskovacs.com/blog/RunningWatiNTestsOnVista.aspx