Click here to Skip to main content
Click here to Skip to main content

WatiN - Web Application Testing In .NET

, 8 Jan 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
This article exposes how to use WatiN and how to create UI tests for Web applications.

WatiN Test Screenshoot

Introduction

Nowadays, testing code is not so difficult. We have lots of tools that help us to achieve this goal. But in Web application development, we use to forget to test one of the most important layers, the UI. In this article, I'm going to briefly cover how to do UI and functional testing using WatiN (Web Application Testing In .NET). I'll try to show you what impressed me of this framework from a developer point of view.

What is WatiN?

WatiN stands for Web Application Testing In .NET, and it's a framework that enables web application testing through Internet Explorer. WatiN is inspired on WatiR, a web application testing framework for Ruby. The way that WatiN works is very "easy", knowing that there's a lot of work behind a framework like this. WatiN lets you open Internet Explorer instances, and through interop, interact with the elements in a form. With WatiN, you can get and set values from the elements in a form, and you can fire events of any of the elements in the document too.

Getting started

The API of WatiN is very user-friendly, and watching some code examples and digging a bit, you can get the benefit of this framework quickly. The first thing that we need to build a test method using WatiN is to add a reference to WatiN.Core.dll. WatiN gives us four namespaces; the one that we use to interact with IE instances is WatiN.Core. The different namespaces available are:

  • WatiN.Core.DialogHandlers: The namespace that provides the needed objects to manage the dialogs that the browser can show to the user. Some of the handlers you can find in this namespace are, for example: AlertDialogHandler, ConfirmDialogHandler, FileUploadDialogHandler, PrintDialogHandler, and LogonDialogHandler.
  • WatiN.Core.Exceptions: The namespace that hosts specific exceptions that would help us to control any unwanted behavior. Some examples of these exceptions are: ElementNotFoundException, IENotFoundException, TimeoutException, and a generic WatiNException.
  • WatiN.Core.Interfaces: The namespace that provides us some interfaces to extend the WatiN framework as, for example, the ILogWriter interface that lets us implement our own LogWriter class.
  • WatiN.Core.Logging: The namespace that hosts classes that will help us to log the actions that our code is doing. We can use the DebugLogWriter class to write log messages to the debug window, or we can use the Logger class to save our logs in other ways.

Because the scope of this article is introductory, I will only show you the usage of the most important objects of the framework. In order to learn more about the rest of the namespaces and/or objects, you can check the "References & Resources" section at the bottom of this article.

Now that we have a brief idea of what WatiN is and what we can do with it, let's write some code to see the API's usage.

using WatiN.Core;

[STAThread]
static void Main(string[] args)
{
    // create a new Internet Explorer Instance

    IE ie = new IE();

    // point it to http://www.google.com

    ie.GoTo("http://www.google.com");

    // fill the search box

    ie.TextField(Find.ByName("q")).TypeText("WatiN");

    // performs click event

    ie.Button(Find.ByValue("Google Search")).Click();
}

The above code example opens an Internet Explorer instance and points it to www.google.com, then, when the page is loaded, it searches a textbox with name "q" and fills it with the text "WatiN". Then, we do the same we did for the textbox but, this time, looking for a button whose value is "Google Search". In this case, we perform the "Click()" event of the button. If we run this console application, we will see an Internet Explorer opening and doing the things we coded. Isn't that cool?

Solving initial workarounds

Back into the previous piece of code, I have to mention a couple of things. In order to automate tasks with Internet Explorer, we need to run our test under a thread that has a single-threaded apartment (STA) as the apartment state. Notice that over the Main() method, we have the [STAThread] attribute that sets the apartment state to STA. A big question that I made myself when I came across this framework was, "And what about running this tests with NUnit?". NUnit doesn't run tests in a thread using the STA apartment state; it uses multithreaded apartment (MTA), but this isn't a big problem, so we can attach a config file to the NUnit test project and specify the apartment state mode that we want. Let's see how to do it:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="NUnit">
            <section name="TestRunner" type="System.Configuration.NameValueSectionHandler"/>
        </sectionGroup>
    </configSections>
    <NUnit>
        <TestRunner>
            <!--<span class="code-comment"> Valid values are STA,MTA. Others ignored. --></span>
            <add key="ApartmentState" value="STA" />
        </TestRunner>
    </NUnit>
</configuration>

If we run the "Getting started" code sample, Internet Explorer will remain open after the actions we coded are performed. This will not affect us if we only want to run one test; we only need to close one Internet Explorer window. But if we want to run more than one test, we can do two things to avoid having to manually close the Internet Explorer windows. First, we can create the instance of the IE object using the using keyword and using the constructor that lets us specify the URL where the new instance will be pointed. If we do that, it gets closed and disposed after the test execution. Second, we can call the Close() method of the IE instance to programmatically close the window. Both are valid, but I prefer the first one because I think that the resulting code is more elegant than if we used the second one.

