Introduction
In this article, I have explained how we can use WaTiN for test automation by writing a small wrapper utility over the core WaTiN functionalities. WaTiN stands for “Web Application Testing in .NET”, and is derived from WaTiR which is “Web Application Testing in Ruby”. The WaTiN libraries can be downloaded from WatiN Libraries.
One of the issues faced while writing an Automation Test Suite is locating the controls and then checking the functionality. It is accentuated by the fact that our test fails because the automated test is unable to find a particular control in the application, in-spite of the fact that the developer has added it. A good way to tackle this issue is by writing a small wrapper class over the WaTiN library and using this class in the automated tests.
Background
I have used WaTiN in conjunction with NUnit, and there’s already an article for this - NUNIT and WatiN.
Using the Code
The wrapper utility is mainly a set of four classes along with a small Strings project. One of the classes is ConfigurationReader
; I am using it so that I can specify the website URL, database connection string, user name, password etc., in the app.config. This way, we can modify the app.config for use in any application and need not modify the source code if the same application is deployed at different web servers.
using System.Configuration;
namespace Automation.Common
{
public static class ConfigurationReader
{
#region Public Methods
public static string getHomePageUrl()
{
return getConfigurationValue( "WebSiteUrl" );
}
public static string getConnectionString()
{
return getConfigurationValue( "ConnectionString" );
}
.....
...
Then, we have an Enumerators
class. This class basically has a list of controls that have been used in the application and have to be automated or have some functionality associated with them.
namespace Automation.Common
{
public class Enumerators
{
public enum ControlType
{
Span,
Link,
Table,
TableCell,
TableRow,
Frame,
Image,
TextField,
CheckBox,
Button,
SelectList,
Div,
RadioButton,
FileUpload
}
}
}
Now we have the UseDialogOnce
class; this is a third-party code I found on the internet. Basically, this class helps on handling pop-up dialogs and closing them as specified. Here is the link: UseDialogOnce.
Now, the core Utilities
class. This class is the heart of the wrapper solution I am proposing. In this class, we have methods which act as wrapper methods over the core WaTiN methods, and help in locating the controls by ID, Custom etc., as WaTiN allows us to. The advantage of this class is that I check if the control exists before returning the object to the actual test we have written, thereby giving the exact message as to what was not found. Also, in this class, there are multiple overloads to locate the specified control inside a browser instance, or a dialog page, or a table, or an IFrame
, and so on. There can be more overloads as required.
The first method of significance is NavigateToHomePage
. This method can be called from the test classes we write, passing a reference to the existing browser instance. Other than that, it also checks if the browser instance exists or not; if not, it creates a new instance and navigates to the home page of the application. The home page URL can be taken from the app.config, as I mentioned earlier.
public static void NavigateToHomePage( ref IE ieBrowser )
{
if ( ieBrowser == null )
{
ieBrowser = new IE();
ieBrowser.ShowWindow( NativeMethods.WindowShowStyle.Maximize );
}
ieBrowser.GoTo( ConfigurationReader.getHomePageUrl() );
ieBrowser.WaitForComplete();
}
Another major method of this class is FindControlInBrowserById
. Similar to this method, there are other methods like FindControlInBrowserByCustom
, SelectControlInBrowserByID
etc., I won’t be going into the details of each method. Below is the FindControlInBrowserById
:
public static object FindControlInBrowserByID( IE ie, string strID,
Enumerators.ControlType ctrl )
{
if ( ctrl == Enumerators.ControlType.Span )
{
Span sp = ie.Span( Find.ById( strID ) );
Assert.IsTrue( sp.Exists, "Could not Find: " + strID );
return sp;
}
else if ( ctrl == Enumerators.ControlType.Link )
{
Link lnk = ie.Link( Find.ById( strID ) );
Assert.IsTrue( lnk.Exists, "Could not Find: " + strID );
return lnk;
}
else if ( ctrl == Enumerators.ControlType.Frame )
{
Frame iFrame = ie.Frame( Find.ById( strID ) );
return iFrame;
}
else if ( ctrl == Enumerators.ControlType.Image )
{
Image img = ie.Image( Find.ById( strID ) );
Assert.IsTrue( img.Exists, "Could not Find: " + strID );
return img;
}
else if ( ctrl == Enumerators.ControlType.TableCell )
{
TableCell tCell = ie.TableCell( Find.ById( strID ) );
Assert.IsTrue( tCell.Exists, "Could not Find: " + strID );
return tCell;
}
else if ( ctrl == Enumerators.ControlType.Table )
{
Table tbl = ie.Table( Find.ById( strID ) );
Assert.IsTrue( tbl.Exists, "Could not Find: " + strID );
return tbl;
}
else if ( ctrl == Enumerators.ControlType.TableRow )
{
TableRow row = ie.TableRow( Find.ById( strID ) );
Assert.IsTrue( row.Exists, "Could not Find: " + strID );
return row;
}
else if ( ctrl == Enumerators.ControlType.CheckBox )
{
CheckBox chk = ie.CheckBox( Find.ById( strID ) );
Assert.IsTrue( chk.Exists, "Could not Find: " + strID );
return chk;
}
else if ( ctrl == Enumerators.ControlType.Button )
{
Button btn = ie.Button( Find.ById( strID ) );
Assert.IsTrue( btn.Exists, "Could not Find: " + strID );
return btn;
}
else if ( ctrl == Enumerators.ControlType.TextField )
{
TextField txt = ie.TextField( Find.ById( strID ) );
Assert.IsTrue( txt.Exists, "Could not Find: " + strID );
return txt;
}
......
....
..
...
else
{
return null;
}
}
On the same lines, there are methods like ClickLink
, SelectControlInBrowserById
, AddTextInTextBox
etc. The ClickLink
method clicks a link in the browse, or table, or dialog page as specified. SelectControlInBrowserById
is useful for controls which have an action associated with them and we need to perform that action to proceed to the next step in the test. AddTextInTextBox
, as the name suggests, adds the specified text in the textbox control specified by the ID.
Finally, how to use this wrapper we have written. It's pretty easy to do that; below is a sample code of a simple create record test:
namespace Automation.Tests
{
[TestFixture]
public class JailRecordTest
{
IE ie;
[TestFixtureSetUpAttribute]
public void SetUp()
{
StartProcess();
}
[Test]
[Description( "Verify the Title of the HomePage" )]
public void Verify_HomePage_PageTitle()
{
Assert.AreEqual( AppStrings.HomePageTitle, ie.Title );
}
[Test]
[Description( "Create a New Instance of Jail Record" )]
public void Create_New_JailRecordTest()
{
Frame appFrame =
( Frame )Utilities.FindControlInBrowserByCustom( ie, "name",
AppStrings.AppFrame, Enumerators.ControlType.Frame );
Table menuBarTable =
( Table )Utilities.FindControlInFrameByID( appFrame,
AppStrings.AppMenuBar, Enumerators.ControlType.Table );
Image newButtonImage =
( Image )Utilities.FindControlInTableByID( menuBarTable,
JailRecordStrings.NewJailRecordImage,
Enumerators.ControlType.Image );
newButtonImage.Click();
ie = IE.AttachToIE( Find.ByTitle( JailRecordStrings.NewJailRecordPageTitle ) );
Utilities.AddTextInTextBox( ref ie,
JailRecordStrings.NewJailRecordJailIdTxtBox, "Demo Record" );
Table jailRecordMenuBar =
( Table )Utilities.FindControlInBrowserByID( ie,
JailRecordStrings.NewJailRecordMenuBar, Enumerators.ControlType.Table );
Image saveAndCloseButtonImage =
( Image )Utilities.FindControlInTableByID( jailRecordMenuBar,
JailRecordStrings.NewJailRecordSaveCloseImage,
Enumerators.ControlType.Image );
saveAndCloseButtonImage.Click();
ie = IE.AttachToIE( Find.ByTitle( AppStrings.HomePageTitle ) );
}
#region Private Methods
private void StartProcess()
{
Utilities.NavigateToHomePage( ref ie );
}
#endregion
[TestFixtureTearDownAttribute]
public void TearDown()
{
ie.Close();
}
}
}
This test was written for automating a CRM like web application, so we had to locate the frame first, then the control, primarily to improve the performance of the test. So first, we find the frame, then the control inside it, and finally, we have a MenuBar
which has a “New” button. We find the button and click it. After clicking it, a new browser window is opened, hence the IE.AttachToIE
method is used. In the new browser window, we enter the required data and then find the “Save” button as we had found the “New” button, and save the record. The verification part, if the record has been created, can be done quite easily, by looking up for the record name in the grid shown on the main page (CRM specific – that’s why I have removed that portion from the code). Now, if you analyze the code, if we had used only WaTiN instead of the wrapper utility, we would have ended up with approximately 100 lines of code in the Test method, and it would have been very confusing as to where we are locating the actual controls and then checking their functionalities. With this wrapper, the Test method is reduced to less than half of that, and the best part is that, the utility can be used across the entire web application.
I hope this Wrapper utility helps my fellow Test Automation developers, and is useful in automating complex web applications. Any suggestions or improvements, I can be contacted anytime. Thank you.
Points of Interest
As I did mention earlier, finding a web control in a web application is one of the toughest parts for a Test Automation Developer. The only way to ensure that your automated test runs alright is to make sure that the developers always provide an ID for the web control they are using in the web application. I had a really tough time convincing developers to make it a practice.
Also, we were using Telerik and Rad Controls in our applications extensively. Automating them was a very big issue, as it's difficult to find the way they are rendered (even with the IE Developer Tool). I could discuss that in another article, if required.
History
The WaTiN DLL I have used in this project is 1.2.1.4000; as of now, the latest release is 2.0 Beta 1. I will be upgrading the project to use that soon.