Watin是一个UI自动化测试工具,支持ie/firefox,官方网站:http://watin.org/

主要有以下特点:

  • 支持主要的html元素,见:http://watin.org/documentation/element-class-mapping-table/
  • 可以通过多种属性查找html元素
  • 支持ajax站点测试
  • 支持对页面进行截图
  • 支持frames和iframe
  • 支持弹出对话框如alert, confirm, login以及模态对话框等
  • 方便的集成到你的测试工具,如:VS的单元测试,NUnit,MBUnit,Fitness等。

 

如何获取

目前最新版本为2.1,最后更新于2011(虽然好久不更新,但是用来做ui测试足够了),可以从http://sourceforge.net/projects/watin/下载,包括以下内容:

  • bin/:支持.net 2.0/3.5/4.0各版本的程序集
  • examples/:各种测试功能的简单例子
  • mozilla/:firefox浏览器插件,用于使用firefox浏览器进行测试
  • source/:完全使用C#编写的源代码
  • WatiN.chm:API文档

 

它还有一个录制工具WatiN Test Recorder:http://sourceforge.net/projects/watintestrecord,也好久不更新了,目前最新的3.0 Alpha版还用不起了,稳定的版本是2.0 Beta 1228。安装在64位系统下可能没办法直接运行,还需要做以下操作:

  • 通过corflags.exe /32bit+ "Test Recorder.exe"标记为32位,corflags在vs sdk目录下。
  • 通过regsvr32 comutilities.dll注册组件

当然,建议最好还是不要用录制工具去生成脚本,录制出来的脚本垃圾代码太多,手写测试脚本才是最可靠的。

 

同类工具

还有很多类似功能的UI测试工具:

 

详细说明

控件继承关系

所有的控件都位于WatiN.Core命名空间下,以下仅列出部分主要类型:

  • WatiN.Core.Component
    • WatiN.Core.Element 页面上的元素都是从Element类型派生而来,提供了元素的基本属性如Id,Name,方法如Click,Focus等。
      • WatiN.Core.Element<TElement>
        • TextField 文本(<input type=hidden/>,<input type=password/>,<input type=text/>,<textarea/>)
        • Button 按钮(<button />,<input type=button />,<input type=submit />,<input type=reset />)
        • Image (<img/>, <input type=image />)
        • CheckBox
        • RadioButton
        • SelectList
        • FileUpload
        • ElementContainer<TElement> 容器类型
          • Label (<label />)
          • Link 链接(<a />)
          • Div
          • Para (<p/>)
          • Form
          • Table
          • TableBody
          • TableCell
          • TableRow

 

IE类型主要方法

整个测试都围绕IE类型的一些方法来进行,打开浏览器、查找控件、执行输入或点击操作、对结果进行校验等,那么了解它提供了哪些方法显得格外重要,这里仅列出主要的:

  • AddDialogHandler/RemoveDialogHandler:添加/移除对话框处理程序,主要用来处理alert等弹出对话框,具体见WatiN.Core.DialogHandlers命名空间下的类型
  • CaptureWebPageToFile:网页截图并保存到文件
  • WaitForComplete:等待页面加载完成
  • AttachTo:按条件在进程中查找已有的浏览器窗口,返回IE类型实例(这种方法不需要通过IE.Goto方法打开窗口)
  • RegisterAttachToHelper:注册自定义的IE类型用于AttachTo方法
  • Exists:进程中查找是否存在符合条件的浏览器窗口
  • Back/Forward/Refresh/Close/ForceClose/Reopen:后退/前进/刷新/关闭/强制关闭/关闭并重新打开空页面窗口
  • GoTo/GoToNoWait:打开URL
  • ShowWindow/SizeWindow:调整窗口大小
  • ClearCache/ClearCookies:清理缓存/清理Cookie
  • GetCookie/GetCookieContainerForUrl/GetCookiesForUrl:获取Cookie
  • SetCookie:设置Cookie

 

HTML元素主要属性及方法

这里主要列出控件基础类型Element的属性和方法

