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. Java 项目优化实战

    https://blog.coding.net/blog/java-coding-performance 1 Visual VM 2 优化一 2.1 背景 2.2 原实现 2.3 剖析 2.4 方案 ...

  3. java 学习写架构必会几大技术点

    java 学习写架构必会几大技术点 关于学习架构,必须会的几点技术 1. java反射技术 2. xml文件处理 3. properties属性文件处理 4. 线程安全机制 5. annocation ...

  4. jetty服务器启动方法总结【备用】

    1. 使用Java命令启动 java -jar start.jar ctrl + c 关闭 终端窗口一直存在 2. 使用Java命令启动2 java -jar start.jar & 启动成功 ...

  5. ES6-set && 数组剔重

    set Set:ES6中提供的新的数据结构set.特点:1.类似数组,属性值时唯一的!!2.Set本身是一个构造函数,用来生成数据结构,表现形式{1,"3",78},是个数据集合 ...

  6. Django + Apache + 树莓派 搭建内网微信公众号服务器

    其实早在微信开放公众号开发平台时就想弄一个自己的公众号服务器,奈何对web服务器搭建和开发一窍不通,只是注册了一下开发者帐号,并没有采取行动,万恶的拖延症. 前一年,开始接触python,打开了神奇世 ...

  7. 长轮询(long polling)

    HTTP请求不是持续的连接,你请求一次,服务器响应一次,然后就完了.长轮训是一种利用HTTP模拟持续连接的技巧.具体来说,只要页面载入了,不管你需不需要服务器给你响应信息,你都会给服务器发一个Ajax ...

  8. DB2死锁的解决办法

    db2 get snapshot for locks on sampledb2 get db cfg for sampledb2 update db cfg using dlchktime 10000 ...

  9. 【转】Nginx服务器详细配置含注释

    #使用的用户和组 user www www; #指定工作衍生进程数(一般等于CPU的总核数或总核数的两倍) worker_processes 8; #指定错误日志存放的路径,错误日志的记录级别可为de ...

  10. JAVA 异常类

    1.Exception(异常) :是程序本身可以处理的异常. 2.Error(错误): 是程序无法处理的错误.这些错误表示故障发生于虚拟机自身.或者发生在虚拟机试图执行应用时,一般不需要程序处理. 3 ...