selenium_一种基于Page Object的实现及常见异常处理

原创 2017-06-22 mekhidu 腾讯移动品质中心TMQ
前言
Selenium的使用本身十分容易,配置好环境后,即可选择自己熟悉的语言快速的编写脚本。花费力气引入设计模式增加首次构建难度,主要是为了以下两点:
1、减少维护成本。
Web产品往往界面变动频繁,如果每次更新后都需要花大量时间更新用例,自动化测试的收益大大降低。长期使用问题会越积越多,最后导致自动化方案破产。良好的设计模式能够减少重复代码,将元素操作与用例实现隔离开来,增加用例层的可读性,减少元素属性变化带来的测试用例重构工作,使得用例维护更加容易。
2、增加用例稳定性。
现在的Web前端存在大量AJAX与DOM元素操作,如果采用简单的面条代码编写用例,需要重复地去解决前端的AJAX等待和元素刷新等操作带来的StaleElementReference、NoSuchElement、NotClickable等异常。

Page Object设计模式简介

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

image.png

举个简单的例子,假设待测产品包含两个页面:登录页、个人中心。需要编写一个测试用例,实现用户登录并且点击签到按钮的过程。
用传统的线性方法实现时,用例逻辑、元素查找、元素locator等混在一起。如果是做冒烟测试或者简单页面的测试可以选择这种方式:

image.png

采用Page Object模式实现该用例的简要步骤如下:
第一步,为每个页面创建一个Class,页面Class内包含此页面元素的定位和行为:

image.png
image.png

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

image.png

虽然初始构建工作量更大,但是在产品变更频繁时,使用PageObject模式的优点明显:
1、测试用例的代码和页面元素操作的代码分离,用例可读性
2、同一个元素的定位器不会出现在多个用例中,元素变更时只需要修改元素所在页面类
3、登录的方法可以复用于多个用例中,如果产品登录流程发生变动,只需要修改登录页登录方法的实现。
为了在页面Class里更好的管理定位器和减少元素查找的代码量,Selenium提供了PageFactory类,使得我们在实现页面类时只需要用注解描述元素定位即可。调用元素的各个方法时,工厂类会自动根据定位器实时查找元素,在减少代码量的同时还能够帮忙避免DOM刷新带来的不稳定,详细使用见selenium官网。

Page Object基于控件的实现

既然PageObject设计模式这么棒,那为什么不直接拿来用呢?原因是在自动化工程的建设过程中,作者发现不同页面之间的DOM元素存在复用,页面并不是最小的UI单元,控件才是。这也是目前许多Web产品的特征,基于一套开源或者自研的前端控件库,页面结构由控件组成。同一控件的不同实例DOM结构类似,用户在页面上的操作可以看作是对各类控件动作的组合。
对这种类型的Web产品如果直接采用Page Object模式构建自动化测试,会导致在页面Class里重复对同一种控件的内部元素进行解析和操作,造成了大量重复代码并且加大了维护的难度。因此本次自动化测试工程的构建从控件出发,对每个控件的属性和方法进行封装,通过不同控件方法的组合来模拟用户操作,通过对用户操作的组合实现用例的自动化。如下图,自动化测试框架现在从下往上分为三层。

image.png

控件层,对前端所用控件在自动化工程中的抽象,继承自BaseConrol基类,基类中包含返回元素引用的方法getControl、查找子元素的方法getChildElement、等待元素加载的方法waitElementLoad等。派生的控件类包含各自特殊功能的实现,如小说目录包含翻页、按钮功能包含点击、导航栏包括后退等。
工具类层,包含两部分。
第一部分是一个控件查找类ControlFinder,可以根据XPath、CSS Selector等返回对应的控件。
第二部分是按照功能区划分的helper类,以本产品为例,按照找书、读书、个人中心等功能区创建了SearchHelper、ReadHelper、UserCenterHelper等工具类。这些类继承自BaseHelper,各个helper内通过对各种控件方法的组合对用户常见动作进行模拟。
用例层,继承自BaseStory,BaseStory内包含了用例执行前准备、用例结束后数据清理、失败截图、driver管理等功能。Story的划分按照产品用例类别进行划分,每个Story中包含多个用例,用例的编写依靠对工具层用户行为的组合。控件层和工具类层的实现在下一章结合具体问题给出。
基于这种模式,最后实现的用例如下:

