Today, I am going to be looking at WatiN, which stands for Web Application Testing in .Net, and seeing how well it actually works with an ASP.net web application.
WatiN is a type of record and playback framework to allow for automating the UI testing for web applications. It uses IE and allows you to interact with the page, for example clicking buttons and typing in text.
A simple example from their documentation shows how to search for WatiN on google and assert you got the correct results back.
[Test]
public void SearchForWatiNOnGoogle()
{
using (IE ie = new IE(“http://www.google.com”))
{
ie.TextField(Find.ByName(“q”)).TypeText(“WatiN”);
ie.Button(Find.ByName(“btnG”)).Click();
Assert.IsTrue(ie.ContainsText(“WatiN”));
}
}
You can use any test framework to get started, with MbUnit you simply write the tests as normal, but in your test fixture attribute you need to set the ApartmentState to STA. ApartmentState is part of .Net which states how the thread should execute. STA means that the thread will create and enter a single threaded apartment, everything will just be kept on the single thread.
[TestFixture(ApartmentState = ApartmentState.STA)]
If you don’t have this set, when you run your tests you will receive the following exception:
Message: The CurrentThread needs to have it’s ApartmentState set to ApartmentState.STA to be able to automate Internet Explorer.
Type: System.Threading.ThreadStateException
Source: WatiN.Core
TargetSite: Void CheckThreadApartmentStateIsSTA()
Now we have running tests, we can start to look at testing a ‘real’ application. Below is the screenshot of the application under test (AUT).
Entering information would cause the page to look like this:
Very simple. To test this worked correctly, we would want to simulate what the user is doing and to verify it worked the same way a person would, in this case that it displayed the search term typed into the text box.
First we need to find the text box (called searchText) by it’s name and enter a string. We then find the button (by its value – the text displayed to the user and not the ID, the button ID is in fact Button1) and click it. We then verify the response.
[Test]
public void WatiNSearchText_SearchWatiN_TextDisplayedOnScreen()
{
IE ie = new IE(“http://localhost:49992/WatiNSite/”);
// Find the search text field and type Watin in it.
ie.TextField(Find.ByName(“searchText”)).TypeText(“WatiN”);
// Click the search button.
ie.Button(Find.ByValue(“Search”)).Click();
//Verify it contains search term.
bool result = ie.Text.Contains(“WatiN”);
Assert.IsTrue(result);
}
The URL points to the ASP.net development server for the solution, with a static port set.
That works with simply html, but what happens when ASP.net is pain with the naming. When using a master page with ASP.net, you place all of the pages content within a ContentPlaceHolder. When ASP.net renders this, to ensure naming of elements does not clash it prefixes the IDs.
Without using the placeholder, the textbox html is:
However, after using master pages it becomes:
Not great! All the elements are referred as strings, by moving to master pages all of our tests would break. WatiN will report this as:
Message: Could not find a ‘INPUT (text password textarea hidden) or TEXTAREA’ tag containing attribute name with value ‘searchText’
One solution has been provided by James Avery in his WatiN Test Pattern post. Here, he basically says that each page in the website should have a adapter in the test code which we code against which in turn calls the page (this is not mocking the page). It means that changes to the website, such as naming, requires only changing the adapter class to match. Following his pattern, the test would be:
[Test]
public void WatiNSearchText_SearchWatiN_TextDisplayedOnScreen2()
{
Default page = new Default(“http://localhost:49992/WatiNSite/”);
page.SearchText.TypeText(“WatiN”);
page.SearchButton.Click();
bool result = page.Text.Contains(“WatiN”);
Assert.IsTrue(result);
}
Its a lot easier to read as everything is referred to as the page and you can easily imagine what is happening. The adapter looking like this:
public class Default : IE
{
public TextField SearchText
{
get { return TextField(Find.ByName(“searchText”)); }
}
public Button SearchButton
{
get { return Button(Find.ByValue(“Search”)); }
}
public Default(string url) : base(url)
{
}
}
We could even take this a step on and abstract the searching into its own method which we call in the test.
public void SearchFor(string term)
{
SearchText.TypeText(term);
SearchButton.Click();
}
The test would then be:
[Test]
public void WatiNSearchText_SearchWatiN_TextDisplayedOnScreen3()
{
Default page = new Default(“http://localhost:49992/WatiNSite/”);
page.SearchFor(“WatiN”);
bool result = page.Text.Contains(“WatiN”);
Assert.IsTrue(result);
}
This does solve some of the problems as it makes the tests less dependent on the application code. However, the ASP.net naming is still getting in the way. If we add another container or the element is inside a custom control we are still going to have a problem. The answer is RegEx!
In our adapter, I change the way we find the element to use a method RegExName. Now its Find – ByName – RegExName – ElementName.
public TextField SearchText
{
get { return TextField(Find.ByName(RegExName(“searchText”))); }
}
Our RegExName method just takes in the elementname and return a regex which will ignore all of the ASP.net prefixes.
private Regex RegExName(string elementName)
{
return new Regex(“.*” + elementName + “$”);
}
By using an adapter and RegEx we can abstract away from the real implementation and make our tests a lot more beneficial. The tests are quite easy to write as long as you keep things focused. By arranging your tests as above, you should have a lot more long term success.
But having an automated test run for the UI will never replace a human tester as there are other scenarios to test for, cross browser, usability, wording which a test framework cannot test.
Ben, I wanted to mention another web testing tool for the .NET platform, named InCisif.net.
I created a page comparing your first sample using WatiN and InCisif.net at WatinInCisif.net.SyntaxCompare.html
to desmontrate the 2 different APIs.
Regarding the ASP.NET problem that you mentioned we also tried to offer solutions:
– Out of the box our Api support your solution this way:
Page.Control(“re:.*searchText$”).Text = “WatiN”;
– We also created a concept of MappedNames. One logical name can be associated with an ASP.NET complex name. Our record mode will generate the ASP.NET complex name.
By the way InCisif.net comes with it own testing framework, we implemented parameterized test support in may 2007 inspired by mbUnit.
We do support NUnit and probably mbUnit. I still did not try yet.
Thanks.
Frederic Torres
http://www.InCisif.net
Web Testing with C# or VB.NET
Hi Ben,
I’ve discovered that MbUnit has the WebAssert class which is supposed to work with Asp.Net. How do you use it? How do you obtain a reference to the control you are testing?
Thanks
ulu
The real problem that I’m seeing with using WatiN in unit testing is in setting the URL.
Every example that I see uses a fixed page. My tests have to work in different environments.
Obviously, there’s a crass solution of setting a config file for each environment, but I’m wondering if anyone has something more flexible, along the lines of VS allowing you to view a page in the browser. What kind of mechanism would achieve that programmatically?
This is how the same test would look like in Ivonna:
[Test]
public void WatiNSearchText_SearchWatiN_TextDisplayedOnScreen()
{
var session = new TestSession();
System.Web.UI.Page page = session.GetPage(“Default.aspx”);
// Find the search text field and type Watin in it.
var searchText = (TextBox)page.FindControl(“searchText”);
searchText.Text = “WatiN”;
// Click the search button.
page = session.ProcessPostback(“Search”);
//Verify it contains search term.
bool result = ((Label)page.FindControl(“resultLabel”)).Text == “Hello WatiN”;
Assert.IsTrue(result);
}
While the code doesn’t seem much improvement over that of WatiN, there are several important differences:
– You don’t need a Web server running.
– You have a “real” Page object, as well as other controls. For example, if you have a custom control on your page, you can inspect/set its properties as well.
– Everything happens in the same AppDomain, so you can do some setup, i.e., inject mocked services.
Sorry for spamming your blog..
I am trying to use MbUnit/Nunit as test harness. Basically, use this to run test for my team. So I am trying to do minimal installation and install MbUnit and .Net framework. When I run the test, I am getting variety of error like Com state. I fix it then I get error as mshtml missing issue. I resolve this one, then I get error as “permission denied for for mshtml”
I would appreciate any insight you can share with this scenario. I googled a lot but no success so far.
I am using Watin
Thanks