属性,熟悉js dom的话从字面意思就能看懂:

  • Id/IdOrName/Name/ClassName/TagName/Title/Text/InnerHtml/OuterHtml/OuterText/Style 元素自身的属性
  • Parent/NextSibling/PreviousSibling/DomContainer/TextBefore/TextAfter
  • Enabled/Complete/Exists:是否启用/是否完成加载/是否存在

方法:

  • Ancestor:查找最近的祖先元素,类似于jQuery的closest方法
  • Blur/Change/Click/ClickNoWait/DoubleClick/Focus/Flash/Highlight/KeyDown/KeyDownNoWait/KeyPress/KeyPressNoWait/KeyUp/KeyUpNoWait/MouseDown/MouseEnter/MouseUp/Refresh/FireEvent:触发控件的事件
  • GetValue(attributeName)/GetAttributeValue(attributeName):获取属性值
  • SetAttributeValue(name, value):设置属性值
  • WaitForComplete/WaitUntil/WaitUltilExists/WaitUntilRemoved:等待指定条件达成

以上的属性、方法在支持的元素中都能使用,有一些元素还有自己单独的属性/方法,如TextField有自己的MaxLength/ReadOnly属性、TypeText/AppendText方法等。

 

在页面中查找控件

IE类型提供了诸多方法用于在页面中查找控件,其中最主要的方法如下:

  public virtual TElement ElementOfType<TElement>(string elementId) where TElement : Element; // 通过id查找
public virtual TElement ElementOfType<TElement>(Regex elementId) where TElement : Element; // 通过正则表达式匹配id查找
public virtual TElement ElementOfType<TElement>(Predicate<TElement> predicate) where TElement : Element; // 通过自定义方法匹配
public virtual TElement ElementOfType<TElement>(Constraint findBy) where TElement : Element; // 通过Find类型提供的方法查找 public virtual Element Element(string elementId);
public virtual Element Element(Regex elementId);
public virtual Element Element(Predicate<Element> predicate);
public virtual Element Element(Constraint findBy);

其他类型的控件一般都是由ElementOfType<TElement>方法扩展而来,如TextField:

  public virtual TextField TextField(string elementId);
public virtual TextField TextField(Regex elementId);
public virtual TextField TextField(Predicate<TextField> predicate);
public virtual TextField TextField(Constraint findBy);

这里简单演示一下TextField的使用:

  browser.TextField("lwme");
browser.TextField(new Regex("lwme", RegexOptions.IgnoreCase));
browser.TextField(t => t.Id.ToLowerInvariant() == "lwme");
browser.TextField(Find.ById("lwme"));

更灵活的使用可以直接用自定义方法匹配,或者Find类提供的方法。

Find类提供了许多有用的方法来查找元素:

  • ById/ByName/ByClass/ByText/ByValue/ByTitle/ByUrl/BySrc/ByStyle:通过各种属性来查找元素
  • By(attributeName, …):上面的方法就是基于这个方法而定义的,通过这个方法可以查找自定义属性
  • ByIndex:按控件序号
  • ByFor/ByLabelText:按对应<label />
  • BySelector:支持jQuery/Sizzle的css Selector

 

使用方法

注:测试代码大部分来自官方例子并稍作修改。

直接从程序集目录引用WatiN.Core.dll到项目中,由于WatiN使用了COM组件即Interop.SHDocVw.dll,所以必须使用单线程模式运行(可以使用STAThreadAttribute标识)。

先来个简单的控制台例子:

        [STAThread]
static void Main(string[] args)
{
using (var browser = new IE("http://lwme.cnblogs.com"))
{
browser.TextField(Find.ById("q")).TypeText(" ");
browser.Image(Find.ById("btnZzk")).Click();
Console.WriteLine(browser.ContainsText("囧月"));
}
Console.Read();
}

 

在Visual Studio单元测试中运行

在使用vs单元测试中一般会用到以下Attribute:

  • AssemblyInitialize/AssemblyCleanup:程序集加载之后/程序集卸载之前
  • ClassInitialize/ClassCleanup:类加载之后/类卸载之前
  • TestInitialize/TestCleanup:每个测试方法运行之前/之后
  • TestClass:每个测试的类都必须有这个属性
  • TestMethod:每个测试的方法都必须有这个属性

