donderdag 15 januari 2009

Release WatiN 2.0 CTP2

I'm happy to announce the second CTP release of WatiN 2.0, offering support for both Internet Explorer and FireFox.

Changes in this release

  • Works with FireFox 3.x and FireFox 2.x (both jssh.xpi plug-ins are included in the Mozilla directory).
  • Greatly improved performance and stability when running tests with FireFox

Fixed bugs

  • Problem with setting ActiveElement in FF 3.x.
  • SF issue 1954487  Setting TextField.Value for TextArea in FireFox fails
  • SFssue 1913072  BrowserFactory.Settings.WaitForCompleteTimeOut doesn't work

Thanks to Edward Wilde for making it work with FF 3.x

Enjoy testing with WatiN!

dinsdag 13 januari 2009

WatiN roadmap update

In my last post I talked a bit about what was going on with the project. Following is an update on that post.

WatiN 2.0

Edward Wilde has made some changes to the current CTP code to support FireFox 3.x as well. This will be released within one week. Edward Thanks!

Development for the first beta is still in progress. The FireFox specific classes are added and progress is great! Still on track for a first beta in Q1. Just to give you a feel off what is already working, the following test fixtures in the WatiN.Core.UnitTests assembly do run against IE (7) and FireFox (3.1):

  • BaseElementCollectionTest
  • ButtonTests
  • CheckBoxTests
  • DocumentTests (work in progress)
  • DivTests
  • RadioButtonTests
  • SelectListTests
  • TestFieldTests

Another very interesting initiative regarding the 2.0 version is taken by Jon Dick. He started last week writing a browser implementation to run tests with HtmlUnit. This is an in memory, no GUI  browser written in Java. Combined with IKVM he already has the google test example running. Since this browser skips rendering it makes the tests run faster which is always a good thing.

I also wondered of a little bit last week and took a look at automating Chrome (the new browser by Google). One option is to use a telnet session to the browser, much like we currently do for the automation of FireFox (using the jssh plug-in). So hopefully after finishing FireFox support, Chrome support will be there very quickly.

WatiN 1.3.1

Although I have created the branch and already merged a lot of the fixes from the trunk, I still need to add the changes I made to the DialogHandlers which do type text in a dialog (like the prompt dialog, file upload dialog and file download dialog). I think it is worth waiting cause this will (finally) fix the hanging of WatiN test in a VPC or closed/minimized remote desktop session. Hope to get this done soon.

WatiN Test Recorder 2.0

Although I don't do any development on WatiN test recorder project (All credits go to Daaron Dwyer!) I know he is busy working on a 2.0 release. So if you have feature request, this is the time to contact him or spam the trackers on sourceforge :-)

dinsdag 9 december 2008

WatiN roadmap

Today I received a question on the mailing list about the status of WatiN 2.0. I thought I publish my response here as well.

No release date(s) yet, but I can report about the progress and speculate about a roadmap:

  • Currently the development code (a continuation of the 1.3 code base) is refactored to add support for testing with different browsers (IE, FireFox for now). I estimate I'm at 60% of the changes that need to be done to get the basic stuff to work.
  • Next is the integration of the firefox specific code from the 2.0 CTP version into the development code. I estimate that I will go for a first integration around the end of this month.

So my current guesstimate is that a beta or final release of 2.0 will be earliest at the end of q1 2009.

In the meantime Edward Wilde will update the current 2.0 CTP version to add support for FireFox 3.x. We haven't spoken about a release date be hopefully this refresh will be available early next year.

I will also release WatiN 1.3.1 in December with some important bug fixes.

So nothing final yet but I hope this gives you (all) a feel of what we are aiming for.

WatiN Find.ByDefault explained

My next post will be Part III of Supporting custom elements with WatiN. I got so excited about the possibilities that I started working on a wrapper for the Ra-Ajax controls as a show case. It is becoming very COOL and its fun to create as well. But as I said in my next post I'll show you more.

This post is all about a new feature of WatiN 1.3 which might not be that discoverable so I felt it needed some promotion.

Find.ByDefault

When you want to automate some action against an element on a web page, WatiN offers you different types of elements (TextField, Button, etc) to find an element. Each of these offer overloads accepting something to find the element by. For example, if you want to find a TextField by its id ("firstName_Id" in this example) you would typically have this code:

