Selenium 2.0的由来及设计架构(五)

上一篇 / 下一篇  2013-08-25 21:52:59 / 个人分类:功能自动化

接着上一篇继续介绍Selenium2.0架构设计,本文介绍IE driver


IE Driver


IE 是一个很有意思的浏览器,它是由一些协同工作的COM接口构建成的,这一直延伸到Javascript引擎,常见的Javascript变量实际参考了隐含的COM实例。Javascript窗口是一个IHTML窗口,文档是一个COM接口IHTML文档的实例。微软已经做了非常出色的工作通过增强浏览器来维护现有的行为。这意味着如果一个应用程序如果支持IE6的COM类,它仍可以支持IE9。


IEDriver随着时间推移体系结构也跟着演变,一个非常迫切的需求设计是为了避免安装程序,这是个不常用的需求,所以需要一些解释。首先,当一个开发人员下载个包,在很短暂的时间内,它使用 WebDriver很难通过5分钟的测试,更重要的是WebDriver用户不能在自己的机器上安装软件,这意味着当项目想在IE上测试时,大家不需要记得登陆到持续集成的服务器上运行一个安装文件,,最后, 有些语言并不需要运行安装包,常见的JAVA就是添加JAR文件路径到CLASSPATH中,并且我的经验告诉我需要安装包的那些类库并不太受欢迎。


所以,最后选择结果是不需要安装包。


在windows上编程使用的自然语言是运行于.NET上可能是C#。 通过使用在每个版本的window中都有的IE COM自动化接口,IEDriver集成了IE。特别是,我们通过调用原生的MSHTML和ShDocVw的dll文件使用COM接口,从而形成IE的一部分。在C# 4中,CLR/COM互操作性通过使用单独的主互操作程序集(PIAs)完成,PIA本质上是一个在CLR和COM上的一座桥梁。


遗憾的是,使用C# 4将意味着使用一个很新的.NET RUNTIME版本,很多公司避免走在技术的前沿,更喜欢稳定性好的和问题已知的老版本。使用C# 4我们会自动排除一定比例的用户群,使用PIA也有一些其他缺点。考虑到许可的限制,在于微软协商后,很明确的是,Selenium项目不会有权利发布PIA,无论是MSHTML或ShDocVw类库。即使被授权,每个安装的windows和IE都有一个类库的唯一组合,这意味着我们需要处理很多这样的事情,按需构建客户机的PIA也并不可行,因为他们需要一些可能在普通用户机器上并不存在的开发者工具。


所以,尽管C#是一种很有吸引力的编程语言,但不能选它。至少在与IE通信时,我们需要使用一些原生的东西。很自然的下一个选择就是C++,这也是我们最后选择的语言。使用C++有一个优势就是我们不需要使用PIA,但是它意味着我们需要重新发布Visual Studio C++ 运行的 DLL,除非我们静态链接他们。因为为了能使用那个DLL我们需要运行一个安装程序,链接类库与IE进行通信。


如果不使用安装程序确实需要支付非常高的成本,但是,回想以前复杂的方案,为了让我们的用户更容易的使用是很值得投资的。结论就是我们对当前的工作重新评估,因为为了给用户带来好处,需要付出的是能给一个高级C++开源项目做贡献的人数看起来似乎比那些贡献于同样的C#项目的人要少。


IE driver原始设计如下图:

从堆栈底你可以看到我们使用IE的COM自动化接口,为了在概念层面上更容易的应付,我们包装了这些原始的接口使用了一组C++类,深刻反映了主要的WebDriver API。为了获得与C++通信的的JAVA类,我们使用了JNI,


通过COM接口的C++ 抽象的JNI方法实现。

这种方法当JAVA是唯一的客户端语言时很适用,但是如果每种语言都需要改变底层类库的话,它就很痛苦且过于复杂。所以虽然JNI能起效,但是它不提供正确的抽象层。


什么是正确的抽象层呢?每种语言,我们想支持的每种语言都有一种直接调用C代码的机制,在C#里,需要PInvoke的形式。在Ruby里是FFI,Python是ctypes,而在JAVA里,有一个很棒的库