在测试过程中还会用到各种Assert类型来对结果进行校验,更多参考:http://msdn.microsoft.com/zh-cn/library/ms243147(v=vs.80).aspx#中国(简体中文)

先来个简单的Google搜索测试:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using WatiN.Core; namespace TestProject
{
[TestClass]
public class GoogleTests
{
[TestMethod, STAThread]
public void Search_for_watin_on_google_the_old_way()
{
using (var browser = new IE("http://www.google.com.hk"))
{
browser.TextField(Find.ByName("q")).TypeText("WatiN");
browser.Button(Find.ByName("btnK")).Click();
Assert.IsTrue(browser.ContainsText("WatiN"));
}
}
}
}

以上是老版本的测试代码,在新版本中还支持一种自定义的Page,把HTML元素作为Page的字段并用FindByAttribute进行标识,可以最大程度做到代码重用:

[Page(UrlRegex = "www.google.*")]
public class GoogleSearchPage : Page
{
[FindBy(Name = "q")]
public TextField SearchCriteria; [FindBy(Name = "btnK")]
public Button SearchButton;
}

现在,测试代码变成了:

        [TestMethod, STAThread]
public void Search_for_watin_on_google_using_page_class()
{
using (var browser = new IE("http://www.google.com.hk"))
{
var searchPage = browser.Page<GoogleSearchPage>();
searchPage.SearchCriteria.TypeText("WatiN");
searchPage.SearchButton.Click();
Assert.IsTrue(browser.ContainsText("WatiN"));
}
}

还可以更进一步的达到代码重用:

        [TestMethod, STAThread]
public void Page_with_an_action()
{
using (var browser = new IE("http://www.google.com.hk"))
{
browser.Page<GoogleSearchPage>().SearchFor("WatiN");
Assert.IsTrue(browser.ContainsText("WatiN"));
}
} [Page(UrlRegex = "www.google.*")]
public class GoogleSearchPage : Page
{
[FindBy(Name = "q")]
public TextField SearchCriteria; [FindBy(Name = "btnK")]
public Button SearchButton; public void SearchFor(string searchCriteria)
{
SearchCriteria.TypeText("WatiN");
SearchButton.Click();
}
}

不过可惜的是FindByAttribute不支持自定义属性,所以,在需要用到自定义属性的时候就不能用FindByAttribute,而要改用Find类型提供的方法:

[Page(UrlRegex = "www.google.*")]
public class GoogleSearchPage : Page
{
public TextField SearchCriteria
{
get { return Document.TextField(Find.ByName("q")); }
} public Button SearchButton
{
get { return Document.Button(Find.ByName("btnK")); }
} public void SearchFor(string searchCriteria)
{
SearchCriteria.TypeText("WatiN");
SearchButton.Click();
}
}

 

从已有的窗口返回IE实例

主要使用AttachTo方法,查找已经打开的窗口返回IE实例:

        [TestMethod, STAThread]
public void Attach_should_return_MyIE_instance()
{
new IE("www.google.com.hk") { AutoClose = false };
var myIe = Browser.AttachTo<IE>(Find.ByTitle("Google"));
Assert.IsNotNull(myIe);
Assert.IsTrue(myIe.Title.StartsWith("Google"));
myIe.Close();
}

还可以自定义IE类型:

    public class MyIE : IE
{
public MyIE(string url) : base(url) { }
public MyIE(IEBrowser browser) : base(browser) { }
public string MyDescription
{
get
{
return Title + " opened by 囧月 " + Url;
}
}
}
public class AttachToMyIEHelper : AttachToIeHelper
{
protected override IE CreateBrowserInstance(IEBrowser browser)
{
return new MyIE(browser);
}
}

然后通过注册AttachHelper来返回自定义IE实例:

    [TestClass]
public class MyIEAttachToHelperExample
{
static MyIEAttachToHelperExample()
{
Browser.RegisterAttachToHelper(typeof(MyIE), new AttachToMyIEHelper());
} [TestMethod, STAThread]
public void Attach_should_return_MyIE_instance()
{
new IE("www.google.com.hk") { AutoClose = false };
var myIe = Browser.AttachTo<MyIE>(Find.ByTitle("Google"));
Assert.IsNotNull(myIe);
Assert.IsTrue(myIe.MyDescription.StartsWith("Google"));
Assert.IsTrue(myIe.MyDescription.Contains("囧月"));
Assert.IsTrue(myIe.MyDescription.EndsWith(myIe.Url));
myIe.Close();
}
}

 

