引子
2011年初来公司实习的时候,接的第一份活就是维护UI自动化用例,从此开始我轰轰烈烈的Tester生涯,此处省略十万字。。。
经历第一代UI自动化的没落,DWR接口测试的兴起,以及直接参与项目组的功能测试,最终又回到了一年前的原点。
思考良多,苦逼地推出了第二代UI自动化框架,大名Dagger。
废话少说,先讲技术选型
由于历史传统,Selenium2.0成为不二选择,Selenium2.0是Selenium1.0与WebDriver的合体,新框架Dagger是以WebDriver为基础。
再讲设计思想和定位
新框架之所以取名Dagger(**),就是希望它和**一样轻便灵巧,框架专注于实现各种Web操作的封装。其他的外围,如数据库连接,分布式执行,持续集成等则根据情况添加,将开发,维护,使用,分析用例的成本尽可能降低。
打个比方,**绑在木棍上就是长矛,装在步枪上就是刺刀,可以随机应变。因此,Dagger是如此之小,小到核心类只有3个,架包2个。
框架小巧方便,则可以把主要精力放在用例的实现和组织上,使用例尽量贴近业务。
怎样写用例,怎样封装xpath,怎样组织用例,才可以使写用例和维护用例成本最低?不仅仅是技术问题,更多的是实践经验的积累。
抛砖引玉,关于UI自动化的一些设想
一次性用例,主要用于验证功能是否正常上线;
SeleniumIDE,是Selenium自带的自动化录制工具,支持将录制用例直接输出为JAVA代码。充分掌握这一工具后,可以大幅提高写用例的效率;
一个用例就是一个类,内含所须的元素定位Xpath和各种驱动数据;
优势:不同的人同时写用例不会互相干扰,责任清晰明确。
缺点:潜在的维护成本。
责任人制度(很多时候用例的成效上不去,是因为用例挂了以后没有责任人及时分析,反馈维护);
核心的,稳定的功能,用例写的规范些,便于阅读和维护;
普通的,易变的功能,用例写的随便些,挂了就重写一个;
UI自动化用于回归,要像脚本,不要像代码,就是手动回归的脚本化;
用例傻瓜化,容易写容易读容易维护;
一个用例一组专用账号,方便隔离和数据准备,也便于后续的用例分布式并行执行;
最关键的一点,自动化必须贴合业务!
下面上代码(节约版面,部分节选)
架包只有2个:
selenium-server-standalone-2.18.0.jar
testng-6.3.1.jar
框架核心类3个:
BrowserEmulator 浏览器类,一个浏览器的抽象
GlobalSettings 所有的配置项都统一放在这里
PageElement 页面元素类
先讲BrowserEmulator
宾老板曾问我,为啥要把Selenium再封装一层,变成BrowserEmulator这个类?宾老板永远是那么的犀利,吓得我颇心虚。幸好我是有备而来,理由有三:
1. Selenium本身提供了大量API,这是好事吗?我觉得不是,功能强大是好,但里面95%的API都不是常用的,徒增烦恼罢了,因此加一层封装;
2. Selenium2.0(Webdriver)有不少Bug和缺陷,谁用谁知道!因此要加一层封装,补全之。
3. 加了一层封装以后,哪怕Selenium大变样甚至以后不再用Selenium了,也可以保证BrowserEmulator对外提供接口的稳定性。
代码如下,穿插说明。
/**
* Selenium2.0二次封装
* @author 尘泥
*/
public class BrowserEmulator {
WebDriver BrowserCore;
WebDriverBackedSelenium Browser;
这里有一个三层体系,最里面的是WebDriver,外面包一层WebDriverBackedSelenium,最后再包一层用户直接接触的BrowserEmulator。看官可以在下文看到有些API是基于WebDriver实现的,有些则是基于WebDriverBackedSelenium,看起来很囧,不是么?理论上,所有的Web操作只用WebDriver相关的API就可以实现,但是,亲,要自己写代码实现哦!很麻烦,考虑到这一点,Selenium2.0本身就提供WebDriverBackedSelenium类,把WebDriver类包装一层,并对外提供大量类似于Selenium1.0的API,用起来很爽,是不是?不是,因为WebDriverBackedSelenium模拟了Selenium1.0的API,但模拟比较蹩脚,有些API有Bug,有些则根本没实现。所以,还得回过来靠WebDriver实现。一会要用WebDriverBackedSelenium,一会要用WebDriver,可以想见,如果不在最外面封装一层BrowserEmulator,用户使用起来会是何等的苦逼!?当然了,熟练的自动化测试工程师不在此例。
ChromeDriverService ChromeServer;
要使用Chrome浏览器的话,还必须起一个server。不过即便要多起一个server,chromedriver还是比firefoxdriver启动快得多!
JavascriptExecutor js;
加个JS执行器,有些东西还得靠JS直接来实现,详见下文。
public BrowserEmulator() {
chooseBrowserCoreType(GlobalSettings.BrowserCoreType);
根据配置参数选择浏览器类型,目前只支持FFdriver和Chromedriver,理论上,还可以支持IEdriver和htmlUnitdriver,但实际上,虽然它们都是实现了webdriver接口,但是实现的具体代码各不相同,导致一些Web操作在不同driver之间有兼容性问题。具体问题,下文将有涉及。
Browser = new WebDriverBackedSelenium(BrowserCore,"www.163.com");
Browser.setSpeed(GlobalSettings.StepInterval);
// TODO 这里Selenium2.0存在Bug,setSpeed()实际上无法设置运行速度。
Browser.setTimeout(GlobalSettings.Timeout);
js = (JavascriptExecutor) BrowserCore;
}
…
…
/**
* 在iframe中输入文本
* @param locator Xpath Of Frame
* @param Text
*/
这个方法目前只支持FF,chrome下不行,解决中。。。
public void typeInFrame(String locator, String Text) {
pause();
waitForElementPresent(locator);
// 进入指定iframe
WebElement myframe =BrowserCore.findElement(By.xpath(locator));
BrowserCore.switchTo().frame(myframe);
// 进入编辑节点
WebElement editable =BrowserCore.switchTo().activeElement();
editable.sendKeys(Text);
// 返回
BrowserCore.switchTo().defaultContent();
}
/**
* Robot敲击键盘
* @param KeyCode
*/
出于安全性考虑,有些Web操作要求监听“真正的”键盘事件,这种情况应该也是比较常见的。所以这里使用java原生提供的Robot实现模拟键盘事件。而且键盘可以帮我们解决很多问题,比如,我按一下F5就可以实现页面刷新,那就不须要额外再为刷新页面写一个API了,又可以使代码简洁一些了。很多时候不能依赖框架提供这样那样几乎万能的API,其实自己完全可以想办法绕过去的。
public void pressKeyboard(int KeyCode) {
pause();
Robot rb = null;
try {
rb = new Robot();
} catch (AWTException e) {
e.printStackTrace();
}
rb.keyPress(KeyCode); // 按下按键
rb.delay(100); // 保持100毫秒
rb.keyRelease(KeyCode); // 释放按键
}
/**
* Robot控制鼠标
* @param positionX
* @param positionY
* @param KeyCode
*/
原理同上一个API
这个API主要是为Flash自动化准备的。处于安全性的考虑,如果Flash操作将导致页面离开这个Flash or 会有表单提交之类的,那么Flash安全框架会强制要求这一次click必须是“真实的”鼠标点击。
public void pressMouse(int positionX, int positionY, int KeyCode) {
}