JNI(JAVA Native Architecture)。我们需要使用最常用标准来开发API,通过我们的对象模型,扁平化,使用一个简单的两三个字母前缀表示主接口方法,WD代表WebDriver,wde代表WebDriver Element。因此WebDriver.get变成了wdGet,WebElement.getText成了wdeGetText。每个方法返回一个整数的状态码,out参数用于表示允许函数返回值,我们不再使用像这样的方法签名:


int wdeGetAttribute(WebDriver*, WebElement*, const wchar_t*, StringWrapper**)


为了调用代码,WebDriver, WebElement 以及StringWrapper是不确定的类型,我们说明了API中的差异,使得更清楚应该使用什么值作为参数,尽管可以简单的使用void。因为我们想正确处理国际化文本,你也可以看到我们使用宽字符文本。


在JAVA端,我们通过一个接口公开了这个方法库,然后我们使它看起来像普通的面向对象的接口,例如,Java定义的getAttribute方法看起来像:


public String getAttribute(String name) {

  PointerByReference wrapper = new PointerByReference();

  int result = lib.wdeGetAttribute(

      parent.getDriverPointer(), element, new WString(name), wrapper);

  errors.verifyErrorCode(result, "get attribute of");

  return wrapper.getValue() == null ? null : new StringWrapper(lib, wrapper).toString();

}

设计图如下:

所有的测试都在本地机上运行,这样子不错,但是一旦我们在远程WebDriver上使用IE driver时,就会随机锁定,我们跟踪过这个问题是因为IE 对COM自动化接口的限制。它们这么设计是用在单线程单元模式,本质上讲,归结到一个要求就是,我们每次在同一线程中调用这个接口。在本地运行时,默认情况就是这样。然而,Java 应用程序服务器,使用多个线程处理预期负载,结果呢?我们没有办法确保所有情况下是在同一线程里访问IE driver。


对于这个问题的解决方案是将IE driver在单线程执行器里运行,在应用程序服务器里通过Futures序列化所有访问,一段时间内我们是这样设计的。但是,似乎在调用代码中如此复杂并不公平,很容易想象这样的情形,人们一不小心从多个线程中使用IE driver,我们决定降低driver本身的复杂性。在一个单独的线程中使用IE实例使用PostThreadMessage Win32 API 跨线程边界通信,因此,在写此文时,IE driver的设计看起来像下图:

这不是我们会自愿选择的那种设计,但是它具有可工作性和避免麻烦的优点,我们用户可能会选择它。


这种设计的缺点是,它很难决定IE实例是否锁定它本身。当我们与DOM交互时如果一个对话框打开了,或者在线程边界的那一边发生了灾难性的失败,可能就出现了。因此我们发送每一个线程消息都会有一个timeout超时,我们设置为相对宽松的2分钟。从用户邮件反馈来看,这个假设大致如此,但并不正确,在IE driver更高版本里把超时时间做成了可配置的。


另一个缺点是内部调试可以深入问题,但需要快递完成(毕竟,你只有两分钟时间尽可能的跟踪代码),随后跨线程使用断点以及了解期望的代码路径。不用讲,在一个开源项目里,有很多有趣的问题需要解决,很少有食欲。这显著的降低了系统的“巴士因素”,作为项目的维护者,这让我很担心。


为了解决这个问题,越来越多的IE driver的内容移植到了相同的自动化原子如Firefox driver和Selenium Core。我们通过编译计划要使用的每个原子并准备将其作为C++头文件,每一个功能都公开成一个常量,在运行时,我们准备从这些常量中执行Javascript。这种方法意味着我们可以给IE driver开发测试一定比例的代码,而不需要C编译器,也不许更多人投入定位解决错误。最后,我们的目标只是保留原生代码中交互的API,并依赖进更多的原子。


我们正研究的另一个方法是重写IE driver来使用轻量级的HTTP 服务器,可以让我们把它当成远程WebDriver。如果发生这种情况,我们可以降低很多线程边界的复杂性,减少代码总量,让控制流易用性显著增强。


本文是译文,原文请参考:Http://www.aosabook.org/en/selenium.html

===========================

关注微信zzzmmmkkk,不定期吐槽有关测试技术,测试经验,测试思考和生活感悟等。


TAG: 设计架构 IEDriver webdriver WebDriver 巴士因素

 

评分:0

我来说两句

Open Toolbar