共享同一个IE实例

很多时候想要置创建一个IE实例,然后扎起多个测试方法中共享IE实例,那么就很可能有这种代码:

    [TestClass]
public class ProblemWithSharingTests
{
private static IE ie; [ClassInitialize]
public static void testInit(TestContext testContext)
{
ie = new IE("http://lwme.cnblogs.com");
} [TestMethod]
public void testOne()
{
Assert.IsTrue(ie.ContainsText("囧月"));
} [TestMethod]
public void testTwo()
{
Assert.IsTrue(ie.ContainsText("囧月"));
}
}

但是在运行里面会发现其中有一个测试会运行失败,在官方的例子中给出了一个解决方法,先定义如下类型:

    public class IEStaticInstanceHelper
{
private IE _ie;
private int _ieThread;
private string _ieHwnd; public IEStaticInstanceHelper()
{
Console.WriteLine("created");
}
public IE IE
{
get
{
var currentThreadId = GetCurrentThreadId();
Console.WriteLine(currentThreadId + ", was:" + _ieThread);
if (currentThreadId != _ieThread)
{
_ie = IE.AttachTo<IE>(Find.By("hwnd", _ieHwnd));
_ieThread = currentThreadId;
}
return _ie;
}
set
{
_ie = value;
_ieHwnd = _ie.hWnd.ToString();
_ieThread = GetCurrentThreadId();
}
} private int GetCurrentThreadId()
{
return Thread.CurrentThread.ManagedThreadId;
}
}

每次在获取IE实例的时候判断线程ID是不是当前线程ID,如果不是则通过AttachTo方法获取已有窗口再返回,从而解决了由于共享IE实例导致测试失败的错误。

新的测试代码如下:

    [TestClass]
public class UnitTest
{
private static IEStaticInstanceHelper ieStaticInstanceHelper;
private static int _ieThread; [ClassInitialize]
[STAThread]
public static void testInit(TestContext testContext)
{
ieStaticInstanceHelper = new IEStaticInstanceHelper();
Settings.AutoStartDialogWatcher = false;
ieStaticInstanceHelper.IE = new IE("http://lwme.cnblogs.com");
_ieThread = Thread.CurrentThread.ManagedThreadId;
} public IE IE
{
get { return ieStaticInstanceHelper.IE; }
set { ieStaticInstanceHelper.IE = value; }
} [ClassCleanup]
[STAThread]
public static void MyClassCleanup()
{
ieStaticInstanceHelper.IE.Close();
ieStaticInstanceHelper = null;
} [TestMethod]
[STAThread]
public void testOne()
{
lock (this)
{
Assert.AreEqual(_ieThread, Thread.CurrentThread.ManagedThreadId);
Assert.IsTrue(IE.ContainsText("囧月"));
}
} [TestMethod]
[STAThread]
public void testTwo()
{
lock (this)
{
Assert.AreNotEqual(_ieThread, Thread.CurrentThread.ManagedThreadId);
Assert.IsTrue(IE.ContainsText("囧月"));
}
}
[TestMethod]
[STAThread]
public void testThree()
{
lock (this)
{
Assert.AreNotEqual(_ieThread, Thread.CurrentThread.ManagedThreadId);
Assert.IsTrue(IE.ContainsText("囧月"));
}
}
}

 

运行javascript

browser或者html元素的DomContainer都有Eval/RunScript方法用以运行脚本,其中Eval可以获取从js返回的值。

        [TestMethod, STAThread]
public void test_javascript()
{
using (var browser = new IE("http://www.google.com.hk/"))
{
var now = DateTime.Now;
var q = browser.TextField(Find.ByName("q"));
var jsobjref = "document.querySelector('input[name=q]')";
Assert.IsTrue(string.IsNullOrEmpty(browser.Eval(jsobjref + ".value")));
browser.RunScript(jsobjref + ".value='" + now.ToShortDateString() + "';");
Assert.AreEqual(now.ToShortDateString(), browser.Eval(jsobjref + ".value"));
browser.RunScript(jsobjref + ".value='囧月';");
Assert.AreEqual("囧月", browser.Eval(jsobjref + ".value"));
}
}

对于ajax的测试也是依赖这两个方法。

 

弹出对话框

假如存在以下的服务端代码用于登录:

protected void doLogin_click(object sender, EventArgs e)
{
if (username.Text == "lwme" && password.Text == "lwme")
{
ClientScript.RegisterStartupScript(this.GetType(), "login", "alert('登录成功');", true);
}
else
{
ClientScript.RegisterStartupScript(this.GetType(), "login", "alert('登录失败');", true);
}
}

那么就可以这样测试登录逻辑:

        [TestMethod, STAThread]
public void Test_Login_success_with_dialog()
{
using (IE ie = new IE("localhost/login.aspx"))
{
AlertDialogHandler adh = new AlertDialogHandler();
ie.AddDialogHandler(adh);
ie.TextField("username").TypeText("lwme");
ie.TextField("password").TypeText("lwme");
ie.Button("doLogin").Click();
adh.WaitUntilExists();
string msg = adh.Message;
adh.OKButton.Click();
ie.WaitForComplete();
ie.RemoveDialogHandler(adh);
Assert.IsTrue(msg.Contains("登录成功!"));
}
} [TestMethod, STAThread]
public void Test_Login_failed_with_dialog()
{
using (IE ie = new IE("localhost/login.aspx"))
{
AlertDialogHandler adh = new AlertDialogHandler();
ie.AddDialogHandler(adh);
ie.TextField("username").TypeText("test");
ie.TextField("password").TypeText("test");
ie.Button("doLogin").Click();
adh.WaitUntilExists();
string msg = adh.Message;
adh.OKButton.Click();
ie.WaitForComplete();
ie.RemoveDialogHandler(adh);
Assert.IsTrue(msg.Contains("登录失败"));
}
}

 

URL跳转

假如登录之后进行url跳转:

  if (username.Text == "admin" && password.Text == "admin")
{
Response.Redirect("index.aspx");
}

那么可以这样去测试逻辑:

        [TestMethod, STAThread]
public void Test_Login_success_with_redirect()
{
using (IE ie = new IE("localhost/login.aspx"))
{
ie.TextField("username").TypeText("lwme");
ie.TextField("password").TypeText("lwme");
ie.Button("doLogin").ClickNoWait();
ie.WaitForComplete();
Assert.IsTrue(ie.Url.EndsWith("index.aspx", StringComparison.InvariantCultureIgnoreCase));
}
}

 

结尾

本文只是对WatiN功能简单的做一些介绍,更多有用的功能还有待挖掘。

话说WatiN已经好久不更新了,目前看来Visual Studio 的Coded UI Test或许是一个不错的选择。

--EOF--

