使用开源工具:WatiN 的自动化实用程序






4.87/5 (13投票s)
一篇关于创建 WatiN 库的包装器实用程序的文章。
引言
在本文中,我解释了如何通过在核心 WaTiN 功能之上编写一个小型包装器实用程序来使用 WaTiN 进行自动化测试。WaTiN 代表“Web Application Testing in .NET”(.NET 中的 Web 应用程序测试),源自 WaTiR(“Web Application Testing in Ruby” - Ruby 中的 Web 应用程序测试)。WaTiN 库可以从WatiN Libraries下载。
编写自动化测试套件时面临的一个问题是定位控件然后检查功能。这个问题因我们的测试失败而加剧,因为自动化测试无法在应用程序中找到特定的控件,尽管开发人员已经添加了它。解决此问题的一个好方法是编写一个小型包装器类来包装 WaTiN 库,并在自动化测试中使用此类。
背景
我已将 WaTiN 与 NUnit 结合使用,并且已经有一篇关于此的文章 - NUNIT and WatiN。
Using the Code
包装器实用程序主要由四类和一个小型 Strings 项目组成。其中一个类是 `ConfigurationReader`;我使用它以便可以在 _app.config_ 中指定网站 URL、数据库连接字符串、用户名、密码等。这样,我们可以修改 _app.config_ 以在任何应用程序中使用,而无需修改源代码,因为相同的应用程序可以部署在不同的 Web 服务器上。
using System.Configuration;
namespace Automation.Common
{
/// <summary>
/// Reads the app.config for the application DB name and username etc
/// </summary>
public static class ConfigurationReader
{
#region Public Methods
/// <summary>
/// Reads the HomePage URL from app.config
/// </summary>
/// <returns>Home Page URL</returns>
public static string getHomePageUrl()
{
return getConfigurationValue( "WebSiteUrl" );
}
/// <summary>
/// Reads the Database Connection String from app.config
/// </summary>
/// <returns>Connection String</returns>
public static string getConnectionString()
{
return getConfigurationValue( "ConnectionString" );
}
.....
...
然后,我们有一个 `Enumerators` 类。此类基本上包含应用程序中使用并需要自动化或与之关联的某些功能的控件列表。
namespace Automation.Common
{
public class Enumerators
{
public enum ControlType
{
Span,
Link,
Table,
TableCell,
TableRow,
Frame,
Image,
TextField,
CheckBox,
Button,
SelectList,
Div,
RadioButton,
FileUpload
}
}
}
现在我们有了 `UseDialogOnce` 类;这是我在互联网上找到的第三方代码。基本上,此类有助于处理弹出对话框并按指定方式关闭它们。链接如下:UseDialogOnce。
现在,核心是 `Utilities` 类。此类是我提出的包装器解决方案的核心。在此类中,我们有方法充当核心 WaTiN 方法的包装器方法,并帮助按 ID、Custom 等定位控件,因为 WaTiN 允许我们这样做。此类的好处是,在将对象返回给我们编写的实际测试之前,我会检查控件是否存在,从而提供找不到什么的确切消息。此外,在此类中,有多个重载可以定位浏览器实例、对话框页面、表格或 `IFrame` 等中的指定控件。根据需要可以添加更多重载。
第一个重要的方法是 `NavigateToHomePage`。此方法可以从我们编写的测试类调用,传递对现有浏览器实例的引用。除此之外,它还检查浏览器实例是否存在;如果不存在,它会创建一个新实例并导航到应用程序的主页。如我之前提到的,主页 URL 可以从 _app.config_ 获取。
/// <summary>
/// Opens the Homepage of the web application
/// </summary>
/// <param name="ieBrowser">IE Browser Instance object</param>
public static void NavigateToHomePage( ref IE ieBrowser )
{
if ( ieBrowser == null )
{
ieBrowser = new IE();
ieBrowser.ShowWindow( NativeMethods.WindowShowStyle.Maximize );
}
ieBrowser.GoTo( ConfigurationReader.getHomePageUrl() );
ieBrowser.WaitForComplete();
}
此类中的另一个主要方法是 `FindControlInBrowserById`。与此方法类似,还有 `FindControlInBrowserByCustom`、`SelectControlInBrowserByID` 等其他方法,我将不详细介绍每种方法。下面是 `FindControlInBrowserById`
/// <summary>
/// Finds the control specified by id in the browser
/// </summary>
/// <param name="ie">IE Object</param>
/// <param name="strID">Id of the Control to be found</param>
/// <param name="ctrl">Type of Control</param>
/// <returns>The control if found, else "null"</returns>
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;
}
}
同样,还有 `ClickLink`、`SelectControlInBrowserById`、`AddTextInTextBox` 等方法。`ClickLink` 方法单击指定位置(浏览器、表格或对话框页面)中的链接。`SelectControlInBrowserById` 对于具有相关操作并且我们需要执行该操作以继续测试下一步的控件非常有用。`AddTextInTextBox` 正如其名称所示,将指定文本添加到由 ID 指定的文本框控件中。
最后,如何使用我们编写的包装器。这非常简单;下面是一个简单的创建记录测试的示例代码。
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();
//Creating the Record once the new IE instance opens
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();
}
}
}
此测试是为了自动化一个类似 CRM 的 Web 应用程序而编写的,因此我们首先需要定位框架,然后定位控件,主要是为了提高测试性能。因此,我们首先找到框架,然后在其中找到控件,最后,我们有一个 `MenuBar`,其中有一个“New”(新建)按钮。我们找到该按钮并单击它。单击后,会打开一个新的浏览器窗口,因此使用了 `IE.AttachToIE` 方法。在新浏览器窗口中,我们输入所需数据,然后找到“Save”(保存)按钮(就像我们找到“New”按钮一样),并保存记录。记录是否已创建的验证部分可以很容易地完成,只需在主页(CRM 特定 - 因此我已将该部分从代码中删除)显示的网格中查找记录名称即可。现在,如果您分析代码,如果我们只使用 WaTiN 而不是包装器实用程序,那么在 Test 方法中将有大约 100 行代码,并且会非常混乱,不知道我们在哪里定位实际控件然后检查它们的函数。使用此包装器,Test 方法减少了一半以上,最好的部分是,该实用程序可以跨整个 Web 应用程序使用。
我希望这个包装器实用程序能帮助我的同行测试自动化开发人员,并在自动化复杂的 Web 应用程序方面有所帮助。如有任何建议或改进,随时可以与我联系。谢谢。
关注点
正如我之前提到的,在 Web 应用程序中查找 Web 控件是测试自动化开发人员最困难的部分之一。确保自动化测试顺利运行的唯一方法是确保开发人员始终为他们在 Web 应用程序中使用的 Web 控件提供 ID。我花了很长时间说服开发人员养成这个习惯。
另外,我们在应用程序中广泛使用了 Telerik 和 Rad 控件。自动化它们非常困难,因为很难找到它们的渲染方式(即使使用 IE Developer Tool)。如果需要,我可以在另一篇文章中讨论这一点。
历史
我在此项目中使用的 WaTiN DLL 是 1.2.1.4000;目前,最新版本是 2.0 Beta 1。我将很快将项目升级到使用该版本。