Friday, 22 August 2008

WatiN, Ajax and some Extension Methods

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 + "$"));
}


 

9 comments:

Anonymous said...

Hi Richard. Thanks for the article.

For the WaitForAsyncPostBackToComplete example, is it really necessary to have the IsPageInAsyncPostback() function defined on your page? Can't you just ie.Eval("Sys.WebForms.PageRequestManager.getInstance().get_isInAsyncPostBack();") from within your extension method?

Richard Allen said...

Hi Jo, thank you for your comment, I hope you found the article useful.

You are absolutely right, it is not necessary to have the IsPageInPostBack() function defined in the page. I will make the modifications accordingly - don't know why I missed that, I guess I was just so happy to to find a solution I just followed it blindly. Cheers.

Anonymous said...

Thank you you share.Although I don't meet this question .I'm begining to learn WatiN.So if you can give me some advice.I'm very thanks.
Best regard.
ruan313585161@gmail.com

Anonymous said...

Hi Richard,

Thanks for the pointers but when trying to use the waitForAsyncPostBackToComplete() method I'm getting a "Sys in undefined" error. Would you have any idea on why? (missing ref, etc?)

Thanks,
Richie.

Richard Allen said...

"Sys is undefined" generally occur when the page has failed to download the ajax script manager scripts which can happen if your code interupts the page load lifecycle by calling DataBind or something similar at the wrong time in the page lifecycle. Also, it is worth noting that I have only tested this using the AjaxControlToolKit.

Deepak N said...

Hi Richard,

I'm using the following method to disable other controls during async postback.
Then in WatIn test i used your method WaitForAsyncPostBackToComplete to wait for ajax load to complete. But this method returns "false" even when async call is in progress.Is there some thing that i'm missing here.

Advance Thanks for the reply.

Anonymous said...

When extending WATIN, I suppose you are you inheriting from the IE class and including the method in there right? because I don't seem to be able to use the extension methods directly by copy pasting them in my helper class. It obviously won't be available for the IE class...can any one share how they have used them?

Anonymous said...

Sorry for my ignorance. I din't know the concept of "Extension methods" in .NET. I Googled it and now know it. Thanks anyways!

Darin Duphorn said...

I get the following:

'Sys.WebForms.PageRequestManager' is null or not an object

Is there a namespace\assembly I need to add?