Creating our first UI test

If our test result is based on the response that the server sends back to the browser, we can parse the response in order to know if our test has failed or not. The way that we do that is exactly the same that we use to make the test, but searching other items in the page that help us to identify what happened. Suppose that we have an aspx page like this:

<%@ Page Language="C#" AutoEventWireup="true" 
         CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" >
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>WatiN Test Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <h2>Check if a nickname is already used</h2>
        <table>
            <tr>
                <td>Nickname:</td>
                <td><asp:TextBox runat="server" ID="txtNickName" /></td>
            </tr>
            <tr>
                <td colspan="2" align="right">
                    <asp:Button runat="server" ID="btnCheck" 
                                Text="Check !!" OnClick="btnCheck_Click" />
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <asp:Label runat="server" ID="lblResult" />
                </td>
            </tr>
        </table>
    </form>
</body>
</html>

And now, suppose that we have this code on the btnCheck.OnClick event:

protected void btnCheck_Click(object sender, EventArgs e)
{
    // do some logic to check if the nickname is already used.

    // i'll return true for all nicknames but "gsus"

    if (txtNickName.Text != "gsus")
        lblResult.Text = "The nickname is already in use";
    else
        lblResult.Text = "The nickname is not used";
}

For writing these article code samples, I'm using Visual Studio 2005 running under Windows Vista, so I don't have Internet Information Services installed. For web development, I'm using the ASP.NET Development Server that came with .NET Framework 2.0, so in the next code sample, you will see that I start the Web Server programmatically in order to run the test. I think that this is a good way to decouple our UI tests from IIS. Now, let's see how the test looks finally:

using System.Diagnostics;
using WatiN.Core;
using NUnit.Framework;
using System;

namespace WebAppUITesting
{
    [TestFixture]
    public class UITesting
    {
        //process object 

        private Process p;

        [TestFixtureSetUpAttribute]
        public void SetUp()
        {            
            // create a new process to start
            // the ASP.Net Development Server

            p = new Process();

            // set the initial properties 

            string path = 
                   Environment.CurrentDirectory.Replace(@"WebAppUITesting\bin", 
                   string.Empty);
            p.StartInfo.FileName = "WebDev.WebServer.EXE";
            p.StartInfo.Arguments = 
                   String.Format("/port:8080 /path:\"{0}WebApp\"", path);
            p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;

            // start the process

            p.Start();
        }

        [Test]
        public void CheckIfNicknameIsNotUsed()
        {
            // create a new Internet Explorer instance
            // pointing to the ASP.NET Development Server

            using (IE ie = new IE("http://localhost:8080/Default.aspx"))
            {
                // Maximize the IE window in order to view test

                ie.ShowWindow(NativeMethods.WindowShowStyle.Maximize);

                // search for txtNickName and type "gsus" in it

                ie.TextField(Find.ById("txtNickName")).TypeText("gsus");

                // fire the click event of the button

                ie.Button(Find.ById("btnCheck")).Click();

                // parse the response in order to fail the test or not 

                Assert.AreEqual(true, ie.ContainsText("The nickname is not used"));
            }            
        }

        [TestFixtureTearDownAttribute]
        public void TearDown()
        {
            // kill the ASP.NET Development Server process

            p.Kill();
        }
    }
}

As you can see, I use the method ContainsText in line 46. This method searches the specified text in the document content and returns a bool indicating if there are occurrences in the search. We can replace line 46 with the next code example, where we directly get the "lblResult" Span content to see if the nickname is already used:

// parse the response in order to fail the test or not 

bool result = (ie.Span(Find.ById("lblResult")).Text == "The nickname is not used");
Assert.AreEqual(true, result);

Conclusion

At this stage, we should know how to basically use WatiN framework to test web applications. I hope you find this framework and this article useful, as I did. Personally, I haven't used any other web application testing framework, but I got really surprised with how we can test our UI using WatiN. I think that we can do intensive UI and functional testing with it.

References & Resources

References:

Resources:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jesus Jimenez
Web Developer
Spain Spain
Jesus Jimenez is a software development consultant in Madrid. He has focused in .Net technologies in the last years and he mainly works in ASP.NET environments. He is MCAD.NET since 2002 and is starting to be a bit paranoid about usability and user interfaces.
 
You can read his blog at: http://blogs.clearscreen.com/dtax

Comments and Discussions

 
QuestionCan I do a multi select using WatiN PinmemberAlpha_Male26-Nov-09 0:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141216.1 | Last Updated 8 Jan 2007
Article Copyright 2007 by Jesus Jimenez
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid