Web 自动化:一种基于 Page Object 的实现及常见异常处理

作者:mekhidu

团队:腾讯移动品质中心TMQ

前言

Selenium的使用本身十分容易,配置好环境后,即可选择自己熟悉的语言快速的编写脚本。花费力气引入设计模式增加首次构建难度,主要是为了以下两点:

1、减少维护成本。

Web产品往往界面变动频繁,如果每次更新后都需要花大量时间更新用例,自动化测试的收益大大降低。长期使用问题会越积越多,最后导致自动化方案破产。良好的设计模式能够减少重复代码,将元素操作与用例实现隔离开来,增加用例层的可读性,减少元素属性变化带来的测试用例重构工作,使得用例维护更加容易。

2、增加用例稳定性。

现在的Web前端存在大量AJAX与DOM元素操作,如果采用简单的面条代码编写用例,需要重复地去解决前端的AJAX等待和元素刷新等操作带来的StaleElementReference、NoSuchElement、NotClickable等异常。

一、Page Object设计模式简介

Page Object设计模式是Selenium官网推荐的一种自动化构建模式。PageObject设计模式对网页进行一个简单抽象,将每个页面设计成一个类,页面元素定位、元素操作、用户行为都被封装进对应的类。编写测试用例时不再直接操作页面元素,而是调用对应页面类的方法。使得测试人员在编写用例时能更多的关注业务逻辑,而不是页面结构与元素。

举个简单的例子,假设待测产品包含两个页面:登录页、个人中心。需要编写一个测试用例,实现用户登录并且点击签到按钮的过程。

用传统的线性方法实现时,用例逻辑、元素查找、元素locator等混在一起。如果是做冒烟测试或者简单页面的测试可以选择这种方式:

采用Page Object模式实现该用例的简要步骤如下:

第一步,为每个页面创建一个Class,页面Class内包含此页面元素的定位和行为:

第二步,调用创建的页面Class来编写用例:

虽然初始构建工作量更大,但是在产品变更频繁时,使用PageObject模式的优点明显:

1、测试用例的代码和页面元素操作的代码分离,用例可读性;

2、同一个元素的定位器不会出现在多个用例中,元素变更时只需要修改元素所在页面类;

3、登录的方法可以复用于多个用例中,如果产品登录流程发生变动,只需要修改登录页登录方法的实现。

为了在页面Class里更好的管理定位器和减少元素查找的代码量,Selenium提供了PageFactory类,使得我们在实现页面类时只需要用注解描述元素定位即可。调用元素的各个方法时,工厂类会自动根据定位器实时查找元素,在减少代码量的同时还能够帮忙避免DOM刷新带来的不稳定,详细使用见selenium官网。

二、Page Object基于控件的实现

既然PageObject设计模式这么棒,那为什么不直接拿来用呢?原因是在自动化工程的建设过程中,作者发现不同页面之间的DOM元素存在复用,页面并不是最小的UI单元,控件才是。这也是目前许多Web产品的特征,基于一套开源或者自研的前端控件库,页面结构由控件组成。同一控件的不同实例DOM结构类似,用户在页面上的操作可以看作是对各类控件动作的组合。

对这种类型的Web产品如果直接采用Page Object模式构建自动化测试,会导致在页面Class里重复对同一种控件的内部元素进行解析和操作,造成了大量重复代码并且加大了维护的难度。因此本次自动化测试工程的构建从控件出发,对每个控件的属性和方法进行封装,通过不同控件方法的组合来模拟用户操作,通过对用户操作的组合实现用例的自动化。如下图,自动化测试框架现在从下往上分为三层。

控件层,对前端所用控件在自动化工程中的抽象,继承自BaseConrol基类,基类中包含返回元素引用的方法getControl、查找子元素的方法getChildElement、等待元素加载的方法waitElementLoad等。派生的控件类包含各自特殊功能的实现,如小说目录包含翻页、按钮功能包含点击、导航栏包括后退等。

工具类层,包含两部分。

第一部分是一个控件查找类ControlFinder,可以根据XPath、CSS Selector等返回对应的控件。

第二部分是按照功能区划分的helper类,以本产品为例,按照找书、读书、个人中心等功能区创建了SearchHelper、ReadHelper、UserCenterHelper等工具类。这些类继承自BaseHelper,各个helper内通过对各种控件方法的组合对用户常见动作进行模拟。

用例层,继承自BaseStory,BaseStory内包含了用例执行前准备、用例结束后数据清理、失败截图、driver管理等功能。Story的划分按照产品用例类别进行划分,每个Story中包含多个用例,用例的编写依靠对工具层用户行为的组合。控件层和工具类层的实现在下一章结合具体问题给出。

基于这种模式,最后实现的用例如下:

三、提高测试稳定性

AJAX异步和DOM元素更新给Web自动化测试的稳定性带来了巨大的挑战,下面列举了常见的几个问题和它们在该模式下的解决办法。

1、查找元素时遇到NoSuchElementException

出现此问题一般是因为Selenium的查找操作在元素加载之前就已经结束。许多测试脚本在这里会选择用Thread.sleep()来等待元素加载,但是等待时间如果选大了浪费,选小了元素可能还没来的及加载。这种问题在此设计模式中可以用工具类ControlFinder集中解决,在根据定位器查找控件时,等待控件元素在页面出现,然后再返回对应的控件。

这里举的例子为每个控件都创建了一个查找方法。如果被测产品的控件提供返回控件名的方法,ControlFinder可以在查找到元素后,用javascript调用该方法返回控件名称,然后通过反射返回对应控件的实例。这样能实现一个查找方法查找多种控件。

2、StaleElementReference