var firstNameField = ie.TextField("firstName_Id");

Which is the same as:

var firstNameField = ie.TextField(Find.ByDefault("firstName_Id"));

And with the default Settings applied, it is the same as:

var firstNameField = ie.TextField(Find.ById("firstName_Id"));

In this post I'll show you how you can change the implementation and behavior of Find.ByDefault() by creating your own default factory class and assigning it to Settings.DefaultFinderFactory .

Make Find.ByName the default

If your site makes heavy use of the name attribute on elements (instead of ids) and you use these to find the elements in your tests, you would typically write code like:

var firstNameField = ie.TextField(Find.ByName("firstName_Name"));

Lets make Find.ByName() the default so your code will look like this:

var firstNameField = ie.TextField("firstName_Name");

To make this work we need to create a new class which implements IFindByDefaultFactory. In the implementation of the ByDefault(string) method we can simply return Find.ByName(value). This is the implementation of the class:

public class FindByNameFactory : IFindByDefaultFactory
{
public BaseConstraint ByDefault(string value)
{
  return Find.ByName(value);
}
    public BaseConstraint ByDefault(Regex value)
{
  return Find.ByName(value);
}
}

The last thing we need to do is register this new factory class and Find.ByName will be the new default:

Settings.FindByDefaultFactory = new FindByNameFactory();

Handle ASP ids with

Another very useful way we could leverage this new feature is dealing with ASP.Net ids. When you automate a website which is build with ASP.Net webforms you probably have to deal with very long ids. By default ASP.Net creates unique ids by concatenating all of the ids of the container controls a control is in. For instance if a TextBox to enter a first name is placed within a tab control container and this is placed in a placeholder, the resulting id could be something like "plc_Main$tab_Details$txt_FirstName". With WatiN you have several ways to find this TextField by id.

Option 1: Using the full Id
The simplest option to find the control is to use the full id. Example:

ie.TextField("plc_Main$tab_Details$txt_FirstName").TypeText("Jeroen");

There are two disadvantages to this approach:
- It relies heavily on the ids of the controls the ASP:TextBox control is placed in. If you remove/rename or add another container to the stack, the TextField won't be found cause the id is changed.
- The longer the id gets the less readable this line of code becomes. Although you can (should in my opinion) abstract the way you find your controls by using a Page model, this still is massy.

Option 2: Using a regular expression
The second option is to find the control by using the overload on TextField which accepts a regular expression. In the following example we create a Regex which matches TextFields ending (hence the $ sign) with "txt_FirstName" :

ie.TextField(new Regex("txt_FirstName$")).TypeText("Jeroen");

Option 3: Make WatiN handle ASP ids by itself

public class FindByAspIdFactory : IFindByDefaultFactory
{
public BaseConstraint ByDefault(string value)
{
  return Find.ById(new Regex(value + "$"));
}
    public BaseConstraint ByDefault(Regex value)
{
  return Find.ById(value);
}
}

Register this new factory class:

Settings.FindByDefaultFactory = new FindByAspIdFactory();

And you can find controls with ASP ids like:

ie.TextField("txt_FirstName").TypeText("Jeroen");

Using (magic) prefixes

If you don't like to always use a regular expression, like we did in the previous example, you could implement something like the Incisif automation framework offers. They choose to put a prefix in front of the ASP id. So only if the specific prefix (a:) is in the id a lookup with a Regex will be done to find the element. Example:

ie.TextField("a:txt_FirstName").TypeText

and the Factory implementation:

public class FindByAspIdFactory : IFindByDefaultFactory
{

public BaseConstraint ByDefault(string value)
{
  if (value.StartsWith("a:")
  {
   Regex regex = new Regex(string.format("(.*\${0}$)|(.*_{0}$)", value));
   return Find.ById(regex);
  }
  return Find.ById(value);


public BaseConstraint ByDefault(Regex value)
{
  return Find.ById(value);
}
}

Other possible uses

Put the attribute you want to match with in front of the value

ie.TextField("id:txt_FirstName").TypeText
ie.TextField("name:txt_FirstName").TypeText

A ruby like notation to express a regular expression (starting and ending with a forward slash) instead of having new Regex() all over your code:

ie.TextField("/txt_FirstName$/").TypeText

And I'm sure you can come up with many more possible uses.

Enjoy!

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/