写在正片开始之前----论元素定位的重要性
web页面的元素定位是UI自动化的基石,我在工作中见过无数同事使用工具获取xpath的方式进行元素定位,这样做有以下缺点:
工具获取的元素定位多为从web树状结构的根节点开始,比如这样的: /html /body /div[4] /div[5] /div[2] /div[4] /div[4] /ul[5] /li[2] /ul /a这样的定位,页面结构略有变化就会找不到这个超链接,导致后面的维护工作量巨大。(正确的定位方法请看本文中的示例)
由于不是自己分析页面结构,就无法提炼出公共的元素定位方法,无法参数化,反而效率会低。并且如果web框架进行整改,由于以上事情没有做,UI自动化的元素定位可能会大面积报错,只能一个一个去修改。
不能更好的理解前端开发设计页面的习惯,无法为之后前端开发植入可测试性代码提出针对性建议。
起因
(接上篇)------以下以华为云VPC的UI自动化为例
测试猿小明自从掌握了RFS的基本用法,感觉UI自动化也就这样了,就是来回点点,输点文字,按下确定键,判断预期是否正确。
公司最近开始准备缩短版本交付周期,要求他写一套UI自动化,用来保障产品功能可用。他迅速的写了30+用例,在本地跑没有问题,就放倒jenkins上(本篇不讨论如何使用jenkins跑RFS,如果有需要,再另起一篇),设置每天凌晨3点定时跑一轮,这样第二天早上就能看到结果了。
第二天,小明傻眼了,测试用例除了头两条,后面的全部失败了,一大片红色的失败图标特别扎眼。
通过仔细的查看log,发现第三条用例创建产品失败了,之后的操作都在这个产品创建成功的基础上进行的。由于找不到指定的实例,之后所有用例全部执行失败了。
小明修改了无法创建产品的bug(查明是环境原因造成的),重新上传了新的自动化用例,并直接开启新一轮测试。结果这次还是失败了,原因是登录时,由于浏览器没有完全加载完,导致登录按钮没有找到,用例执行全军覆没。
小明继续修复了这个问题,再次执行(xN)…..
一天过去了,仅有一次非常幸运的跑完了全部用例,小明也搞的头昏眼花。
乐高积木
第三天,小明看着用例执行报告中一大片红色,已经没那么大干劲了,觉得这简直是无法解决的问题。只好求助公司的老测试Aino。
Aino查看了小明写的测试用例,指着各个测试用例说:“你这测试用例都是一条流水线,中间任何一环失败,后面的测试用例都无法执行,自动化用例不能这么写。”
小明:“那应该怎么办?”
Aino:“这个项目你时间还充裕吧,我这有一套乐高积木,刚好明天是周末,回去和小小明好好玩玩。”
启迪
周一,小明把乐高积木还给Aino。
Aino笑咪咪的问他:“怎么样,好玩么?”
小明:“好玩,一开始随意组装,能逗的儿子哈哈大笑。后来儿子想搞个大的,要组合个小汽车出来。经过我们不断的组合,拆除,计划后重新组合,终于做出了像样的小汽车。”
Aino:“总结下造这个汽车的经验吧~”。
小明:
首先,要分析下小汽车的形状,把汽车拆分成底盘,车身,轮子等等。(分析测试对象)
其次,要知道手头的乐高积木有哪些形状,形状之间如何组合。(分析自己有什么)
设计底盘,车身和轮子的大小,并分别组装好,预留最后组装用的接口。(模块化)
组合整体(中间还出现各种一开始没有考虑到的组装先后问题)。(组合)
Aino拍了拍小明的肩膀:“你已经掌握了工程化最基本的思路,现在,我给你讲讲这个UI自动化项目该怎么做吧。”
我们先看一下被测对象---华为云---VPC。(简单的说,就是云上机房)
访问地址(需注册)https://console.huaweicloud.com/vpc/
虚拟私有云(Virtual Private Cloud,以下简称VPC),为弹性云服务器构建隔离的、用户自主配置和管理的虚拟网络环境,提升用户云中资源的安全性,简化用户的网络部署。
您可以在VPC中定义安全组、VPN、IP地址段、带宽等网络特性。用户可以通过VPC方便地管理、配置内部网络,进行安全、快捷的网络变更。同时,用户可以自定义安全组内与组间弹性云服务器的访问规则,加强弹性云服务器的安全保护。
根据这个图,我大概讲一下VPC各个成员(不用完全理解):
VPC可以理解为云上的局域网。
这个局域网可以划分若干个子网。(广播范围)
子网内的设备ECS(云主机)上绑定了安全组规则。
子网和外界通讯可以绑定ACL(防火墙)规则。
ECS和互联网通讯,可以单独绑定EIP(弹性公网IP)直接和外网通讯。
子网内所有ECS也可以通过子网绑定的NAT(网络地址转换)网关和外界通讯
VPC有两种连接方式,VPN(虚拟局域网)和云专线(直接给你拉条网线/光纤)
【重要理论】对于绝大部分的自动化(无论是UI还是接口),都是完成了增删改查功能。
所以,我们的自动化也要围绕着增删改查来进行,对测试用例细粒度把控,也要能细化到具体项的增删改查功能。
大多数情况下,自动化测试由于牵扯到资源释放的问题,都会遵循增---查---改---查---删---查的顺序进行。这样在一个正常的情况下,创建出来的资源都在正确执行后被删除。一套无法恢复环境到执行前状态的自动化是不合格的。
我们写测试用例,里面就有一项“前置条件”,里面基本是对测试环境的描述。
在自动化中,前置条件可以认为是对资源状态的控制,这个控制可以是显性的(比如设置在setup中),也可以是隐性的(之前执行的测试用例)。
前置条件有问题,是导致测试用例大面积失败的主要原因。小明之前编写的测试用例,创建一个VPC就是作为其他用例执行的大前提,所以如果VPC创建失败,所有的用例都会失败。
那怎样避免这种情况发生?
答案是无法避免,但是可以尽量缩小因失败影响的范围。
比如以上例子,我们的VPC修改,可以放到一个已经创建好的VPC上去进行,这样即使目前VPC的创建自动化用例失败了,我们一样也可以测试VPC的修改功能。
这样,就牵扯到我们的另一个话题:预置资源。
预置资源,就是我们在测试对象中创建好的资源,比如各个模块的实例。
可是,这样有什么用?
这可不是为你节省了创建资源的时间:)
我们不希望自动化测试是在一个不稳定或者什么都没有的环境下进行,因为如果那样做,不稳定性会大大增加,任何一个微小的错误都会被无限放大。
如果所有的模块之间都是继承关系(很遗憾,实际大部分情况都是如此),比如有A才有B,有B才有C,有C才有D这样的调用链,我们写了分别创建A,B,C,D的自动化用例,如果A创建失败,会造成BCD全部失败。B失败,会造成CD全部失败。
即使非常幸运的ABCD都创建成功了,我们又有另一个问题:删除。
因为调用链的关系,删除一般都得反着来,即删除的顺序为DCBA,同样的,删除调用链上的任何一个环节的失败,也会影响到后面用例的执行。
那怎么办呢?
当然是创建预置资源了J
我们创建预置资源a,b,c,(分别对应ABC)(资源创建一般都是一条调用链上的,因为这样节省资源),然后就成了这样(自己思考下这样有什么好处):
A资源凭空造,然后测试增删改查。(最后A资源被自动删除)
a资源下创建B资源,然后测试增删改查。(最后B资源被自动删除)
b资源下创建C资源,然后测试增删改查。(最后C资源被自动删除)
c资源下创建D资源,然后测试增删改查。(最后D资源被自动删除)
----------------------------------------思考时间------------------------------------------------
想好了么?(有时候,思考的过程比你得到的结果更宝贵)
这样做最大的好处,就像把整个调用链上的各个节点放了checkpoint,如果本小结失败了,可以从下个小结从头(checkpoint)开始,因为资源已经创建好了。
如果以后完成了自动化用例,创作者能很清晰的指出“这几个是一个循环”,“这里使用了我预置好的资源”。
细心的小明又指出了另一个问题:“那我如何判断我该创建多少这样的资源?”
这里,就牵扯到预置资源创建的细粒度问题。
根据Aino的个人经验,如果自动化测试调用链上游的资源会影响8-10条以上的用例,就应该为这组自动化用例创建一个预置资源。因为我们能承受的自动化失败浪费的时间,就在这个范围内。
预置资源还有一个好处,一些我们测试的前提条件,可能是外部的,这些外部的资源创建,不在我们的考虑范围内。比如ECS(云主机)的创建,是需要消耗大量时间的,那我们就可以提前创建好它。
是自动化测试就会存在失败,失败一般有以下几种可能:
自动化用例本身的问题,比如UI测试的元素定位不唯一,接口测试的传参错误等等。(这种就是我们需要人工维护的部分了)
执行的前置条件未满足。(这条我们已经使用预置资源尽量避免)
环境不稳定,比如UI测试的页面加载不出来(服务器卡了),接口测试的响应超时等等。
其实第三种是很常见的情况。自动化从某种层面上来讲,是很“僵硬”的。
这里我们可以学习TCP/IP,对失败的部分进行重试。
重试的原理也很简单,就是对结果进行判断,如果不是想要的结果,就再试试。
这个“重试”也可大可小,可以是自动化中的某一个步骤,也可以是整条自动化用例,甚至是某个模块全部的自动化用例。
根据在实际操作中的经验,重试机制会大大提高自动化测试的成功率,并且排除了很多因为环境不稳定造成的失败。
IT行业有个很明显的特点,就是复用率特别高。或者通俗点说,大家的代码可能都很像:)。一块代码逻辑,处处使用,是很正常的。
所以后面就有了框架,有了组件,大家统一抄。
既然这样,就知道开发同学一定会遵循框架,使用组件。我们在做自动化之前,需要分析下框架和组件,从中抽象出共通的部分。
抽取出的公共部分,会很好的指导我们进行自动化用例的参数化。
比如:
抽象出所有的创建资源的按钮的元素定位。
抽象出搜索框的一系列操作,并做成一个关键字(函数)。
抽象出需要更改的配置信息,并参数化。
抽象出浏览器分辨率和显示按钮的关系,并在某个地方判断,选择。
抽象出调用链的出入参关系。
这里只列举了几个,只要你觉得对你的工作有意义,就可以找出规律并使用它。
如果你研究多了,你可以给开发提建议:“伙计,在这给我加个ID如何~”。
工欲善其事,必先利其器。先看看我们有什么样的武器。(以下截图中使用seleniumLibary,实际应该使用Selenium2Library)
点击和输入:
Click Element
页面操作使用最多的关键字,点击元素,可以不用点击的特别“具体”,比如某个div下只有一个a链接,那么点击div也可以。但是,如果元素定位不唯一或者点击对象的覆盖范围有其它可点击元素,会报错---“其它元素收到了点击”。
此外,可用的点击关键字还有Click Element At Coordinates,用于无法确认元素的情况,比如复杂控件中的点击。一般不使用,因为X轴和Y轴的绝对位置可能会因为环境的变化而变化。
在某些情况下,也会使用Double Click Element,唯一的差别是这个关键字是双击。
Input Text
输入文字的关键字,一般来说,对象都是一个input或者textarea。要注意的是,至今为止Selenium2Library中这个操作都有个bug,当连续在多个元素上输入文字的时候,中间如果不加sleep,概率有输入错乱的情况发生。
还有个关键字,在这里要特别指出,那就是Input Password,因为这个和Input Test唯一的区别是,在log中不打印,以防别人通过log就知道密码,所以这个关键字可不仅仅是能在输入密码的时候使用:)。
作为一位资深点点点,大家都懂,如果页面没加载出来按钮,我们不会去点按钮的。但是程序就是这么傻,咋办,先等待元素状态稳定再操作呗。
在RFS中,智能等待主要分为三种:
1. 判断页面元素状态的智能等待:
Wait Until Page Contains Element
判断页面是否包含元素,唯一要注意的是,如果页面包含多个符合定位条件的元素,此关键字只要其中一个满足条件,即为成功。
对应的反向关键字为Wait Until Page Does Not Contain Element,这里和上面相反,页面不能有任何一个元素符合定位条件,才为成功。
Wait Until Element Is Enabled
在这里的Enabled是指的元素的Enbale/Disable属性,当元素属性为Disabled时,元素是不可操作的,有时候,在页面加载的元素之间有逻辑判断,比如a的状态为1时,b的元素才会从Disabled变成Enabled(页面由于加载顺序的问题,这种情况经常发生),所以我们要确认目标元素的状态为Enabled,然后再对元素进行操作。
Wait Until Element Is Visible
原理同上,有时页面的元素虽然已经加载了,但是不是Visible状态,实际页面是不显示的,这样的情况下,对元素的操作也是无效的。
对应的反向关键字为Wait Until Element Is Not Visible,不过一般这条和上面的Wait Until Page Does Not Contain Element一样,大多数情况下是作为断言用的。
用户自定义关键字---元素等待:
以上三个关键字,我喜欢一起使用,因为一般操作的元素一定是页面包含,并且可用,可见的,这里,我们引入了用户自定义关键字:
用户自定义关键字就是编程中封装,可以把相对应的一组操作放到一起,并且抽取其中可能的变量([Arguments])(也可以不抽取):
wait element [Arguments] ${element locator} comment 这里的文字可以在log中看到,本用户自定义关键字检察元素是否被包含,可用,可见,以下依次判断: Wait Until Page Contains Element ${element locator} 20s Wait Until Element Is Enabled ${element locator} 20s Wait Until Element Is Visible ${element locator} 20s sleep 1
上面代码中的${wait time}是在资源文件中已定义的变量,sleep? ? 1 的作用是降低运行速度,增加容错率。
2. 断言用的智能等待:
【Tips】断言在这里是指对某个操作结果的判断。
Wait Until Page Contains
强烈推荐,不用元素定位,全页面等待某个文本,出现即为断言结果为True,在超时时间过了不出现即为False,适用于关键操作有页面提示的框架。
相对应的反向关键字为Wait Until Page Does Not Contain,适用于在业务状态变化后,页面某文字消失的情况,不是特殊情况,不建议使用,因为大部分情况下,某文字的消失,并不能说明业务一定成功了。
Wait Until Page Contains Element
在页面上等待符合定位的某元素出现,即为判断结果为True,在超时时间过了不出现即为False,某些情况下比较好用。
相对应的反向关键字为Wait Until Page Does Not Contain Element,使用方法和上面一样,这里就不再描述。
Wait Until Element Contains
判断元素包含的文本,其实这个有3个需要注意的地方:
元素先被找到,如果页面未包含元素,直接报错。
文本是“包含”,比如目标文本内容为"123",我们断言的内容为"23",也会判断为True。
如果定位有多个符合的元素,就会依次查找。
所以这个关键字是判断某一瞬页面是中某元素是否包含某文本,如果文本在这一瞬没有出现,就会判断为False。
相对应的反向关键字为Wait Until Element Does Not Contain,使用方法和上面一样,这里就不再描述。
用户自定义关键字---断言:
由于华为云在关键操作后(一般是提交表单),都会有后台响应的页面提示,会显示5秒,为了方便定位,我们在提交表单3秒后,可以先做一次截屏(Capture Page Screenshot),这样通过截图能分析失败原因,然后再断言:
wait contains [Arguments] ${wait text} comment 先sleep3秒等待响应出现并截图,之后再断言我们需要断言的内容 sleep 3 Capture Page Screenshot wait until page contains ${wait text} 20s sleep 1
这里sleep1 的作用是降低运行速度,增加容错率。
3. 重试用的智能等待:
记得我们上面说的“重试机制”么,大部分情况下的重试,都使用这个关键字来完成。
Wait Until Keyword Succeeds
这个关键字很重要,这个关键字很重要,这个关键字很重要
我们做手工测试,如果看到某个按钮没有加载出来,或者点击无反应,我们会稍等下再点点看。
默认情况下,自动化脚本如果点击失败,直接就会报错,然后本条自动化直接失败。这个是造成自动化稳定性的罪魁祸首。
使用这个关键字,可以在失败的情况下重试。
使用方法如下:
Wait Until Keyword Succeeds? ? 3x? ? 5s? ? click element? ? css=#123
其中,3x代表重试次数,5s代表每次重试间隔时间,后面就是关键字和其参数了,这里是点击ID为123的元素。
这样,就做到了如果点击ID为123的元素,如果成功,继续往下走;如果失败,隔5秒后重新点击试试。成功了继续,失败了再来,直到三次都失败为止。
这样,我们就可以这样封装点击和输入的操作了:
wait click [Arguments] ${element locator} comment 这里使用了上面已经建立好的wait element用户自定义关键字 wait element ${element locator} comment 使用focus可以聚焦元素 focus ${element locator} comment 尝试点击元素,成功继续,失败5秒后重试,3次失败后本行失败 Wait Until Keyword Succeeds 3x 5s click element ${element locator} wait input [Arguments] ${element locator} ${input text} comment 这里使用了上面已经建立好的wait element用户自定义关键字 wait element ${element locator} comment 使用focus可以聚焦元素 focus ${element locator} comment 尝试输入文字,成功继续,失败5秒后重试,3次失败后本行失败 Wait Until Keyword Succeeds 3x 5s input text ${element locator} ${input text}
扩展下,以上关键字click element也可以是某个用户自定义关键字,比如登录,这样可以做到登录失败,就可以再次登录,比如以下写法:
loginHEC comment 使用google chrome浏览器打开网址 comment 网址后面使用get参数直达我们想要到的页面 Open Browser https://console.huaweicloud.com/vpc/?region=cn-east-2&locale=zh-cn#/vpc/vpcmanager/dashboard gc comment 由于默认打开的浏览器是小窗口,这里设置浏览器的大小为1920 1080 Set Window Size 1920 1080 comment 最大化浏览器,最大化后,会根据windows系统桌面的分辨率重置浏览器分辨率,有可能会导致上面设置1080p分辨率失效 Maximize Browser Window comment 尝试登录系统,如果其中任意一步失败,则重试 Wait Until Keyword Succeeds 3x 5s login_retry login_retry comment go to是在当前浏览器中跳转到url,这里使用go to是因为使用这个可以重置浏览器到输入用户名和密码的界面,方便retry go to https://console.huaweicloud.com/vpc/?region=cn-east-2&locale=zh-cn#/vpc/vpcmanager/dashboard comment 输入用户名和密码,并点击确定 wait input css=#userNameId username wait input css=#pwdId password wait click css=#btn_submit comment 断言是否页面上有“快速指南”,如果有,登陆成功 Wait Contains 快速指南
看到这里,小明提出一个问题,要是在Open Browser就失败了,后面岂不是没有重试了?
所以我们也要把loginHEC的关键字通过这种方式,放到test suite的setup中去:
这样就做到了打开浏览器重试3次 乘以 登录重试3次,不是特殊中的特殊情况,这样足够了。
以上就是我们能处理90%以上UI自动化用到的关键字了,是不是很少 :)
后面我会在实际例子中穿插介绍其它常用的关键字,但是在这之前,我们还有个很重要的事要做,那就是:
参数化一般有两个用处:
使用比较多的参数,我们如果需要更改,就得手动去每个脚本中修改,其实我们可以把这个值提取到变量中,这样就能做到全局修改,比如最典型的超时等待时间的参数化。
一条用例使用多个参数进行测试的情况,比如下拉列表的遍历,边界值的测试等等。
比如综合上面所有一定义的关键字,可以把所有的超时时间20s修改为参数${wait time},失败重试的次数参数化为${retry times},用户名和密码修改为${username}和${password},并参数化访问的网址为${VPC Dashboard URL},替换后,资源文件就变成了这样:
*** Settings *** Library Selenium2Library Library Collections *** Variables *** ${wait time} 20s ${retry times} 3x ${username} 你申请的用户名 ${password} 你设置的密码 ${VPC Dashboard URL} https://console.huaweicloud.com/vpc/?region=cn-east-2&locale=zh-cn#/vpc/vpcmanager/dashboard *** Keywords *** wait element [Arguments] ${element locator} comment 这里的文字可以在log中看到,本用户自定义关键字检察元素是否被包含,可用,可见,以下依次判断: Wait Until Page Contains Element ${element locator} ${wait time} Wait Until Element Is Enabled ${element locator} ${wait time} Wait Until Element Is Visible ${element locator} ${wait time} sleep 1 wait click [Arguments] ${element locator} comment 这里使用了上面已经建立好的wait element用户自定义关键字 wait element ${element locator} comment 使用focus可以聚焦元素 focus ${element locator} comment 尝试点击元素,成功继续,失败5秒后重试,${retry times}次失败后本行失败 Wait Until Keyword Succeeds ${retry times} 5s click element ${element locator} wait input [Arguments] ${element locator} ${input text} comment 这里使用了上面已经建立好的wait element用户自定义关键字 wait element ${element locator} comment 使用focus可以聚焦元素 focus ${element locator} comment 尝试输入文字,成功继续,失败5秒后重试,${retry times}次失败后本行失败 Wait Until Keyword Succeeds ${retry times} 5s input text ${element locator} ${input text} wait contains [Arguments] ${wait text} comment 先sleep3秒等待响应出现并截图,之后再断言我们需要断言的内容 sleep 3 Capture Page Screenshot wait until page contains ${wait text} ${wait time} sleep 1 loginHEC comment 使用google chrome浏览器打开网址 Open Browser ${VPC Dashboard URL} gc comment 由于默认打开的浏览器是小窗口,这里设置浏览器的大小为1920 1080 Set Window Size 1920 1080 comment 最大化浏览器,最大化后,会根据windows系统桌面的分辨率重置浏览器分辨率,有可能会导致上面设置1080p分辨率失效 Maximize Browser Window comment 尝试登录系统,如果其中任意一步失败,则重试 Wait Until Keyword Succeeds ${retry times} 5s login_retry login_retry comment go to是在当前浏览器中跳转到url,这里使用go to是因为使用这个可以重置浏览器到输入用户名和密码的界面,方便retry go to ${VPC Dashboard URL} comment 输入用户名和密码,并点击确定 wait input css=#userNameId ${username} wait input css=#pwdId ${password} wait click css=#btn_submit comment 断言是否页面上有“快速指南”,如果有,登陆成功 Wait Contains 快速指南
准备工作就做好了,这样我们就可以创建我们本文第一条自动化测试用例。
【Tips】由于本文要示例元素定位的方法,所以元素定位均未参数化,而使用comment对操作进行说明。元素定位使用chrome开发者模式。
在这里,我建议测试用例封装成一个关键字,因为只有这样,才方便测试用例进行整体重试。
我们准备写一条创建VPC的测试用例,主要操作步骤如下图所示:
(Chrome浏览器)右键点要操作的对象(此处是“虚拟私有云”),选择“检察(N)”
当把鼠标放在阴影部分(<span class...)时,Web页面会展示此元素的作用域,并且我们观察到这个span中没有什么属性是唯一的。
在Selenium中,对元素的操作要求不是那么严格,我们可以把鼠标从span依次往上移,看看父元素和周边元素的作用域。只要这个作用域还在我们鼠标可点击的范围内,均可以作为点击的对象来操作。
向上移动后,我们发现父元素的div中有一个属性id="_vpc" ,并且它的作用域也是在我们要点击的范围内:
由此,我们可以使用css选择器来定位元素,在css中,#_vpc可以理解为"选择所有id为_vpc的元素":
分析出的定位结果为: #_vpc
验证的方法很简单,点击开发控制台元素页面任意元素,按Ctrl+F出现元素搜索框(注意不要点到Web页面上了)
输入我们分析出的css选择器结果:
被选择器选中的元素会标记为黄色,可以把鼠标放到上面,看看是Web页面上显示的作用域是不是我们的预期结果。
同时有个很关键的点,在搜索框的最后有个“1 of 1”,如果选择器查到的结果有多个,可能会对操作有影响,一般我们要确保此处是“1 of 1”。如果有多个,可以使用后面紧跟的上下箭头来切换标黄被选中的元素。
有了元素定位,我们的用例就有了第一条脚本:
create VPC comment 点击dashboard的“虚拟私有云”按钮,进入虚拟私有云页面 wait click css=#_vpc
进入虚拟私有云页面,我们要点击“创建虚拟私有云”按钮进入创建虚拟私有云的页面,定位如下:
这里有唯一的class属性可以定位到按钮,和id有简写一样,class可以用"."来简写,所以定位结果为:
.cti-btn-label
更新到测试脚本中:
create VPC comment 点击dashboard的“虚拟私有云”按钮,进入虚拟私有云页面 wait click css=#_vpc comment 点击拟私有云页面中右上角的创建虚拟私有云按钮,进入虚拟私有云创建页面 wait click css=.cti-btn-label
进入创建页面后,需要输入新创建的VPC的名字,输入框一般在html中有两种,一种叫input,一种叫textarea,下图中的input框中有个属性是唯一的,我们可以通过它来对这个input框进行定位:
元素类型[属性名称和其对应值]也可以作为选择器来使用,所以这个输入框的定位结果为:
input[meta-data-uba="www_v1_vpc.click.vpc_createVpcList_name_input"]
更新到测试脚本中:
create VPC comment 点击dashboard的“虚拟私有云”按钮,进入虚拟私有云页面 wait click css=#_vpc comment 点击拟私有云页面中右上角的创建虚拟私有云按钮,进入虚拟私有云创建页面 wait click css=.cti-btn-label comment 输入新的虚拟私有云名字:vpc-temp wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_createVpcList_name_input"] vpc-temp
同理,子网的名字输入也可以定位出来:
更新到测试脚本中:
create VPC comment 点击dashboard的“虚拟私有云”按钮,进入虚拟私有云页面 wait click css=#_vpc comment 点击拟私有云页面中右上角的创建虚拟私有云按钮,进入虚拟私有云创建页面 wait click css=.cti-btn-label comment 输入新的虚拟私有云名字:vpc-temp wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_createVpcList_name_input"] vpc-temp comment 输入新的子网名字:Subnet-temp wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_createVpcList_portal_input"] subnet-temp
最后点击创建按钮:
create VPC comment 点击dashboard的“虚拟私有云”按钮,进入虚拟私有云页面 wait click css=#_vpc comment 点击拟私有云页面中右上角的创建虚拟私有云按钮,进入虚拟私有云创建页面 wait click css=.cti-btn-label comment 输入新的虚拟私有云名字:vpc-temp wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_createVpcList_name_input"] vpc-temp comment 输入新的子网名字:Subnet-temp wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_createVpcList_portal_input"] subnet-temp comment 点击创建按钮创建VPC wait click css=.cti-btn-label
创建成功后,会显示如下页面,我们可以判断页面中是否出现红框的文字,从而判断创建虚拟私有云是否成功:
create VPC comment 点击dashboard的“虚拟私有云”按钮,进入虚拟私有云页面 wait click css=#_vpc comment 点击拟私有云页面中右上角的创建虚拟私有云按钮,进入虚拟私有云创建页面 wait click css=.cti-btn-label comment 输入新的虚拟私有云名字:vpc-temp wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_createVpcList_name_input"] vpc-temp comment 输入新的子网名字:Subnet-temp wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_createVpcList_portal_input"] subnet-temp comment 点击创建按钮创建VPC wait click css=.cti-btn-label comment 判断页面上是否出现“任务提交成功!”来判断提交是否成功 wait contains 任务提交成功!
整个用例就完成了,在测试用例中调用这个关键字,就能自动化执行创建一个虚拟私有云:
*** Settings *** Suite Setup Wait Until Keyword Succeeds ${retry times} 5s loginHEC Suite Teardown Close All Browsers Resource Resource.txt *** Test Cases *** VPC Create vpc-temp create vpc
等等,如果这么做,失败了就没有重试了,根据我们之前讲的,是可以通过关键字重试的:
*** Settings *** Suite Setup Wait Until Keyword Succeeds ${retry times} 5s loginHEC Suite Teardown Close All Browsers Resource Resource.txt *** Test Cases *** VPC Create vpc-temp Wait Until Keyword Succeeds ${retry times} 5s create vpc
仔细观察创建VPC的关键字,如果真的在过程中失败了,重试也是无效的,因为我们创建的过程中,失败可能在任何界面:
所以,我们需要在这条用例的关键字的最前面加一个重置,即无论在哪失败,测试用例都能回到用例最初的页面。
最常用的方法有两个:
使用go to关键字强制跳转到web页面(可参考login retry的用法)
使用Open Browser打开一个新的浏览器,并访问web页面
绝大多数情况下,使用方法1,因为开新的浏览器会消耗执行机资源。
又因为这个重置在我们测试”虚拟私有云“这个模块时会用的比较多,所以我们把这个动作封装成一个关键字,叫"Enter VPC Board"
修改后的完美结果为:
Enter VPC Board comment 通过go to打开VPC dashboard go to ${VPC Dashboard URL} comment 点击dashboard的“虚拟私有云”按钮,进入虚拟私有云页面 wait click css=#_vpc create VPC Enter VPC Board comment 点击拟私有云页面中右上角的创建虚拟私有云按钮,进入虚拟私有云创建页面 wait click css=.cti-btn-label comment 输入新的虚拟私有云名字:vpc-temp wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_createVpcList_name_input"] vpc-temp comment 输入新的子网名字:Subnet-temp wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_createVpcList_portal_input"] subnet-temp comment 点击创建按钮创建VPC wait click css=.cti-btn-label comment 判断页面上是否出现“任务提交成功!”来判断提交是否成功 wait contains 任务提交成功!
这样,即使在任务中任意地方失败,用例都会从头重试整个创建过程。
观察下我们已经创建的VPC。可以看到对新的VPC,有修改和删除操作:
在进行修改和删除操作之前,我们观察到有组件提供的搜索实例功能,为了能唯一确定我们的操作对象,可以使用这个功能先搜索出我们要操作的对象,并把这个动作封装成一个关键字:
我们可以看到上图中,其实是有两个可以使用的属性,但是这里选择了下面的,因为下面的属性是所有搜索框输入都能使用的属性,我们可以根据它创建整个自动化中都适用的搜索用户自定义关键字,后面的定位方法略,最后关键字内容为:
Search [Arguments] ${Search Text} comment 输入搜索对象 wait input css=.ti-searchbox-input ${Search Text} comment 点击搜索按钮 wait click css=.ti-searchbox-search sleep 1
以后我们遇到搜索框,就可以使用这个关键字,使要操作的实例唯一。
创建完成后,自然是要进行修改了,这里有个小技巧,修改这个功能,可以修改内容后再修改回来,万一修改功能不可用,我们后面对这条实例的其它操作也不会受影响。
先定位下“修改”的位置,这里使用Xpath的text()进行定位,当然,也可以使用jquery进行定位,后面再演示:
然后是修改名字输入框,修改CIDR输入框和提交修改的按钮的元素定位:
提交成功后,再次操作,把修改的内容全部修改回来,注意,这里搜索时使用新的vpc名字,最后完成的脚本为:
modify VPC comment 先通过go to URL的方式重置本用例,进入VPC模块页面 Enter VPC Board comment 通过用户自定义搜索关键字,唯一要操作的实例对象 Search vpc-temp comment 点击修改按钮(这里其实是个链接) wait click xpath=//a[text()='修改'] comment 修改vpc的名字和cidr wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_detail_editVpcModel_name_input"] vpc-modify-temp wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_detail_editVpcModel_cidrMask_input"] 17 comment 点击确定按钮提交修改 wait click css=button[ng-click="clickOK()"] comment 判断页面上是否出现“成功”来判断提交是否成功 wait contains 成功 comment 通过用户自定义搜索关键字,唯一要操作的实例对象 Search vpc-modify-temp comment 点击修改按钮(这里其实是个链接) wait click xpath=//a[text()='修改'] comment 修改vpc的名字和cidr wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_detail_editVpcModel_name_input"] vpc-temp wait input css=input[meta-data-uba="www_v1_vpc.click.vpc_detail_editVpcModel_cidrMask_input"] 16 comment 点击确定按钮提交修改 wait click css=button[ng-click="clickOK()"] comment 判断页面上是否出现“成功”来判断提交是否成功 wait contains 成功
尝试进行删除操作,但是这里无法直接删除:
要进入VPC去删除里面的子网,才能进行进一步的VPC删除,所以我们先进行子网的删除(由于定位没有难点,此处省略):
delete subnet comment 先通过go to URL的方式重置本用例,进入VPC模块页面 Enter VPC Board comment 通过用户自定义搜索关键字,唯一要操作的实例对象 Search vpc-temp comment 点击VPC详情链接 wait click css=a[meta-data-uba="www_v1_vpc.click.vpcList_vpc_detail"] comment 通过用户自定义搜索关键字,唯一要操作的实例对象 search subnet-temp comment 点击subnet后面的删除按钮 wait click xpath=//a[text()='删除'] comment 点击确认删除按钮 wait click css=button[ng-click="clickOK()"] comment 判断页面上是否出现“成功”来判断提交是否成功 wait contains 成功 这次可以对VPC进行删除操作了: delete VPC comment 先通过go to URL的方式重置本用例,进入VPC模块页面 Enter VPC Board comment 通过用户自定义搜索关键字,唯一要操作的实例对象 Search vpc-temp comment 点击删除按钮(这里其实是个链接) wait click xpath=//a[text()='删除'] comment 点击确认删除按钮 wait click css=button[ng-click="clickOK()"] comment 判断页面上是否出现“成功”来判断提交是否成功 wait contains 成功
把以上的创建---修改---删除放在一起,连跑下,一个最小的循环体就完成了:
如果所有的用例不出错,创建-修改-删除,最后资源会恢复到最初的状态。由于目前这个小循环体只牵扯4条用例,我们不需要添加预置资源。如果后面做自动化用例设计,创建的资源要被数十条用例使用,就可以事先创建好这个资源。
当然,事先创建资源也有不好的地方,主要体现在以下两个方面:
某些新创建资源和以前创建的资源(预置资源)有差别,比如创建时加入了新的属性。
用例有失败,复杂的预置资源会影响自动化环境手动恢复的时间。
所以,可以根据业务的不同,有节制的使用预置资源,如果自动化创建的资源,和后面多条用例都有耦合,我们可以这么做:
此资源创建完成后,判断资源是否完全创建成功,并把这个结果传给一个变量,并把这个变量设置为全局变量。和此资源耦合的用例,先判断这个变量的值,符合条件再跑。这样,有耦合的这些用例就不会傻傻的跑很久。
我们还是用上面VPC的创建-修改-删除来做个例子。需要做的是:
1. 新增一个判断VPC创建状态并返回这个状态值的用户关键字:
check VPC comment 初始化一个套件内使用的公共变量,并赋值为uncreated,用来记录VPC创建是否完成的状态 Set Suite Variable ${vpc created status} uncreated comment 先通过go to URL的方式重置本用例,进入VPC模块页面 Enter VPC Board comment 直接检查页面上是否存在刚才创建的VPC wait contains vpc-temp comment 由于RF会在失败后停止执行,如果上面的检查成功了,才能执行以下这条,并赋新值 Set Suite Variable ${vpc created status} created
2. 在第一个使用刚才创建VPC的资源的用例之前执行这个用户关键字,并在后面所有要使用这个资源的前面先判断资源状态,如果有资源就执行,否者直接跳过:
*** Settings *** Suite Setup Wait Until Keyword Succeeds ${retry times} 5s loginHEC Suite Teardown Close All Browsers Resource Resource.txt *** Test Cases *** VPC Create vpc-temp Wait Until Keyword Succeeds ${retry times} 5s create vpc check VPC Resource Wait Until Keyword Succeeds ${retry times} 5s check vpc modify VPC vpc-temp Run keyword if '${vpc created status}'=='created' Wait Until Keyword Succeeds ${retry times} 5s modify vpc delete subnet subnet-temp Run keyword if '${vpc created status}'=='created' Wait Until Keyword Succeeds ${retry times} 5s delete subnet delete VPC vpc-temp Run keyword if '${vpc created status}'=='created' Wait Until Keyword Succeeds ${retry times} 5s delete vpc
小明马上跳出来问:为什么不把这个资源判断放在setup中执行?
其实也可以放在modify vpc vpc-temp的setup中,但是由于RF的限制,setup中不太方便使用wait until keyword succeeds,除非再封装一层。而且把这条放在外面,在分析的时候,能一眼看到是资源文件没有创建成功。
——————————————————追加部分 自动滚屏————————————————
至此,只要按照上面的方法,绝大部分的UI自动化问题都能解决。但是对绝大部分Selenium使用者来说,还有一个让很多人头大的问题,那就是需要操作必须滚动后才能看见的元素。
网上有很多办法,一般来说简单的有以下三种方法:
使用focus,可以聚焦元素后,自动滚屏过去。
使用Scroll Element Into View (Selenium2Library3.0)自动滚到元素可见。
直接用scroll down这个关键字滚屏,但是我们不知道需要滚屏多少,很多时候都是估算。
其中1和2的方法,有时候能生效,有时候有莫名其妙的失败。3的方法每次滚屏都可以成功,但是有可能因为版本的变更,自动化执行机分辨率的变化等等造成滚过了或者没滚到。
然后,笔者自己写了一个用户关键字,改造了我们上文提到的用户关键字wait element,使用了3的方法,但是可以智能的获取滚动高度,大家可以根据需要取用:
scroll to [Arguments] ${scroll value} comment 由于部分Selenium2Library不支持Scroll Down,故自己写一个用js滚屏的关键字 Execute Javascript var q=document.documentElement.scrollTop=${scroll value} wait element [Arguments] ${Element_LOCATOR} wait until page contains element ${Element_LOCATOR} ${wait time} Wait Until Element Is Enabled ${Element_LOCATOR} ${wait time} comment 这里重试是因为有小概率会发生因为页面加载不完全导致的获取滚动高度有问题的情况 Wait Until Keyword Succeeds 4x 5s get scroll height ${Element_LOCATOR} comment 当被操作额元素在屏幕一半以上时,会出现计算的滚屏高度为负值的情况,故先判断下 run keyword if ${scroll} >= 0 scroll to ${scroll} Wait Until Element Is Visible ${Element_LOCATOR} ${wait time} Capture Page Screenshot sleep 2 get scroll height [Arguments] ${Element_LOCATOR}