WPF UI Automation using WebBrowser Control and WebClient

One smart day i was sitting on my seat and was feeling the fresh air, when i got an email about developing a Proof of concept for a upcoming project. I felt like a super hero as it was like saving the day from monsters and Alien Laser guns. Jokes apart, i was tasked to download a file from a website. Simple isn’t it, but i do not have to do it through mouse and keyboard, my application should go to website, login to website, click some links, navigate automatically through pages, reach the specific download page, initiate the download request and save the file to some place.
Requirements :
– Development platforms can be Silverlight, WPF or windows
– Should go to a website say http://www.gmail.com
– Login to an account
– Click Inbox
– Find emails with attachments
– Download Attachement
Development Phase:
I started to develop it in Silverlight as i was provided with a sample UI and some implemented code in Silverlight. Soon i came to know that in Silverlight we do not have much support for DOM processing. I decided to develop in WPF. I thought of a new scenerio that is below:
– Go to telerik.com
– Login to site
– Move to free tials page
– Download MVC package from the list

My User interface looked like below:

XAML
<Window x:Class=”WebBrowserAutomationWPF.MainWindow”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml
Title=”MainWindow” Height=”350″ Width=”525″ Loaded=”Window_Loaded”>
<Grid>
<WebBrowser HorizontalAlignment=”Stretch” Name=”webBrowser1″ VerticalAlignment=”Stretch” Margin=”0,38,0,0″ />
<Button Content=”Idle” Height=”23″ HorizontalAlignment=”Left” Name=”showHtml” VerticalAlignment=”Top” Width=”186″ Click=”showHtml_Click” />

</Grid>
</Window>

It has a Web Browser control and a Button. I am using the button for showing the progress only, nothing fancy.
On Windows loaded event i ask it to move to http://www.telerik.com/ and also subscribe to two events which will become handy as we go on.