使用WatiN进行UI自动化测试的更多相关文章

  1. 【转】Web UI自动化测试原理

    目前市面上有很多Web UI自动化测试框架,比如WatiN, Selinimu,WebDriver,还有VS2010中的Coded UI等等.  这些框架都可以操作Web中的控件,模拟用户输入,点击等 ...

  2. 腾讯优测优分享 | 游戏的UI自动化测试可以这样开展

    腾讯优测是专业的自动化测试平台,提供自动化测试-全面兼容性测试,云真机-远程真机租用,漏洞分析等多维度的测试服务,让测试更简单! 对于目前的两大游戏引擎cocos-2dx.unity3D,其UI自动化 ...

  3. 如何正确选择UI自动化测试

    近年流行一个词-UI,和UI搭边好像都那么高大上,软件测试行业也不例外,比如UI自动化测试. 常见的UI自动化测试程序有哪些呢? l  带UI的Unit Test,比如mock掉底层代码,仅仅测试UI ...

  4. UI自动化测试框架(项目实战)python、Selenium(日志、邮件、pageobject)

    其实百度UI自动化测试框架,会出来很多相关的信息,不过就没有找到纯项目的,无法拿来使用的:所以我最近就写了一个简单,不过可以拿来在真正项目中可以使用的测试框架. 项目的地址:https://githu ...

  5. 关于去哪儿网的UI自动化测试脚本(Python实现)

    UI自动化测试Qunar机票搜索场景访问Qunar机票首页http://flight.qunar.com,选择“单程”,输入出发.到达城市,选择today+7日后的日期,点“搜索”,跳转到机票单程搜索 ...

  6. UI自动化测试(三)对页面中定位到的元素对象做相应操作

    前两天分别讲述了UI自动化测试基础以及对页面元素该如何进行定位,这一篇自然就是对定位到的页面元素对象进行相应操作啦. 阅读目录 1.常用操作元素对象的方法 2.鼠标事件操作 3.键盘事件操作 4.We ...

  7. Selenide UI 自动化测试

       我没有拼写错误,确实不是 Selenium ,但是,只要是 Web UI 自动化测试框架,基本上都是基于Selenium 的.Selenide 也不例外.那为啥不直接用Selenium呢? 因为 ...

  8. django+appium实现UI自动化测试平台---构思版

             背景 UI自动化,在进行的过程中,难免会遇到平台化, 在实际的工作中,有的领导也会想要实现自动化测试的平台化.自动化平台化后,有了更为实际的成果, 在做UI自动化,很想吧现在的自动化 ...

  9. <自动化测试方案_7>第七章、PC端UI自动化测试

    第七章.PC端UI自动化测试 UI自动化测试又分为:Web自动化测试,App自动化测试.微信小程序.微信公众号UI层的自动化测试工具非常多,比较主流的是UFT(QTP),Robot Framework ...

随机推荐

  1. mssql

    1.打开php.ini,将 ;extension=php_mssql.dll前面的分号(;)去掉,然后重启 Apache. 如果不行的话,进行第2步: 2.检查一下你的php安装目录下的ext下面有没 ...

  2. 使用cookie实现打印浏览记录的功能

    可以用cookie知识来实现打印浏览记录.这里面用到的思路是将浏览记录以字符串的方式保存到cookie中,当浏览记录增加时,再将其转化为数组. $uri=$_SERVER['REQUEST_URI'] ...

  3. Test注解的两个属性(转)

    Test注解的两个属性:expected和timeout Junit的Test注解支持两个可选的参数expected和timeout.expected声明一个测试方法必须抛出一个异常.如果不抛出异常或 ...

  4. MongoDB基本管理命令

    MongoDB是一个NoSQL数据库系统:一个数据库可以包含多个集合(Collection),每个集合对应于关系数据库中的表:而每个集合中 可以存储一组由列标识的记录,列是可以自由定义的,非常灵活,由 ...

  5. tomcat 性能优化

    tomcat 性能优化tomcat默认参数是为开发环境制定,而非适合生产环境,尤其是内存和线程的配置,默认都很低,容易成为性能瓶颈. tomcat内存优化linux修改TOMCAT_HOME/bin/ ...

  6. js判断字符串中是否含有指定汉语

    核心代码: function haveChinese(str,c){ if(escape(str).indexOf(escape(c))!=-1){ retrun true; } return fal ...

  7. mouseover、mouseout,mouseenter、mouseleave区别

    心情压抑的一天,我想好好的. mouseover与mouseenter 不论鼠标指针穿过被选元素或其子元素,都会触发 mouseover 事件. 只有在鼠标指针穿过被选元素时,才会触发 mouseen ...

  8. CSV的导入导出

    using System; using System.Data; using System.IO; namespace COMMON { public class CSVhelperClass { / ...

  9. 一句话的代码,从集合中找出第一个重复字符的方法javascript版。

    有的时候需求是这样的: 找出集合中第一个重复的字符所在的位置,刚才看了园内某自许为算法的代码,感觉非常之啰嗦故写了以下代码! 本人对神马算法之类的完全不懂,但那些伪算法家们也别出来装蒜.一句话:不要欺 ...

  10. Mysql 分段统计

    今天遇到个小问题觉得挺有意思,与大家分享. 需求是这样的,对数据库中的一张表做按时间的分段统计,结果只要每个区间的数量. select YEAR(create_time) as nian,MONTH( ...