image.png

提高测试稳定性
AJAX异步和DOM元素更新给Web自动化测试的稳定性带来了巨大的挑战,下面列举了常见的几个问题和它们在该模式下的解决办法。
1、查找元素时遇到NoSuchElementException
出现此问题一般是因为Selenium的查找操作在元素加载之前就已经结束。许多测试脚本在这里会选择用Thread.sleep()来等待元素加载,但是等待时间如果选大了浪费,选小了元素可能还没来的及加载。这种问题在此设计模式中可以用工具类ControlFinder集中解决,在根据定位器查找控件时,等待控件元素在页面出现,然后再返回对应的控件。

image.png

这里举的例子为每个控件都创建了一个查找方法。如果被测产品的控件提供返回控件名的方法,ControlFinder可以在查找到元素后,用javascript调用该方法返回控件名称,然后通过反射返回对应控件的实例。这样能实现一个查找方法查找多种控件。
2、StaleElementReference
这是Selenium测试脚本常见的异常,出现此问题的原因一般是所操作的DOM元素被刷新了。如下图的搜索页面的提示词,红框圈出来的提示词在页面中的层级一致,元素属性一致,但是从左图到右图,该提示词所在的DOM元素其实是被刷新过,两个看起来一样的元素在Selenium看来有着不一样的element id。就像两个人即使长得一样,但是身份证不一样,并不是同一个人。

image.png

现实现这样一个用例:
1)搜索“雪”,检查第一个提示词。
2)然后再输入“中”,检查第一个提示词。
问题写法如下:

image.png

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

image.png

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

image.png

下面看一下这个问题在该设计模式下的处理方法:
首先在控件基类中定义一个getControl()方法,此函数根据控件的定位器查找控件,并等待控件可见。

image.png

然后编写Label控件的类,Label控件包含一个检查文本的方法,该方法在默认等待时间内循环检查控件的文本。
1)如果元素查找和获取元素文本都发生在DOM刷新之前,那么获取的文本是刷新前文本,循环继续。
2)如果DOM刷新发生在元素查找和获取元素文本之间,则抛出异常。异常被处理,程序继续循环,下一次即可正常获取文本的值。
3)如果元素查找和获取元素文本都发生在DOM刷新之后,程序获取到最新值,检查通过。
4)如果DOM刷新超时,Assert不通过。
这样当DOM刷新后,测试程序马上获取到更新后的文本。如果超过规定的响应时间,也认为是待测产品异常,用例不通过。

image.png

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

image.png

3、Element is not clickable
出现这个问题一般有三种原因:
1)该元素处于非点击状态
2)该元素被其它元素遮挡
3)该元素处于浏览器窗口外
为了规避第一种情况,在实现控件的点击方法时,应该先等待其变成可点击状态。
对于第二种情况,在浏览器上这种被遮挡的元素本来就不应该对其进行操作,应该尽量避免操作被遮盖的元素。
第三种情况是使用ChromeDriver时特有的异常,点击视野外的元素时有一定概率触发异常,官方傲娇的表示不修复此问题。为了提高这里的稳定性,在点击之前应该利用Selenium的Action移动到待点击元素。
综上,一个安全的点击事件应该长这样:

image.png

@暴雨-感谢腾讯分享的干货,思想刚好一致!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,510评论 25 707
  • 1.测试与软件模型 软件开发生命周期模型指的是软件开发全过程、活动和任务的结构性框架。软件项目的开发包括:需求、设...
    Mr希灵阅读 21,938评论 7 278
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,728评论 1 92
  • Selenium入门 欢迎阅读Selenium入门讲义,本讲义将会重点介绍Selenium的入门知识以及Selen...
    厲铆兄阅读 11,855评论 3 68
  • 这是和菲菲一起度过的第二个情人节。正如菲菲所说:Happy wife,happy life。有快乐的妻子,就会有快...
    windroc阅读 311评论 0 0