这是Selenium测试脚本常见的异常,出现此问题的原因一般是所操作的DOM元素被刷新了。如下图的搜索页面的提示词,红框圈出来的提示词在页面中的层级一致,元素属性一致,但是从左图到右图,该提示词所在的DOM元素其实是被刷新过,两个看起来一样的元素在Selenium看来有着不一样的element id。就像两个人即使长得一样,但是身份证不一样,并不是同一个人。

现实现这样一个用例:

1)搜索“雪”,检查第一个提示词。

2)然后再输入“中”,检查第一个提示词。

问题写法如下:

上图代码在第二次调用hintWord.getText()时,DOM元素已经被刷新,hintWord所指向的DOM元素已经不是最初的那个元素了,因此会抛出异常。这类解决思路是,在输入“鹰”之后,重新查找一次该元素,于是有以下代码:

但是上面的代码运行过程中还是会有一定概率抛出

StaleElementReferenceException,抛出异常时的执行顺序如下图。脚本输入“中”之后,前端准备更新提示词,在更新完成之前第二次查找提示词元素的脚本已经执行了,这是hintWord还是指向ID=1的元素,然后前端完成DOM更新,测试脚本调用hintWord.getText()。因为此时ID=1的元素已经不在页面上了,所以程序出错。

下面看一下这个问题在该设计模式下的处理方法:

首先在控件基类中定义一个getControl()方法,此函数根据控件的定位器查找控件,并等待控件可见。

然后编写Label控件的类,Label控件包含一个检查文本的方法,该方法在默认等待时间内循环检查控件的文本。

1)如果元素查找和获取元素文本都发生在DOM刷新之前,那么获取的文本是刷新前文本,循环继续。

2)如果DOM刷新发生在元素查找和获取元素文本之间,则抛出异常。异常被处理,程序继续循环,下一次即可正常获取文本的值。

3)如果元素查找和获取元素文本都发生在DOM刷新之后,程序获取到最新值,检查通过。

4)如果DOM刷新超时,Assert不通过。

这样当DOM刷新后,测试程序马上获取到更新后的文本。如果超过规定的响应时间,也认为是待测产品异常,用例不通过。

下面是用自定义控件实现用例的代码,通过实时查找实现了控件变量的一次声明多处调用。

3、Element is not clickable

出现这个问题一般有三种原因:

1)该元素处于非点击状态

2)该元素被其它元素遮挡

3)该元素处于浏览器窗口外

为了规避第一种情况,在实现控件的点击方法时,应该先等待其变成可点击状态。

对于第二种情况,在浏览器上这种被遮挡的元素本来就不应该对其进行操作,应该尽量避免操作被遮盖的元素。

第三种情况是使用ChromeDriver时特有的异常,点击视野外的元素时有一定概率触发异常,官方傲娇的表示不修复此问题。为了提高这里的稳定性,在点击之前应该利用Selenium的Action移动到待点击元素。

综上,一个安全的点击事件应该长这样:

获取更多测试干货,请搜索微信公众号:腾讯移动品质中心TMQ!

版权所属,禁止转载!

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏智能算法

微信小程序,开发大起底

作者简介:张智超,北京微函工坊开发工程师,CSDN微信开发知识库特邀编辑。微信小程序爱好者。 感谢@翟东平 @qq_31383345 @nigelyq 等热情参...

45413
来自专栏HTML5学堂

JavaScript | 选中并获取多行文本框内容的效果

HTML5学堂(码匠):文本操作一直是开发中不可避免的存在,用户选中的文本内容,是否可以进行获取并处理到需要的位置当中?如果可以,这样的操作到底需要使用到哪些方...

3906
来自专栏编程

10个短小却超实用的JavaScript代码段

JavaScript正变得越来越流行,它已经成为前端开发的第一选择,并且利用基于JavaScript语言的NodeJS,我们也可以开发出高性能的后端服务,甚至我...

1928
来自专栏程序员的知识天地

web前端开发规范总结

Web前端作为开发团队中不可或缺的一部分,需要按照相关规定进行合理编写(一部分不良习惯可能给自己和他人造成不必要的麻烦)。不同公司不同团队具有不同的规范和文档。...

2312
来自专栏快乐八哥

CSS3制作心形头像

1.功能需求: 最近有一个基于微信开发的Mobile Web项目,是一个活动页面。功能需求:用户使用微信扫描二维码,然后授权使用微信登录,然后读取用户的昵称和头...

23210
来自专栏腾讯IVWEB团队的专栏

React V16 给我们带来了那些东西 ?

在如今越来越复杂的前端环境下,往往可能需要加载且渲染大量的 DOM 节点,那么在渲染的过程中,即使我们使用了 React virtualDom 进行维护,但是,...

5570
来自专栏java一日一条

Jsoup代码解读之六-parser(下)

读Jsoup源码并非无聊,目的其实是为了将webmagic做的更好一点,毕竟parser也是爬虫的重要组成部分之一。读了代码后,收获也不少,对HTML的知识也更...

672
来自专栏腾讯IVWEB团队的专栏

React + Redux 组件化方案

在介绍组件化方案之前,先对 react 和 redux 做一个简单介绍。理想中的组件化,第一步应该就是组件的标签化, 例如有一个 Header 组件,无需关注...

6380
来自专栏大学生计算机视觉学习DeepLearning

2018最新mfc作为上位机接收硬件端USB或串口数据显示成图片 解决串口接收数据丢字节丢包问题

第一步:首先建立一个MFC工程,成功后会跳出一个对话框,直接在对话框上点击右键-》点击插入ACTIVAE控件-》选择MicrosoftCommunication...

3232
来自专栏nice_每一天

一天带你入门到放弃vue.js(三)

自己新建的标签赋予特殊功能的是组件,而指定是在标签上使用类似于属性,以v-name开头,v-on,v-if...是系统指令! v-是表示这是vue的指令if,f...

1261

扫码关注云+社区