Whilst I've been working with WatiN over the past couple of months I have encountered a few issues especially that unfortunately WatiN was unable to solve straight out of the box, so I thought I would knock a post together that contains the extension methods I have managed to cobble together from various sources on the Internet and a little bit from my own head.
WaitUntilEnabled
I encountered this issue whilst working with the CascadingDropDown provided by the Ajax Control Toolkit, now WatiN does provide a function called WaitUntil which allows you to pass in the name of the attribute you want to examine and the value that you expect the attribute to become, unfortunately however, it seems the CascadingDropDown adds a "disabled" attribute with a value of "true" to the html page. The drop down list then becomes enabled when the disabled attribute no longer exists or is set to false. Therefore it was necessary to write an extension method that waited until the control no longer had a "disabled" property with a value of "true. The method is as follows:
/// <summary>
/// Waits until the specified element is enabled within the web page.
/// </summary>
/// <param name="element">The element.</param>
public static T WaitUntilEnabled<T>(this IElement element) where T : class
{
var timer = new SimpleTimer(IE.Settings.WaitForCompleteTimeOut);
do
{
var value = element.GetAttributeValue("disabled");
if (value != "True" &&
value != "true")
{
return element as T;
}
Thread.Sleep(200);
}
while (!timer.Elapsed);
throw new TimeoutException("Waited too long for " + element.Id + " to become enabled");
}
UPDATED 15/09/08: Whilst preparing for my Ready, Steady, Speak session at Remix I came across a small bug where the value retrieved for the disabled attribute was "True" instead of "true" as initially thought, I don't know what caused the difference to occur but I have added the additional check anyway.
The usage of this method is as follows:
ie.SelectList(Find.ById("MyCascadingDropDown")).WaitUntilEnabled().Select("AValue");
IfExists
This is just an extension method I wrote to make it a little easier to work with elements that don't exist:
/// <summary>
/// Checks whether the element exists, if so performs the specified action,
/// otherwise just returns the current element object.
/// </summary>
/// <typeparam name="T">The type of element to check for existence.</typeparam>
/// <param name="element">The element.</param>
/// <param name="action">The action.</param>
/// <returns></returns>
public static T IfExists<T>(this Element element, Action<T> action) where T : class
{
if (element.Exists)
{
action(element as T);
}
return element as T;
}
This method can be used as follows:
ie.TextField(Find.ById("MyTextField")).IfExists<TextField>(e => e.TypeText("AValue"));
WaitForAsyncPostBackToComplete
If you are working with controls within an update panel you will inevitably come across a situation where a value is changed via an async postback and whilst that postback is taking place WatiN will try and perform another action which results in an error.The solution to this problem was found after searching the Internet for a little while and I came across this post which explains the problem in a little more detail.
I have modified the original method slightly to make it an extension for the IE browser object, it is used to determine whether the current page is within an asynchronous postback, unfortunately in order to use this method the page you are testing needs to have a JavaScript method defined so if you don't have control over the website you could be a little stuck, here is the Javascript:
<script type="text/javascript">
var prm = Sys.WebForms.PageRequestManager.getInstance();
function IsPageInAsyncPostback()
{
return prm.get_isInAsyncPostBack();
}
</script>
The C# method implementation is as follows:
/// <summary>
///Waits for async post back to complete.
/// </summary>
/// <param name="ie">The ie instance to use.</param>
public static void WaitForAsyncPostBackToComplete(this IE ie)
{
bool isInPostback = true;
while (isInPostback)
{
isInPostback = Convert.ToBoolean(ie.Eval("IsPageInAsyncPostback();"));
if (isInPostback)
{
Thread.Sleep(200);//sleep for 200ms and query again
}
}
}
After a kind comment from Jo (see below), I have modified this as there is no need to place the IsPageInAsyncPostback method inside of the page, I just needed to modify the Eval method to make the entire call, as follows:
/// <summary>
/// Waits for async post back to complete.
/// </summary>
/// <param name="ie">The ie instance to use.</param>
public static void WaitForAsyncPostBackToComplete(this IBrowser ie)
{
switch (ie.BrowserType)
{
case BrowserType.InternetExplorer:
{
bool isInPostback = true;
while (isInPostback)
{
isInPostback = Convert.ToBoolean(ie.Eval("Sys.WebForms.PageRequestManager.getInstance().get_isInAsyncPostBack();"));
if (isInPostback)
{
Thread.Sleep(200); //sleep for 200ms and query again
}
}
break;
}
}
}
And can be used like this:
SelectList list = ie.SelectList(Find.ById("MySelectList"));
list.Option("TestOption").Select();
ie.WaitForAsyncPostBackToComplete();
SelectList childList = ie.SelectList(Find.ById("MyChildSelectList"));
list.Option("TestChildOption").Select();
ByPartialId
The final useful method is ByPartialId, this is merely a wrapper around a regular expression but it does save quite a bit of typing and needing to remember the regular expression syntax. I found this on Ayende Rahien's blog:
/// <summary>
/// Gets an attribute constraint using a partial id.
/// </summary>
/// <param name="partialElementId">The partial element id.</param>
/// <returns></returns>
public static AttributeConstraint ByPartialId(string partialElementId)
{
return Find.ById(new Regex(".*" + partialElementId + "$"));
}