C#
private void Window_Loaded(object sender, RoutedEventArgs e)
{
webBrowser1.Navigate(new Uri(“http://www.telerik.com/“));
webBrowser1.LoadCompleted += new LoadCompletedEventHandler(webBrowser1_LoadCompleted);
webBrowser1.Navigating += new NavigatingCancelEventHandler(webBrowser1_Navigating);
}

In Load completed event i switch each page with a flow id, so on each navigation event i process something different to navigate further.

C#
void webBrowser1_LoadCompleted(object sender, NavigationEventArgs e)
{
mshtml.HTMLDocument doc = (mshtml.HTMLDocument)webBrowser1.Document;

if (flowid == 1)
{
IHTMLElementCollection theElementCollection;
theElementCollection = doc.getElementsByTagName(“input”);
foreach (IHTMLElement curElement in theElementCollection)
{
if (curElement.getAttribute(“name”, 0) != null && curElement.getAttribute(“name”, 0) != String.Empty)
if (curElement.getAttribute(“name”, 0).StartsWith(“ctl00$YourAccount$tm_usercontrols_public_clientnet_loginnameandstatus_ascx1$btnLogin”))
{
curElement.click();
}
}
}
else if (flowid == 2)
{
IHTMLElementCollection theElementCollection;
theElementCollection = doc.getElementsByTagName(“input”);
foreach (IHTMLElement curElement in theElementCollection)
{
if(curElement.getAttribute(“name”, 0)!=null)
if ((curElement.getAttribute(“name”, 0).ToString() == “ctl00$PageContent$usercontrols_public_clientnet_signinform_ascx5$loginForm$UserName”))
{
curElement.setAttribute(“Value”, “<Your loginid email here >”, 0);
}
else if ((curElement.getAttribute(“name”, 0).ToString() == “ctl00$PageContent$usercontrols_public_clientnet_signinform_ascx5$loginForm$Password$tbSanitized”))
{
curElement.setAttribute(“Value”, “<Your password here>”,0);
}
//else if ((curElement.getAttribute(“name”, 0).ToString() == “ctl00$PageContent$usercontrols_public_clientnet_signinform_ascx5$loginForm$RememberMe”))
//{
//    curElement.setAttribute(“checked”, true, 0);
//}
}
theElementCollection = doc.getElementsByTagName(“input”);
foreach (IHTMLElement curElement in theElementCollection)
{
if(curElement.getAttribute(“name”, 0) !=null)
if (curElement.getAttribute(“name”, 0).Equals(“ctl00$PageContent$usercontrols_public_clientnet_signinform_ascx5$loginForm$LoginButton”))
{
curElement.click();
}
}
}
else if (flowid == 3)
{

IHTMLElementCollection theElementCollection;
theElementCollection = doc.getElementsByTagName(“a”);
foreach (IHTMLElement curElement in theElementCollection)
{
if(curElement.getAttribute(“innerText”, 0)!=null)
if (curElement.getAttribute(“innerText”, 0).Equals(“Free Trials”))
{
curElement.click();
}
}

}
else if (flowid == 4)
{
IHTMLElementCollection theElementCollection;
theElementCollection = doc.getElementsByTagName(“span”);
foreach (IHTMLElement curElement in theElementCollection)
{
if (curElement.getAttribute(“innerText”, 0) != null)
if (curElement.getAttribute(“innerText”, 0).Equals(“Download Open Source Version”))
{
curElement.click();
}
}

}
else if (flowid == 5)
{
IHTMLElementCollection theElementCollection;
theElementCollection = doc.getElementsByTagName(“a”);
foreach (IHTMLElement curElement in theElementCollection)
{
if(curElement.getAttribute(“title”, 0) !=null)
if (curElement.getAttribute(“title”, 0).Equals(“Telerik.Web.Mvc.Examples-Razor_OpenSource.zip”))
{
downloadfile((curElement as IHTMLAnchorElement).href);
}
}
}

flowid += 1;
}

Here is the download file function:

C#
private void downloadfile(string path)
{

client.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
client.CookieContainer = cookies;
client.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(client_DownloadFileCompleted);
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
showHtml.Content = “Downloading .. “;
client.DownloadFileAsync(new Uri(path), @”C://web.zip”);
NavigationPath = path;

}

I used webclient to download the file, the problem was to execute the whole procedure silently, so i used webclient else i could have used WebBrowser1.Navigate(((curElement as IHTMLAnchorElement).href); and it would have open a dialog.
The issue arise here, webclient and webbrowser do not share the same session. When we attempt to download the file, through webclient it redirects internally to the login page. And what we get downloaded in web.zip is infact the login.html page. To prevent it, we need to find a way to syncronize the cookie container of webclient and webbrowser document.
I searched a thousand blogs, hundreds of articles, but i seemed it required the Super Hero Magic to do the trick. I keep on hitting the monster, from left, from right, from up and down. And suddenly the monster was defeated and was lying flat in front of me.
The magic was making the webclient CookieAware and using the webbrowser’s navigating event. Webbrowser Only release the cookie container in its event arguments of navigating event. So here is what i did.
C#
public class CookieAwareWebClient : WebClient {

public CookieAwareWebClient() { CookieContainer = new CookieContainer(); }
public CookieContainer CookieContainer { get; set; }
protected override WebRequest GetWebRequest(Uri address)
{
WebRequest request = base.GetWebRequest(address);
if (request is HttpWebRequest)
{
(request as HttpWebRequest).CookieContainer = CookieContainer;
}
return request;
}
}

And for navigating event;

C#
public CookieContainer GetCookieContainer()
{
CookieContainer container = new CookieContainer();

foreach (string cookie in ((mshtml.HTMLDocument)webBrowser1.Document).cookie.Split(‘;’))
{
string name = cookie.Split(‘=’)[0];
string value = cookie.Substring(name.Length + 1);
string path = “/”;
string domain = “.telerik.com”;
container.Add(new Cookie(name.Trim(), value.Trim(), path, domain));
}
return container;
}

void webBrowser1_Navigating(object sender, NavigatingCancelEventArgs e)
{
cookies=GetCookieContainer();
}

The tricky part with telerik.com was that, along with cookie it requires another string, “Application=1;” so we need to ammend it with the cookie, manually and add in the container.
That was how i lived the day of a super hero and defeated a bunch of monsters. I hope these superhero tricks will help the upcoming heroes to take up such tasks with ease and less research. The whole time i spent was fighting with the webbrowser and webclient to plug-in togther. What i achieved was that webclient by nature is not cookie aware, so first we need to make it cookie aware, and second most important thing is that webbrowser only disclose its cookie container in its navigating event. These two things make life easier, and sky is the limit as we can make scrappers, ui automation tools, Ui testing tools out of it. Hope this will help the people who need this functionality in there application. Thankyou All.