Web自动化框架搭建

引子

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存在BugsetSpeed()实际上无法设置运行速度。

       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 positionXint positionYint KeyCode) {

    }

   

/**

     * 模拟Hover动作

     * @param locator

     * @note 暂时只支持ChromeDriver

     */

Selenium2.0本身就是原生提供mouseOver这个函数的。但是,至少在2.18.0,这个API还是一个废材。。。

    public void mouseOver(String locator) {

       pause();

       waitForElementPresent(locator);

      

以下这段代码似乎不是很灵光,优化进行中。。。

       // Hover

       WebElement we = BrowserCore.findElement(By.xpath(locator));

       Actions builder = new Actions(BrowserCore);

       builder.moveToElement(we).build().perform();

       return;

    }

   

    /**

     * Flash中点击元素

     * @param flashID     flash本身在网页中ID

     * @param targetName 目标元素在flashName

     */

关于Flash自动化,我将另开一文讲解(传送门:http://qa.blog.163.com/blog/static/19014700220121278928634/),里面会用到JS执行器。

    public void flexClick(String flashID, String targetName) {

    }

   

    /**

     * 获取Flash中元素左上角的(浏览器,非屏幕)坐标

     * @param flashID    flash本身在网页中ID

     * @param targetName 目标元素在flashName

     * @return           坐标[x,y]

     */

    public int[] flexGetPosition(String flashID, String targetName) {

    }

   

    /**

     * 设置BrowserEmulator各次操作之间的时间间隔,单位毫秒

     */

Selenium2.0原生提供setSpeed函数,用以控制用例执行速度,但是,至少至2.18.0,这个API还是个废材。。。只好自己实现一个。

基本上调试的时候会把步长设为500毫秒,真正运行时设为0毫秒。

    private void pause() {

此处使用万恶的Thread.sleep

}

 

从这里开始是几个元素定位和判断的函数,所谓用例自然要有判定,才有成功与失败之分,此处省略N行代码。。。

    /**

     * 页面元素定位

     * @param locator 暂时只支持xpath,后续考虑支持更多形式,如:idname

     */

    private void waitForElementPresent(final String locator) {

 

       int Timeout = Integer.parseInt(GlobalSettings.Timeout);

      

       try {

           new Wait() {

              public boolean until() {

                  return Browser.isElementPresent(locator);

              }

           }.wait("*** 页面元素(" + locator + ")定位失败 ***"Timeout);

       } catch (Exception e) {

           Assert.fail("*** 页面元素(" + locator + ")定位失败 ***");

           // TODO 更多自定义信息打印

       }

    }

 

再讲GlobalSettings

不说了,很短小

全局变量设置

 * @author 尘泥

 */

public class GlobalSettings {

    /**

     * 设置浏览器类型

     *     1   FireFox

     *     2   Chrome

     **/

    public static int BrowserCoreType = 2;

 

    public static String ChromeDriverPath = "res/chromedriver.exe";

   

    // BrowserEmulator各次操作之间的时间间隔,单位毫秒

    public static String StepInterval = "500";

   

    // BrowserEmulator等待超时时间,单位毫秒

    public static String Timeout = "30000";

   

}

 

最后是PageElement

对页面元素做了简单封装,可以使元素定位清晰一些。

用于页面元素定位

 * @author 尘泥

 */

public class PageElement {

 

    // 页面元素Xpath

    private String xpath = null;

    /**

     * 构造函数

     * @param tag 节点tag

     * @param attr    节点属性,如:id name class

     * @param value   属性值

     */

    public PageElement(String tag, String attr, String value) {

       this.xpath = "//" + tag + "[@" + attr + "='" + value + "']";

    }

 

    // 私有构造函数

    private PageElement(String xpath) {

       this.xpath = xpath;

    }

 

    /**

     * 加入子节点

     * @param tag 子节点tag

     * @param value   子节点属性

     * @param attr    属性值

     * @return    子节点

     */

    public PageElement nextNode(String tag, String value, String attr) {

       return new PageElement(this.xpath + "/" + tag + "[@" + attr + "='" + value + "']");

    }

 

    此处省略NnextNode函数的不同实现。

}

 

终章

除了框架核心代码以外,还提供了七八个demo用例,演示了一些写用例的惯用法。正如云风所说,学习语言就是学习它的惯用法,写用例也是一样。API仔仔细细看几眼就知道,怎么写出优质的用例来,才考验功夫。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值