由于最近框架构建已阶段性完成,从今天开始,会慢慢更新Robot Framework Selenium实战篇内容。

本篇内容会非常多,我会尽量描述:

1. 遇到的问题。

2. 解决方案的选择思路。

3. 最后的实现。


目录

引子:

可行性分析:

框架原理和需要考虑的问题:

框架雏形:

基础框架的第一个坑---Informix数据库连接:

基础框架的第二个坑---密码输入控件:

第一次主动优化---测试数据分离:

框架的第三个(巨)坑---IE:

第二次主动优化---Pabot+Selenium-Grid的并发:

框架的第四个坑---Selenium-Grid不稳定:

第三次主动优化---增加重跑失败用例,并提供录屏功能:

第四次主动优化---元素等待中的各种骚操作:

1. 自动切换 Frame:

2. 自动滚屏,尽量把操作对象放到屏幕中间:

3. 自动高亮操作对象:

4. 保留每次失败前30秒的截图:

第五次主动优化---自带验证的click和input:

第六次主动优化---在log最后按顺序展示全部截图:

第七次主动优化---Selenium不好处理的页面元素:


引子:

程序猿小明调换了新的项目组,新项目主要做的是银行业务,自动化业务还没有开展起来。
这次小明可以对框架进行自主选择,小明想了下,给出了以下几个方案:

1. 纯python编写脚本,好处是灵活,并且有比较多成熟的解决方案;缺点是编写脚本有一定的门槛,并且交付给功能测试人员分析失败,有难度。

2. 使用已有的第三方提供的自动化平台,此平台实现了纯b/s架构下的脚本开发和运维,可以不用git或svn来维护脚本,脚本组件的开发可以通过下拉框选择和拖拽的方式编写;缺点是开发效率低,不支持并发,新功能增加需要依赖第三方开发人员。

3. 使用Robot Framework,没有纯python灵活,但是比第三方的编写脚本方便,支持并发,网上解决方案少。

经过和领导层商讨后,选择了RF,主要原因如下图所示(哈哈):

一个用例是这样的:

RF可以“汉化”,对非脚本开发者更友好,也方便没有代码基础的功能测试人员进行失败分析。

所以,框架又又又选择了Robot Framework:)

上面是说笑,其实确定框架之前,就要做下面这件事:

可行性分析:

1. 分析此框架是否能支持目前自动化需求(可用性)

2. 分析此框架是否能支持未来自动化需求(可扩展性)

3. 分析框架对开发人员的友好度

4. 分析框架对使用人员的友好度

5. 分析框架和现有项目管理体系的融合(比如测试用例执行管理,项目管理等等)

其中3,4上面已经说了,能汉化还是比较友好的:)

第1,2条主要是看看robot现有的库,这里我大概介绍下现在robot常用的库:

SeleniumLibrary --- 之前的Selenium2Library已经不用了,以后都是这个

AppiumLibrary --- 做移动端自动化,Android,IOS

AutoRecorder --- 可以对当前屏幕进行录屏,保存成H5的webm格式,可以在log中直接播放

Database Library --- 数据库操作的库,支持常见的数据库(需要安装各自数据库依赖包)

HTTP RequestsLibrary --- http接口测试,依赖requests库

DataDriver Library --- 更方便的数据驱动方式

robotframework-faker --- 用来创建假数据的库,在某些情况下很好用

Rammbock --- 网络协议级接口测试,可以自定义tcp和udp接口,很强大

SikuliLibrary --- 老牌图像比对自动化库,现在我其实推荐试试ImageHorizonLibrary

SwingLibrary --- 可以支持操作以前java编写的swing程序

AutoItLibrary --- 只要是windows的gui,都能用这个,不过建议和图片比对一起用

Websocket-client --- 做websocket测试用

Pabot --- RF上目前最好用的并发控制库

除了这上面的扩展库,目前的Robot还提供了内建的库,使用率非常高:

BuiltIn --- 不用引用就能用,逻辑判断,断言和最常见的数据处理

String --- 对字符串进行处理的库,基本够用,支持正则

Collections --- 对python的列表,元祖,字典,集合进行处理的库,当然,你也可以直接用Python处理

OperatingSystem --- 对操作系统操作的库,复制粘贴创建删除,设置path,执行cmd或者shell

XML --- 支持对xml进行操作的库,分析xml结构,更改xml节点数据等等

DateTime --- 时间操作,获取时间,时间加减,时间格式转换等等

Process --- 多线程并发的基础

还有一些库,我这也会用起来,比如:

ExcelLibrary --- 这个库支持xls,其实有新版,但是不支持xls,这个我后面会给出具体安装方法

Tuxedo --- 为了能使用oracle tuxedo,目前支持最高Python版本是3.7.7

从上面看,使用robot framework基本已经涵盖了B/S,C/S,API,网络协议等自动化需求。

至于第5点,小明倒是不太担心,因为现在系统间都是通过接口调用来完成数据传递的,只要我们能从Jenkins和Robot拿到测试结果,调用都是小事,本文会在后面详细描述Robot Framework Selenium生成的output.xml如何解析和组合请求。

框架原理和需要考虑的问题:

框架的基本原理也很简单,这里使用一张图就能解释清楚:

大部分自动化框架,都要考虑以下几点:

1. 自动化的用途是什么?

2. 是否要支持并发,如果支持并发,怎么做?

3. 如何保证稳定性,准备用哪些手段?

4. 代码维护的便利性,如何减少版本变更导致的维护量?

5. 自动化最后的使用人是谁?如果不是自动化编写者,失败分析怎么做?

6. 自动化测试的自动化执行如何安排,手动执行自动化又如何做到更便利?

7. 自动化测试的结果如何使用,如何分析?

以后整篇文章,其实都是围绕着上面的问题展开的。

框架雏形:

饭得一口一口吃,事得一步一步做,基于之前的经验(有兴趣的可以看下入门篇和进阶篇),小明一板斧劈出了第一个框架:

这里来解释下,这几个项都是来做什么的,以及为什么这么设计:

1. 测试用例:所有的测试用例都放在这里,是自动化的唯一入口,展开后分层如下:

测试套的基本配置和作用如下:

测试用例的基本配置和作用如下:

测试用例本体的基本配置和作用如下:

2. 页面组件:

3. 数据库组件:

数据查询组件基本写法:

Informix(通过自定义python Library)--坑之一,后面会说

MySQL:

Oracle:

DB2:

4. 通用组件:

5. 引用这个即可使用所有关键字和库

6. 上传文件:

这个就是个文件夹,为了集中存放需要上传的模板文件,Robot Framework 可以使用${CURDIR}来指定当前文件夹,所以放这里,引用很方便。

7. 中文化:

无它,就是为了汉化最基本关键字:

以上就是最基础的框架的雏形,其实已经可以开始在这个上面编写脚本了。

代码的存放,由于公司有现成的SVN,直接用就行。(git也一样)

CI自然选择了Jenkins,配置也很简单(这里Jenkins使用的版本和对应的插件hpi,会上传):

log保留策略和执行机选择:

SVN配置:

执行策略:

执行机是Windows,我们使用pabot跑多线程:

Robot Framework 结果处理和显示:

基础框架的第一个坑---Informix数据库连接:

小明所在的项目组,有4种类型的数据库:

Oracle

MySQL

DB2

Informix

其中前三种都是比较常见的数据库,直接使用Database Library就可以直接连接,设置也相对简单。

只有Informix是很少见的数据库,Database Library不支持,Robot Framework也没有可以直接使用的库。

通过查询发现python有IfxPy工具包可以进行Informix数据库的连接和操作,由于Robot Framework支持封装好的Python函数作为关键字使用(其实RF底层也是这个原理),我们可以自己写python脚本,封装成包,使其作为Library被Robot Framework直接使用。

自己编写python脚本,名为informix.py,内容如下:

import IfxPydef QueryInformixSQL(server, database, host, service, uid, pwd, sql):ConStr = "SERVER={};DATABASE={};HOST={};SERVICE={};UID={};PWD={}".format(server, database, host, service, uid, pwd)conn = IfxPy.connect(ConStr, "", "")stmt = IfxPy.exec_immediate(conn, sql)list_return = []for times in range(100):tupled = IfxPy.fetch_tuple(stmt)list_return.append(tupled)if tupled == False:breakprint(list_return)IfxPy.close(conn)return list_return

代码比较简单,稍微解释下:

1. 入参是连接串的属性和sql语句本身,这样是为了可以在不同informix上使用。

2. 连接串使用了format,主要是防止特殊字符的干扰。

3. 通过fetch_tuple返回的是元祖,需要取出元素放入列表,这样可以控制取出的行数,例子中最多取100行数据。

4. 取出的数据是二维列表,第一维是行,第二维是列。

上面的informix.py脚本搞定了,需要放在方便引用的目录,我放在自动化脚本的根目录:

然后在Robot Framework中引用:

这样,就能直接在Robot Framework中使用了:

使用后的log如下(可以通过控制sql语句来减少行和列):

基础框架的第二个坑---密码输入控件:

这个问题一开始也尝试了以下方法:

1. AutoIt:放弃原因是因为做不成,基于pywin32的还是虚拟的键盘输入,控件输入必须检测到键盘”真实“输入,否则无法输入。(有兴趣的可以去了解下现在常见的密码控件原理)

2. 使用虚拟键盘软件(就不说是什么了,否则有打广告的嫌疑)或者按键精灵,可以做到输入,但是,这种输入由于是”虚拟物理“级的,如果并发,就会乱,所以放弃了。

最后,还是麻烦开发去除了密码控件,自动化表示没有办法解决,好在现在都是发布增量包,这部分不会随着每次发布而变化。

至此,自动化基础框架已经可以跑起来了,组内的同事开始编写自动化脚本。框架的构建,即使非常有经验的架构师,也不可能一开始就很完美,需要在使用中进一步完善。

第一次主动优化---测试数据分离:

组内做的测试数据越来越多,需要进行数据的共享和集中管理,所以单独创建了资源文件:

这种字典格式还是很好维护的,如果需要,最好的当然是使用数据库,但是移植性会受到影响。

框架的第三个(巨)坑---IE:

IE不但是前端开发最讨厌的浏览器,也是Selenium自动化开发中最难缠的敌人:

1. IE不支持最新的xpath版本,导致xpath效率有点低,特别是32位的IE。

2. IE的webdriver,IEDriverServer.exe对IE本身的设置有很多限制。

3. IE如果在同一台机子多线程并发,会互相影响。

4. selenium的截图,在使用执行机跑IE的情况下,会等待8秒超时并截取黑屏。

5. IE的下载不能设置默认路径,下载弹窗不属于web,无法方便的操作。

6. IE的webdriver有不小的几率会发生crash,调起windows错误报告,并超时才能继续。

小明遇到这些问题,实在没有办法,只好请教之前的同事老Aino。

老Aino并没有直接给出解决方案,而是指着问题3问小明:”这个问题,你百度了么?“

小明(。。。。。。)

老Aino微微一笑,拍拍小明的肩膀:

“你,还是太年轻了:)”

会不会合理的使用搜索,却是区别普通菜鸟和老鸟的标准之一,从菜到老分别如下:

1. 直接在百度里输入“ie并发互相影响”去搜索

2. 直接在百度里输入“ie 自动化 并发 互相影响”去搜索

3. 直接在百度里输入“ie Selenium 互相影响”去搜索(在找不到合适的方法的情况下,也可以试试换个关键词,会有奇效)

4. 指定特定的时间段,技术网站(百度搜索栏高级功能)---主要是为了过滤某垃圾知道

5. 使用google+英文进行搜索

6. 国内网站资料特别少的,搜索国外知名技术网站(比如stackoverflow或者codeproject)

7. 直奔官网的wiki和Issues(使用说明和bug)

不恰当的使用搜索,会让你打开无数个重复内容的网页,查看无数个无效的内容

比如你要解决的问题3,官网已经给了说明:

官方两手一摊:“臣妾做不到”

你就不用再百度了,直接换方案---使用Selenium Grid进行ie的多执行机单机并发

除了搜索,很多问题你也是可以通过分析来解决的

比如问题4:selenium的截图,在使用执行机跑IE的情况下,会等待8秒超时并截取黑屏

这个问题,如果稍微想一想就会发现这个问题有个缺口,那就是:

为什么本地跑就可以正常截图?

进一步分析下,远程和本地有什么区别?

那我远程上看着脚本执行试试?

小明照着老Aino的方法,远程上去模仿Jenkins直接使用cmd调用pabot,看着脚本跑,结果有正常截图

开着这个远程,直接通过Jenkins调用脚本,执行机能看到脚本执行了,结果有正常截图

小明一脸懵逼:难道我是robot截图实验的观察者,只要人看着,测试结果就不一样?(双缝衍射实验:))

老Aino:“试试开始后最小化远程桌面”

小明照做,结果有正常截图。

老Aino:“试试一直开着这个远程桌面,不要退出”

从此以后,截图再也没有黑屏过。

经过老Aino的指导,小明把IE的问题逐步解决了,结果如下:

1. IE不支持最新的xpath版本,导致xpath效率有点低,特别是32位的IE:

统一使用64位的IE,不使用32位的IE(32位的IE在X86文件夹下)

2. IE的webdriver,IEDriverServer.exe对IE本身的设置有很多限制:

主要是缩放必须100,统一使用启用保护模式

3. IE如果在同一台机子多线程并发,会互相影响:

这个最后决定使用selenium-grid的多执行机单线程

4. selenium的截图,在使用执行机跑IE的情况下,会等待8秒超时并截取黑屏:

使用一台虚拟机远程所有执行机,这台虚拟机不关机,所有的截图就都OK

5. IE的下载不能设置默认路径,下载弹窗不属于web,无法方便的操作:

下载使用chrome,chrome会默认下载到C盘的用户的Downloads文件夹

6. IE的webdriver有不小的几率会发生crash,调起windows错误报告,并超时才能继续

每次任务启动时,通过bat杀死对应的webdriver进程(后面会说这种方法)

第二次主动优化---Pabot+Selenium-Grid的并发:

小明之前就使用Pabot做过并发(具体操作方法请查看进阶篇),但是Pabot的并发只是单执行机的。

由于IE在多并发时焦点无法控制,所以IE现在不能在一台执行机上多并发,解决方案只有以下几种:

1. Jenkins多任务,每个任务都是单执行机单线程,最后想办法合并测试结果。

2. 使用Selenium-Grid对执行操作进行自动分发,结果收集。

小明指着方案1问老Aino:”既然有方案2,这个方案有意义么?“

老Aino:”有意义,首先,你得知道这两个的区别。“

Selenium-Grid目前在使用的是2,3,4三个版本,其中,2和3是目前使用最广泛的,4是新出的框架,他们大概的原理如下:

Selenium-Grid2和Selenium-Grid3:简单说就是通过HUB分发任务给NODES,实际上,会复制整个自动化脚本到NODE的临时目录(一般都是C盘的临时文件夹),然后调用NODE上的webdriver来进行自动化测试。

Selenium-Grid4:为了解决2和3在稳定性和安全性方面的问题,4使用了全新的架构,加入了路由,队列,控制单元,分布式,docker,能在网络或者系统繁忙时更好的调度任务:

但是,现在所有的Selenium-Grid都不支持对执行机操作系统的控制

比如UI自动化中需要下载某个文件,并在下载后校验文件是否下载成功,这个用例如果执行在Selenium-Grid的node上,执行顺序如下:

1. Grid的Hub(主控机)控制Node(执行机)上打开网页,下载文件,文件保存在Node执行机的指定目录

2. 自动化脚本去Jenkins-master(你自动化代码根执行机)所在的指定目录查找文件是否下载

结果自然是找不到文件(后面会说如何解决这个问题)

而方案1,却不会发生这种问题,因为方案1是使用javaweb来控制节点,属于操作系统级的,但是方案1不能做到动态的负载均衡,事后也要从各个执行机收集log,截图,并合并,并不便利。

小明最后还是选择了Pabot+Selenium-Grid3的组合(Grid3有稳定版)。

Selenium-Grid的配置也很简单,只要下载了对应的selenium-server-standalone的jar包,然后命令行启动即可

HUB配置:

java -jar selenium-server-standalone-3.141.0.jar -role hub -port 5566 -maxSession 16 -browserTimeout 1800 -timeout 1200

NODE配置:

Chrome:

java -jar selenium-server-standalone-3.141.0.jar -role node -hub http://9.1.9.8:5566/grid/register/ -port 6666 -maxSession 2 -browser browserName=chrome,maxInstances=2

IE:

java -jar selenium-server-standalone-3.141.0.jar -role node -hub http://9.1.9.8:5566/grid/register/ -port 5577 -maxSession 1 -browser browserName="internet explorer",maxInstances=1

注意:所有的端口号不要重复

配置好上面的HUB和NODE后,只要在打开浏览器的时候给出remote_url=http://9.1.9.8:5566/wd/hub即可(根据上面的命令行变化)

为了在多并发的时候,windows的可用端口够用,需要在管理员下执行:

netsh int ipv4 set dynamicport tcp start=20000 num=45535
netsh int ipv4 set dynamicport udp start=20000 num=45535

配置好之后,可以通过grid-console来查看node的情况,比如如果按照上面的配置,访问地址是:

http://9.1.9.8:5566/grid/console

Pabot+Selenium-Grid2这个组合基本没有人用过,经过一系列的实验,小明确定这种方式是可行的,采用这种方式,用例的执行顺序是这样的:

1. pabot根据配置的执行并发数,取同样数量的测试套

2. 为每一个测试套创建一个线程,并给每个线程创建一个单独的log目录,目录编号从0开始

3. Pabot的每一次打开浏览器请求,都会由HUB进行分配,所以不会发生测试套和浏览器打开冲突

4. 当只有一个测试套在跑的时候,会不停轮询各个NODE

Node可以随时增加,比如某天需要支持更大的并发数,就可以让自己和同事的电脑加入node

小明终于把Pabot+Selenium-Grid配置完成,高高兴兴的跑了三天。

第四天早上,脚本在没有任何变化的情况下,直接失败了一大半。

框架的第四个坑---Selenium-Grid不稳定:

小明查看了错误日志,发现报错的基本都是Jetty9,经过查找问题原因,发现这个是Jetty9在Grid上的一个bug,目前还没有在Grid3上解决。

那怎么办呢?难道我们每天手动重启下Grid的Hub和Node?

自动化组每天要手动去做服务的重启,岂不是成了笑话:)

结合着Grid其实是在CMD里启动Jar包,小明开始考虑用Jenkins来启动Jar包:

 经过测试,启动后会是这样的:

去观察实际执行机情况,会发现启动的Java进程已经退出了,主要原因是Jenkins认为这个任务执行完成了,会主动退出cmd。由于cmd执行没有nohup这样的模式,退出cmd窗口就意味着进程关闭。

那我们试试不退出,不使用start来启动:

经过测试,启动后会是这样的:

这样其实是符合我们的预期了,但是,这样的Jenkins pipline无法关闭,无法实现我们每次任务执行前重启所有hub和node的需求。

小明又尝试了在Jenkins调用bat,也无法做到执行bat最终不退出cmd窗口。

小明也尝试了微软提供的cmd加强工具PStools中的psexec,最终也因为没有域账户而无法实现。

如果直接在执行机上执行bat,是可以实现Grid的重启的,其实我们缺少的是Jenkins本地执行bat的方法。

小明只好又请教老Aino,老Aino只回复了几个字母:

“taskschd.msc”

小明茅塞顿开,一顿操作后,搞定了:

这里注意下:执行bat必须指定“起始于",否则闪退:

然后在Jenkins里设定cmd里调用这个任务即可:

由于bat还是能做很多事情的,所以最终这个任务调用的bat内容如下:

从上到下依次作用为:

关闭所有的IE的webdriver进程(防止上次测试有未正常退出的IE驱动)

关闭所有的Chrome的webdriver进程 (防止上次测试有未正常退出的Chrome驱动)

关闭所有的IE浏览器(防止上次测试有未正常退出的IE浏览器)

关闭所有的Chrome浏览器(防止上次测试有未正常退出的Chrome浏览器)

关闭所有已经打开的java程序(这里是为了关闭已经打开的Grid的hub和node)

启动一个最大16实例数的hub(只有一台机子执行这个)

启动能使用2个Chrome实例的node

启动能使用1个IE实例的node

这样,每次只要在自动化执行前通过Jenkins调用每个执行机任务计划中的这个任务,就能通过调用这个bat来初始化所有的执行机和执行机上部署的Hub和Node:

由于Grid的Hub和Node没有强制的启动先后顺序(即使node启动时hub没有启动,node也会在hub上线后主动注册上),所以不用刻意安排执行机执行顺序。

第三次主动优化---增加重跑失败用例,并提供录屏功能:

由于当前自动化策略是脚本交付后,由功能测试同事对失败脚本进行失败分析和问题定位,所以需要更加聚焦失败用例,而且给出更直观的问题定位方法,所以我们需要:

1. 第一次pipeline执行的失败用例,再起个任务重新跑,这样能大幅减少log,聚焦失败分析

2. 重新跑的任务,可以开启录像功能,直接看执行录像,更容易进行失败分析

虽然robot提供了重试功能(参数-R),但是这个功能是任务内的,无法重新实现减少log的功能,而且也无法在一个任务中开启/关闭录屏。

目前Robot Framework的录屏是ScreenCapLibrary提供的,官方推荐使用已经对这个库进行二次封装的robotframework-autorecorder库,这个库只要引用,就会对执行过程进行录屏。

但是,这个录屏是操作系统级的,也就是说,录屏的时候有两个条件:

1. 不能多线程,否则录出来的没有意义(多线程会不停的切焦点)

2. 只能录Jenkins-master所在的执行机的屏幕,因为Grid不能分发操作系统

所以,这个重试的任务就只能用原始的pybot(Robot)来跑,并且不使用Grid。效果就是比如500条自动化脚本,第一轮失败了3条,在重试任务中就单线程跑,并且录屏。

robot指定用例执行,可以通过传入需要执行的用例的编号的txt来做到。

要实现这个功能,还要分析如何拿到并且重跑上次失败的用例,我们知道上次用例的成功失败的数据是保留在output.xml中的,恰好Robot Framework中有默认自带的库XML,我们可以对其进行分析,拿到我们想要的数据,并通过这些数据生成失败用例的列表txt:

最终,分析并生成失败用例重跑脚本的代码结构如下:

由于pabot(并发)生成的output.xml是用robot自带的log合并工具rebot进行合并的,这两个xml的结构略有差异,所以在代码中增加了自动判断xml是哪种并分别处理的逻辑。

主函数(关键字)只有一个入参,指向当前目录的output.xml文件:

这个主函数的内容如下:

argfile生成[Arguments]    ${output.xml路径}log    **********结构化XML并分析XML的出处是pabot还是pybot**********${xml}    Parse Xml    ${output.xml路径}Set Suite Variable    ${xml}log    ${xml}${xmldict}    get element    ${xml}${robot generator}    Get From Dictionary    ${xmldict.attrib}    generatorlog    ${robot generator}${is robot}    Evaluate    'Robot' in '${robot generator}'log    ${is robot}log    **********这里分pabot和pybot是因为这两个的output.xml结构不同,pybot比pabot多了一层suite**********run keyword if    '${is robot}'=='False'    argfile生成-pabot    ${output.xml路径}run keyword if    '${is robot}'=='True'    argfile生成-pybot    ${output.xml路径}

主要逻辑如下:

1. 结构化XML

2. 判断是pabot还是pybot的xml,分别进各自的函数处理

3. 通过获取测试套的数量和名称

4. 通过双层for循环获取每个测试套中失败的用例名称,并写入list

5. 使用format把list中的元素(失败用例名称)分别按要求的格式写入txt

6. 根据情况生成rerun.bat,方便Jenkins直接调用

全部代码如下:

*** Settings ***
Test Teardown
Test Timeout      15 minutes
Resource          ../05引用这个即可使用所有关键字和库.txt
Library           XML*** Variables ****** Test Cases ***
argfile生成argfile生成    ${CURDIR}\\output.xml*** Keywords ***
统计列表中元素包含某字符出现的次数[Arguments]    ${list}    ${string}${list-child}    Get Child Elements    ${list}log    ${list-child}${list-child-str}    Convert To String    ${list-child}log    ${list-child-str}${string count}    Get Count    ${list-child-str}    ${string}log    ${string count}[Return]    ${string count}失败用例编号插入-pybot[Arguments]    ${suite}    ${testcase number}log    **********根据已知的suite位置和testcase数目(位置)循环插入字典**********FOR    ${testcase}    IN RANGE    0    ${testcase number}${testcasedict}    get element    ${xml[0][0][${suite}][${testcase}]}${testcase name}    Get From Dictionary    ${testcasedict.attrib}    name${statusdict}    get element    ${xml[0][0][${suite}][${testcase}][-1]}${status}    Get From Dictionary    ${statusdict.attrib}    statusrun keyword if    '${status}'=='FAIL'    Append To List    ${argfile}    ${testcase name}ENDlog    ${argfile}失败用例编号插入-pabot[Arguments]    ${suite}    ${testcase number}log    **********根据已知的suite位置和testcase数目(位置)循环插入字典**********FOR    ${testcase}    IN RANGE    0    ${testcase number}${testcasedict}    get element    ${xml[0][${suite}][${testcase}]}${testcase name}    Get From Dictionary    ${testcasedict.attrib}    name${statusdict}    get element    ${xml[0][${suite}][${testcase}][-1]}${status}    Get From Dictionary    ${statusdict.attrib}    statusrun keyword if    '${status}'=='FAIL'    Append To List    ${argfile}    ${testcase name}ENDlog    ${argfile}argfile生成-pabot[Arguments]    ${output.xml路径}[Documentation]    分析output.xml并回传结果到CTP......    编写人:山豆根行者 2021-05-19log    **********统计xml中包含的suite数目**********${suite number}    统计列表中元素包含某字符出现的次数    ${xml[0]}    'suite'log    ${suite number}${argfile}    Create listSet Suite Variable    ${argfile}FOR    ${suite}    IN RANGE    0    ${suite number}log    ${suite}log    **********统计suite中testcase数目**********${testcase number}    统计列表中元素包含某字符出现的次数    ${xml[0][${suite}]}    'test'失败用例编号插入-pabot    ${suite}    ${testcase number}ENDlog    ${argfile}${number of fail}    Evaluate    len(${argfile})log    ${number of fail}${final str}    Set Variable    ${EMPTY}Set Suite Variable    ${final str}FOR    ${i}    IN RANGE    0    len(${argfile})${final str}=    Format String    ${final str}\n--suite\n*\n--test\n{}    ${argfile[${i}]}ENDlog    ${final str}run keyword if    '${number of fail}'!='0'    create file    ${CURDIR}\\argfile.txt    ${final str}run keyword if    '${number of fail}'=='0'    create file    ${CURDIR}\\argfile.txt    --suite\n*\n--test\nJXK-2021-381-yl652863\ncreate file    ${CURDIR}\\rerun.bat    robot -v Jenkins-Browser:ie -v 默认重试次数:1x -o ${CURDIR}\\retry.xml --argumentfile ${CURDIR}\\argfile.txt ${CURDIR}\\01测试用例    encoding=ansiargfile生成-pybot[Arguments]    ${output.xml路径}[Documentation]    分析output.xml并回传结果到CTP......    编写人:山豆根行者 2021-05-19log    **********统计xml中包含的suite数目**********${suite number}    统计列表中元素包含某字符出现的次数    ${xml[0][0]}    'suite'log    ${suite number}${argfile}    Create listSet Suite Variable    ${argfile}FOR    ${suite}    IN RANGE    0    ${suite number}log    ${suite}log    **********统计suite中testcase数目**********${testcase number}    统计列表中元素包含某字符出现的次数    ${xml[0][0][${suite}]}    'test'失败用例编号插入-pybot    ${suite}    ${testcase number}ENDlog    ${argfile}${number of fail}    Evaluate    len(${argfile})log    ${number of fail}${final str}    Set Variable    ${EMPTY}Set Suite Variable    ${final str}FOR    ${i}    IN RANGE    0    len(${argfile})${final str}=    Format String    ${final str}\n--suite\n*\n--test\n{}    ${argfile[${i}]}ENDlog    ${final str}run keyword if    '${number of fail}'!='0'    create file    ${CURDIR}\\argfile.txt    ${final str}run keyword if    '${number of fail}'=='0'    create file    ${CURDIR}\\argfile.txt    --suite\n*\n--test\nJXK-2021-381-yl652863\ncreate file    ${CURDIR}\\rerun.bat    robot -v Jenkins-Browser:ie -v 默认重试次数:1x -o ${CURDIR}\\retry.xml --argumentfile ${CURDIR}\\argfile.txt ${CURDIR}\\01测试用例    encoding=ansiargfile生成[Arguments]    ${output.xml路径}log    **********结构化XML并分析XML的出处是pabot还是pybot**********${xml}    Parse Xml    ${output.xml路径}Set Suite Variable    ${xml}log    ${xml}${xmldict}    get element    ${xml}${robot generator}    Get From Dictionary    ${xmldict.attrib}    generatorlog    ${robot generator}${is robot}    Evaluate    'Robot' in '${robot generator}'log    ${is robot}log    **********这里分pabot和pybot是因为这两个的output.xml结构不同,pybot比pabot多了一层suite**********run keyword if    '${is robot}'=='False'    argfile生成-pabot    ${output.xml路径}run keyword if    '${is robot}'=='True'    argfile生成-pybot    ${output.xml路径}

以上脚本执行完,会生成包含所有失败用例编号的txt,和重试需要调用的bat,bat里面的内容为:

robot -v Jenkins-Browser:ie -v 默认重试次数:1x -o ${CURDIR}\\retry.xml --argumentfile ${CURDIR}\\argfile.txt ${CURDIR}\\01测试用例

这里,使用了robot的一个技巧:通过Jenkins传参来控制浏览器类型,重试次数等,做到一套代码,配置不同,后面会详细说这个功能。

录屏还有一个,就是要改造代码,让其支持录屏,录屏这个库不能在使用前才Import,这样无法录屏,所以我们使用直接改造我们导入库的txt文件的方法,让其支持录屏

至此,录屏需要的功能组件已全部完成,在Jenkins中组装一下:

大功告成,重试时会增加录像功能,在用例最后内嵌HTML5支持的视频格式webm:

第四次主动优化---元素等待中的各种骚操作:

在目前框架的使用中,小明发现在等待元素过程中有很多可以优化的地方,主要包括:

1. Frame切换需要手动,多层Frame要不停的Select和Unselect

2. 很多元素需要滚屏才能看到,否则无法操作,给滚屏死值会因为分辨率改变而失败

3. 截图时看不到要操作的元素对象,需要结合脚本上下文

4. 截图如果都保留,占用大量的磁盘空间;截图如果不保留,不好定位失败原因

小明通过RF自身提供的库,针对这4个问题都做出了解决方案:

先给出目前已经解决上面4个问题的最终版本的”智能等待“的主关键字:

*** Settings ***
Library           Dialogs
Library           Collections
Library           OperatingSystem
Library           Screenshot
Library           DateTime
Library           Process
Library           DatabaseLibrary
Library           SeleniumLibrary    run_on_failure=截图
Library           String
Resource          05引用这个即可使用所有关键字和库.txt
Library           informix.py
Library           RequestsLibrary
Library           ExcelLibrary*** Variables ***
${默认等待时间}         20s
${默认重试次数}         3x
${默认上传文件路径}       ${CURDIR}\\上传文件
${已执行次数}          0
${主用例是ie}         0
${now-time}       9999-12-31 23:59:59.999*** Keywords ***
通用组件 等待 元素[Arguments]    ${页面元素定位}    ${最大等待时间}=${默认等待时间}[Documentation]    通过检测页面元素是否存在,页面元素是否可用,再根据元素的卷轴高度和浏览器高度,计算需要滚屏多少才能尽量把元素放到web页面正中间,并且滚屏......    注意:如果页面元素会因为滚屏而消失(比如下拉框),请不要使用这个关键字Repeat Keyword    10x    Unselect Frame${is-css}    Run Keyword And Return Status    Should Contain    ${页面元素定位}    css=${emp}    Replace String    ${页面元素定位}    css=    ${EMPTY}${fin}    Replace String    ${emp}    "    '${style}    Set Variable If    ${is-css}    document.querySelector("${fin}").style.backgroundColor    document.evaluate("${fin}", document).iterateNext().style.backgroundColor通用组件 自动切换frame    ${页面元素定位}等待 直到页面元素可用    ${页面元素定位}    ${最大等待时间}Wait Until Keyword Succeeds    10x    1s    通用组件 获取 元素居中滚动像素    ${页面元素定位}run keyword if    ${滚动值} >= 0    通用组件 页面滚动    ${滚动值}等待 直到页面元素可见    ${页面元素定位}    ${最大等待时间}等待 直到页面不包含元素    //div[@class="loading"]    ${最大等待时间}${org-bg}    Execute Javascript    return ${style}run keyword if    "style" not in "${fin}"    Execute Javascript    ${style}='#B9EF0B'截图run keyword if    "style" not in "${fin}"    Execute Javascript    ${style}='${org-bg}'

1. 自动切换 Frame:

这个功能,应该很少有人实现,主要就是一个递归的实际应用:

通用组件 自动切换frame[Arguments]    ${页面元素定位}    ${最大等待时间}=${默认等待时间}[Documentation]    通过循环使用正确得iframeWait Until Keyword Succeeds    20s    0.1s    通用组件 循环选择frame    ${页面元素定位}通用组件 循环选择frame[Arguments]    ${页面元素定位}[Documentation]    通过循环获取frame数量并查看是否能在frame中找到目标元素${noframe}    Create List    noframe${所有的frame}    Get WebElements    //iframe${所有的frame}    Set Variable If    'selenium' in '${所有的frame}'    ${所有的frame}    ${noframe}FOR    ${frame}    IN    ${所有的frame}${不使用frame}    Run Keyword And Return Status    等待 直到页面包含元素    ${页面元素定位}    0.1sExit For Loop If    ${不使用frame}Select Frame    ${frame}${是这个frame}    Run Keyword And Return Status    等待 直到页面包含元素    ${页面元素定位}    0.1sExit For Loop If    ${是这个frame}${多层循环结果}    Run Keyword And Return Status    通用组件 循环选择frame    ${页面元素定位}Exit For Loop If    ${多层循环结果}Unselect FrameEND等待 直到页面包含元素    ${页面元素定位}    0.1s

逻辑如下:

我们先看 通用组件 循环选择frame 这个关键字:

1. 创建一个只有一个值"noframe"的列表

2. 使用get webelements获取所有的iframe

3. 根据布尔表达式的结果分别赋予${所有的frame}值,如果其中本来就包含'selenium'字符串,说明页面上包含iframe,那就赋值为本身,否则就是我们创建的"noframe"列表。这么做是为了适配没有iframe的情况

4. 一个for循环,在for循环第一行先判断如果不切换frame,是否能直接找到元素,如果能找到,直接退出循环

5. 后面再切换到获取到的iframe的webelements(这里直接切换复数的frame),然后再看是否能直接找到元素,如果能找到,直接退出循环

6. 最关键的来了,在这里再调用我们这个关键字本身,做到递归的效果,可以在目前Frame层级上再次进入”二层Frame“,如果”二层Frame“找不到元素且还有”三层Frame“,会自动递归下去,直到没找到并且没有”下一层Frame“,循环会一层层退出来

7. 如果都没有找到,会在每一层触发Unselect Frame一次,会退出到未选择任何Frame的情况

8. 在for循环外再放一个断言,让关键字可以有失败的状态

我们再看 通用组件 自动切换frame 这个关键字:

1. 在通用组件 自动切换frame调用 中使用Wait Until Keyword Succeeds来调用上面的关键字,并给予20秒的最长循环时间

2. 在20秒内,如果关键字失败,会不断循环

还有一个关键操作,就是上面没有处理有frame时,已经切换进去的状态,所以我们在通用组件 等待 元素的入口处,加了这个:

Repeat Keyword    10x    Unselect Frame

这样,只要是10层内的frame,都能直接退出来(开发也不会设计10层Frame....吧)

更新一个问题:SeleniumLibrary的Unselect Frame在某些特殊代码逻辑下,会退出多层Frame,导致递归不能覆盖所有Frame分支:

解决方案:

使用随机列表来取Frame的Web Element,这样可以通过多次循环找到需要操作的元素(除非你运气特别不好)

具体代码如下:

*** Settings ***
Library           Dialogs
Library           Collections
Library           OperatingSystem
Library           Screenshot
Library           DateTime
Library           Process
Library           DatabaseLibrary
Library           SeleniumLibrary    run_on_failure=No Operation
Library           String
Resource          05引用这个即可使用所有关键字和库.txt
Library           informix.py
Library           RequestsLibrary
Library           ExcelLibrary*** Variables ***
${默认等待时间}         20s
${默认重试次数}         1x
${默认上传文件路径}       ${CURDIR}\\上传文件
${已执行次数}          0
${主用例是ie}         0
${now-time}       9999-12-31 23:59:59.999
@{Frame List}
@{需要使用iframe的服务器地址}    172.18.184.44*** Keywords ***
通用组件 等待 元素[Arguments]    ${页面元素定位}    ${最大等待时间}=${默认等待时间}    ${是否滚屏}=是[Documentation]    通过检测页面元素是否存在,页面元素是否可用,再根据元素的卷轴高度和浏览器高度,计算需要滚屏多少才能尽量把元素放到web页面正中间,并且滚屏......    注意:如果页面元素会因为滚屏而消失(比如下拉框),请不要使用这个关键字Repeat Keyword    10x    Unselect Frame${is-css}    Run Keyword And Return Status    Should Contain    ${页面元素定位}    css=${emp}    Replace String    ${页面元素定位}    css=    ${EMPTY}${fin}    Replace String    ${emp}    "    '${style}    Set Variable If    ${is-css}    document.querySelector("${fin}").style.backgroundColor    document.evaluate("${fin}", document).iterateNext().style.backgroundColor${当前页面url}    Get Locationrun keyword if    "@{需要使用iframe的服务器地址}[0]" not in "${当前页面url}"    等待 直到页面包含元素    ${页面元素定位}    ${最大等待时间}run keyword if    "@{需要使用iframe的服务器地址}[0]" in "${当前页面url}"    通用组件 自动切换frame    ${页面元素定位}run keyword if    '${是否滚屏}'=='是'    Wait Until Keyword Succeeds    10x    1s    通用组件 获取 元素居中滚动像素    ${页面元素定位}${滚动值}    Set Variable If    '${是否滚屏}'=='是'    ${滚动值}    -1run keyword if    ${滚动值} >= 0    通用组件 页面滚动    ${滚动值}等待 直到页面元素可见    ${页面元素定位}    ${最大等待时间}${org-bg}    Execute Javascript    return ${style}Wait Until Page Does Not Contain Element    //*[contains(text(),'正在处理')][contains(text(),'请稍')]    60srun keyword if    "style" not in "${fin}"    Execute Javascript    ${style}='#B9EF0B'截图run keyword if    "style" not in "${fin}"    Execute Javascript    ${style}='${org-bg}'通用组件 自动切换frame[Arguments]    ${页面元素定位}    ${最大等待时间}=${默认等待时间}[Documentation]    通过循环使用正确得iframeWait Until Page Does Not Contain Element    //*[contains(text(),'正在处理')][contains(text(),'请稍')]    60s${使用上一次Frame成功}    Run Keyword And Return Status    Wait Until Keyword Succeeds    1s    0.001s    通用组件 选择上次frame    ${页面元素定位}Run Keyword If    not ${使用上一次Frame成功}    Repeat Keyword    10x    Unselect FrameRun Keyword If    not ${使用上一次Frame成功}    Wait Until Keyword Succeeds    30s    0.001s    通用组件 循环选择frame    ${页面元素定位}通用组件 选择上次frame[Arguments]    ${页面元素定位}[Documentation]    使用上次的frame选择并查看是否能在frame中找到目标元素Repeat Keyword    10x    Unselect FrameWait Until Page Does Not Contain Element    //*[contains(text(),'正在处理')][contains(text(),'请稍')]    60sFOR    ${Frame}    IN    ${Frame List}${元素在Frame外}    Run Keyword And Return Status    等待 直到页面包含元素    ${页面元素定位}    0.001sExit For Loop If    ${元素在Frame外}Select Frame    ${Frame}END等待 直到页面包含元素    ${页面元素定位}    0.001s通用组件 循环选择frame[Arguments]    ${页面元素定位}[Documentation]    通过循环获取frame数量并查看是否能在frame中找到目标元素Wait Until Page Does Not Contain Element    //*[contains(text(),'正在处理')][contains(text(),'请稍')]    60s${Frame List}    Create ListSet Test Variable    ${Frame List}${number list}    Create List${noframe}    Create List    noframe${所有的frame}    Get WebElements    //div[contains(@style,'display: block')]//iframe${所有的frame}    Set Variable If    'selenium' in '${所有的frame}'    ${所有的frame}    ${noframe}${frame长度}    Get Length    ${所有的frame}log    ${frame长度}FOR    ${i}    IN RANGE    0    ${frame长度}Append To List    ${number list}    ${i}END${Random number list}    Evaluate    random.sample(${number list}, len(${number list}))    modules=randomlog    ${Random number list}FOR    ${i}    IN    @{Random number list}${不使用frame}    Run Keyword And Return Status    等待 直到页面包含元素    ${页面元素定位}    0.001sExit For Loop If    ${不使用frame}Select Frame    ${所有的frame}[${i}]${是这个frame}    Run Keyword And Return Status    等待 直到页面包含元素    ${页面元素定位}    0.001sRun Keyword If    ${是这个frame}    Append To List    ${Frame List}    ${所有的frame}[${i}]log    ${Frame List}Exit For Loop If    ${是这个frame}${二层循环结果}    Run Keyword And Return Status    通用组件 循环选择frame    ${页面元素定位}Exit For Loop If    ${二层循环结果}Unselect FrameEND等待 直到页面包含元素    ${页面元素定位}    0.001s

这个不解释了,代码已经很清楚了。

2. 自动滚屏,尽量把操作对象放到屏幕中间:

虽然这个是以前就实现的功能,但是这个功能非常实用。

很多同学遇到长页面需要滚屏时,都是估算着滚多少像素,比如滚300,但是这个值其实和分辨率强相关,而且这个值也会随着页面变更而变化。

那怎么办呢?

小明直接尝试了SeleniumLibrary提供的滚屏专用关键字Scroll Element Into View,但是这个关键字不稳定,有时候就”滚“不过去

那我们就自己实现:

SeleniumLibrary有提供两个关键字:

获取从元素到页面顶端的卷轴高度:

获取从元素到页面最左端的卷轴宽度:

通过这两个关键字,我们就能获取需要操作的元素相对页面的卷轴高和卷轴宽,我们一般都是上下滚动,所以最后实现完成的脚本如下:

*** Settings ***
Library           Dialogs
Library           Collections
Library           OperatingSystem
Library           Screenshot
Library           DateTime
Library           Process
Library           DatabaseLibrary
Library           SeleniumLibrary    run_on_failure=截图
Library           String
Resource          05引用这个即可使用所有关键字和库.txt
Library           informix.py
Library           RequestsLibrary
Library           ExcelLibrary*** Variables ***
${默认等待时间}         20s
${默认重试次数}         1x
${默认上传文件路径}       ${CURDIR}\\上传文件
${已执行次数}          0
${主用例是ie}         0
${now-time}       9999-12-31 23:59:59.999*** Keywords ***
通用组件 等待 元素[Arguments]    ${页面元素定位}    ${最大等待时间}=${默认等待时间}    ${是否滚屏}=是[Documentation]    通过检测页面元素是否存在,页面元素是否可用,再根据元素的卷轴高度和浏览器高度,计算需要滚屏多少才能尽量把元素放到web页面正中间,并且滚屏......    注意:如果页面元素会因为滚屏而消失(比如下拉框),请不要使用这个关键字Repeat Keyword    10x    Unselect Frame${is-css}    Run Keyword And Return Status    Should Contain    ${页面元素定位}    css=${emp}    Replace String    ${页面元素定位}    css=    ${EMPTY}${fin}    Replace String    ${emp}    "    '${style}    Set Variable If    ${is-css}    document.querySelector("${fin}").style.backgroundColor    document.evaluate("${fin}", document).iterateNext().style.backgroundColor通用组件 自动切换frame    ${页面元素定位}等待 直到页面元素可用    ${页面元素定位}    ${最大等待时间}run keyword if    '${是否滚屏}'=='是'    Wait Until Keyword Succeeds    10x    1s    通用组件 获取 元素居中滚动像素    ${页面元素定位}${滚动值}    Set Variable If    '${是否滚屏}'=='是'    ${滚动值}    -1run keyword if    ${滚动值} >= 0    通用组件 页面滚动    ${滚动值}等待 直到页面元素可见    ${页面元素定位}    ${最大等待时间}等待 直到页面不包含元素    //div[@class="loading"]    ${最大等待时间}${org-bg}    Execute Javascript    return ${style}run keyword if    "style" not in "${fin}"    Execute Javascript    ${style}='#B9EF0B'截图run keyword if    "style" not in "${fin}"    Execute Javascript    ${style}='${org-bg}'通用组件 获取 元素居中滚动像素[Arguments]    ${页面元素定位}通用组件 页面滚动    0    #滚动到web顶端${宽度}    ${高度}    获取 web页面大小Repeat Keyword    10x    Unselect Frame通用组件 自动切换frame    ${页面元素定位}${卷轴高度}    获取 元素距离web顶端的卷轴高度    ${页面元素定位}log    ${卷轴高度}${滚动值}=    Evaluate    ${卷轴高度}-(${高度}/2)log    ${滚动值}Set Test Variable    ${滚动值}通用组件 页面滚动[Arguments]    ${页面滚动值}[Documentation]    正值向下滚动,负值向上滚动Execute Javascript    var q=document.documentElement.scrollTop=${页面滚动值}

代码逻辑如下:

1. 获取当前浏览器的高

2. 确定元素出现后,获取元素的卷轴高

3. 计算需要滚屏多少像素能尽量把元素放屏幕水平位置的中间

4. 如果这个值大于0,说明有必要滚屏

5. 滚动屏幕到最顶端

6. 滚动已经计算好的像素值

技巧1. 用一个入参控制是否使用滚屏(比如已经打开下拉框,滚屏会导致下拉框收起)

技巧2. 如果不滚屏,给滚屏值强制赋值-1

3. 自动高亮操作对象:

很多框架都有这个功能,但是robot没有提供,我们在等待元素中直接实现就可以了:

通用组件 等待 元素[Arguments]    ${页面元素定位}    ${最大等待时间}=${默认等待时间}    ${是否滚屏}=否[Documentation]    通过检测页面元素是否存在,页面元素是否可用,再根据元素的卷轴高度和浏览器高度,计算需要滚屏多少才能尽量把元素放到web页面正中间,并且滚屏......    注意:如果页面元素会因为滚屏而消失(比如下拉框),请不要使用这个关键字Repeat Keyword    10x    Unselect Frame${is-css}    Run Keyword And Return Status    Should Contain    ${页面元素定位}    css=${emp}    Replace String    ${页面元素定位}    css=    ${EMPTY}${fin}    Replace String    ${emp}    "    '${style}    Set Variable If    ${is-css}    document.querySelector("${fin}").style.backgroundColor    document.evaluate("${fin}", document).iterateNext().style.backgroundColor通用组件 自动切换frame    ${页面元素定位}等待 直到页面元素可用    ${页面元素定位}    ${最大等待时间}run keyword if    '${是否滚屏}'=='是'    Wait Until Keyword Succeeds    10x    1s    通用组件 获取 元素居中滚动像素    ${页面元素定位}${滚动值}    Set Variable If    '${是否滚屏}'=='是'    ${滚动值}    -1run keyword if    ${滚动值} >= 0    通用组件 页面滚动    ${滚动值}等待 直到页面元素可见    ${页面元素定位}    ${最大等待时间}等待 直到页面不包含元素    //div[@class="loading"]    ${最大等待时间}${org-bg}    Execute Javascript    return ${style}run keyword if    "style" not in "${fin}"    Execute Javascript    ${style}='#B9EF0B'截图run keyword if    "style" not in "${fin}"    Execute Javascript    ${style}='${org-bg}'

代码逻辑如下:(我们只用xpath和css定位)

1. 判断是xpath还是css

2. 格式化元素定位为js使用的格式

3. 等元素可操作后,获取元素的原始背景色

4. 替换元素背景色为高亮色

5. 截图完成后,替换背景色为原始背景色

4. 保留每次失败前30秒的截图:

由于我们使用了封装整个用例为1个单独的关键字,并使用Wait Until Keyword Succeeds来调用这个关键字,做到了立刻重试的效果,所以这里保留30秒截图是指每次失败前30秒的截图。

这样,就有几个关键点:

1. 必须有记录Wait Until Keyword Succeeds轮次的方法

2. 必须有记录时间的方法

3. 必须有删除文件的方法

首先解决轮次和时间记录的问题:

把用例调用写成这样:

demo[Documentation]    demoWait Until Keyword Succeeds    ${默认重试次数}    5s    Run Keywords    通用组件 执行次数计数...    AND    testdemo

使用Run Keywords重试多个关键字,并在真正用例封装的关键字之前调用一个计数关键字,这样就能在Wait Until Keyword Succeeds每次失败后,在这里计数,并且还能做些别的操作,所有代码如下:

*** Settings ***
Library           Dialogs
Library           Collections
Library           OperatingSystem
Library           Screenshot
Library           DateTime
Library           Process
Library           DatabaseLibrary
Library           SeleniumLibrary    run_on_failure=截图
Library           String
Resource          05引用这个即可使用所有关键字和库.txt
Library           informix.py
Library           RequestsLibrary
Library           ExcelLibrary*** Variables ***
${默认等待时间}         20s
${默认重试次数}         3x
${默认上传文件路径}       ${CURDIR}\\上传文件
${已执行次数}          0
${主用例是ie}         0
${now-time}       9999-12-31 23:59:59.999*** Keywords ***
通用组件 等待 元素[Arguments]    ${页面元素定位}    ${最大等待时间}=${默认等待时间}    ${是否滚屏}=否[Documentation]    通过检测页面元素是否存在,页面元素是否可用,再根据元素的卷轴高度和浏览器高度,计算需要滚屏多少才能尽量把元素放到web页面正中间,并且滚屏......    注意:如果页面元素会因为滚屏而消失(比如下拉框),请不要使用这个关键字Repeat Keyword    10x    Unselect Frame${is-css}    Run Keyword And Return Status    Should Contain    ${页面元素定位}    css=${emp}    Replace String    ${页面元素定位}    css=    ${EMPTY}${fin}    Replace String    ${emp}    "    '${style}    Set Variable If    ${is-css}    document.querySelector("${fin}").style.backgroundColor    document.evaluate("${fin}", document).iterateNext().style.backgroundColor通用组件 自动切换frame    ${页面元素定位}等待 直到页面元素可用    ${页面元素定位}    ${最大等待时间}run keyword if    '${是否滚屏}'=='是'    Wait Until Keyword Succeeds    10x    1s    通用组件 获取 元素居中滚动像素    ${页面元素定位}${滚动值}    Set Variable If    '${是否滚屏}'=='是'    ${滚动值}    -1run keyword if    ${滚动值} >= 0    通用组件 页面滚动    ${滚动值}等待 直到页面元素可见    ${页面元素定位}    ${最大等待时间}等待 直到页面不包含元素    //div[@class="loading"]    ${最大等待时间}${org-bg}    Execute Javascript    return ${style}run keyword if    "style" not in "${fin}"    Execute Javascript    ${style}='#B9EF0B'截图run keyword if    "style" not in "${fin}"    Execute Javascript    ${style}='${org-bg}'通用组件 获取 元素居中滚动像素[Arguments]    ${页面元素定位}通用组件 页面滚动    0    #滚动到web顶端${宽度}    ${高度}    获取 web页面大小Repeat Keyword    10x    Unselect Frame通用组件 自动切换frame    ${页面元素定位}${卷轴高度}    获取 元素距离web顶端的卷轴高度    ${页面元素定位}log    ${卷轴高度}${滚动值}=    Evaluate    ${卷轴高度}-(${高度}/2)log    ${滚动值}Set Test Variable    ${滚动值}通用组件 页面滚动[Arguments]    ${页面滚动值}[Documentation]    正值向下滚动,负值向上滚动Execute Javascript    var q=document.documentElement.scrollTop=${页面滚动值}通用组件 关闭所有浏览器${is passed}    Run Keyword And Return Status    Run Keyword If Test Failed    testRun Keyword And Ignore Error    获取 cookieRun Keyword And Ignore Error    获取 urlRun Keyword And Ignore Error    关闭 所有浏览器Run Keyword And Ignore Error    Run Keyword If Test Failed    Remove Tags    重试通过    重试2次通过Run Keyword And Ignore Error    Run Keyword If    ${已执行次数}==1 and '${is passed}'=='True'    remove files    pabot_results\\*\\*${TEST_NAME}*.png    *${TEST_NAME}*.pngRun Keyword And Ignore Error    通用组件 循环删除30秒前的图片通用组件 执行次数计数[Documentation]    编写人:山豆根行者 2021-05-28Run Keyword And Ignore Error    通用组件 循环删除30秒前的图片${已执行次数}    Evaluate    ${已执行次数}+1Set Test Variable    ${已执行次数}Run Keyword If    ${已执行次数}==2    Set Tags    重试通过    重试1次通过Run Keyword If    ${已执行次数}==3    run keywords    Set Tags    重试2次通过...    AND    Remove Tags    重试1次通过log    该案例已执行:${已执行次数}次Run Keyword And Ignore Error    关闭 所有浏览器${now-time}    Get Current DateSet Test Variable    ${now-time}通用组件 循环删除30秒前的图片[Arguments]    ${需要保留图片的秒数}=30${delete sec}    Evaluate    ${sectime}-${需要保留图片的秒数}FOR    ${要删除的秒数}    IN RANGE    0    ${delete sec}remove files    pabot_results\\*\\${要删除的秒数}_*_${TEST_NAME}_Round${已执行次数}.png    ${要删除的秒数}_*_${TEST_NAME}_Round${已执行次数}.pngEND截图${now}    Get Current Date${sectime}    Subtract date From date    ${now}    ${now-time}${sectime}    Evaluate    int(${sectime})log    ${sectime}Set Test Variable    ${sectime}Capture Page Screenshot    ${sectime}_{index}_${TEST_NAME}_Round${已执行次数}.png

代码逻辑如下:

1. 执行前先忽略错误执行已经写好的删除从失败起30秒前图片脚本,但是其实这时候没有截图,会失败,但是由于我们忽略错误,所以没关系

2. 给已执行次数+1(之前有给默认值0),这样执行次数就为1,代表第一轮

3. 设置执行次数为测试用例级变量

4. 根据执行次数给用例打tag(额外功能,统计用)

5. 忽略错误关闭所有浏览器(防止有上次未关闭的浏览器)

6. 获取当前时间并设置为测试用例级变量

我们把普通截图封装,所有的截图都调用我们自己封装的,包括Selenium的失败:

Library           SeleniumLibrary    run_on_failure=截图

以下是截图代码逻辑

7. 获取现在时间,并和执行次数计数里获取的时间进行减法,获取一个“从本轮开始跑到现在的绝对时间”

8. 保存截图时,截图名字用各种变量组合:

${sectime}_{index}_${TEST_NAME}_Round${已执行次数}.png

${sectime} --- 从本轮开始跑到现在的绝对时间

{index}--- robot内置变量,自然数递增,防止重名

${TEST_NAME}--- robot内置变量,本条用例名字

${已执行次数}--- 当前轮次数,为了区分多个轮次,否则会误删上轮的

然后我们看下真正删除图片那部分脚本 通用组件 循环删除30秒前的图片:

9. 最后一个图片获取的“绝对时间”减去30秒,就是我们本轮要删除的图片的秒数

10. 做一个这个删除秒数的for循环,去删除图片:

这个是pabot(多线程)时图片保存的路径和图片名称特征:

pabot_results\\*\\${要删除的秒数}_*_${TEST_NAME}_Round${已执行次数}.png

这个是pybot(单线程)时图片保存的路径和图片名称特征:

${要删除的秒数}_*_${TEST_NAME}_Round${已执行次数}.png

最后,还要在Teardown的关键字通用组件 关闭所有浏览器里处理第一次就通过和第三次也失败的截图

这样就处理完成了保留每次失败前30秒截图的功能

第五次主动优化---自带验证的click和input:

随着自动化用例规模的不断增加,小明发现每天都有用例因为点击没有点上,输入没有输入正确而失败。

主要原因基本都是虽然元素已经可见可用了,但是由于一些浏览器操作没有完成(比如其它元素加载,等待接口数据返回,蒙层未消失等等),Selenium会“以为”已经操作成功了。

所以,需要再加一个我们自己的判断:

点击操作:

分析:点击操作其实会造成两个结果:

1. 被操作对象被点击,导致其被聚焦,也就是focused

2. 被操作对象被点击,导致其消失,也就是Not Visible

所以做个循环来判断状态即可:(基本原理,代码根据自己业务情况优化)

点击 元素[Arguments]    ${页面元素定位}FOR    ${i}    IN RANGE    0    5click element    ${页面元素定位}${是否已经点中}    Run Keyword And Return Status    Element Should Be Focused    ${页面元素定位}Exit For Loop If    '${是否已经点中}'=='True'${是否已经消失}    Run Keyword And Return Status    Wait Until Element Is Not Visible    20sExit For Loop If    '${是否已经消失}'=='True'END

输入操作:

分析:输入后,取输入的值,比对,不正确就重新输入:(基本原理,代码根据自己业务情况优化)

输入 文本[Arguments]    ${页面元素定位}    ${文本}FOR    ${i}    IN RANGE    0    5sleep    ${i}Clear Element Text    ${页面元素定位}input text    ${页面元素定位}    ${文本}${实际填入的值}    Get Value    ${页面元素定位}截图Exit For Loop If    '${实际填入的值}'=='${文本}'END

从这以后,再也没有出现操作没有成功的问题

第六次主动优化---在log最后按顺序展示全部截图:

功能实现起因:

有时候,需要在log中看下整体测试过程,我们由于每次操作元素都需要截图,所以有把这些图片展示出来,方便定位问题的想法。

实际实现有两种方式:

1. 所有图片合成成一个gif,有点类似“录屏”效果,缺点是播放线性,占用log空间。

2. 把已有的截图在用例最后按顺序展示,有需要就打开看。

最后使用方案2,主要实现功能和注意点:

1. 循环展示当前用例截图。

2. 调用“通用组件 循环展示轮次测试截图”,这里只是调用关键字。

3. “通用组件 循环展示轮次测试截图”底层是更改了“screenshot.py”,增加了关键字“Display Img all”和获取图片目录关键字"get screenshot_root_directory"。

4. 为了让图片按顺序排列,需要给图片第一位秒数补零到4位,防止“7”秒排到"17秒"后面,所以修改“截图”关键字。

5. 修改了“截图”关键字,对应的“通用组件 循环删除n秒前的图片”也要做修改。

通用组件 关闭所有浏览器${is passed}    Run Keyword And Return Status    Run Keyword If Test Failed    这里使用不存在的关键字,如果因为失败而调用这个关键字,会赋值FalseRun Keyword And Ignore Error    Run Keyword If Test Failed    通用组件 尝试获取页面弹窗消息Run Keyword And Ignore Error    截图Run Keyword And Ignore Error    获取 cookieRun Keyword And Ignore Error    获取 urlRun Keyword And Ignore Error    关闭 所有浏览器Comment    ${is pabot}    Run Keyword And Return Status    Should Exist    pabot_resultsRun Keyword And Ignore Error    Run Keyword If Test Failed    Remove Tags    重试通过    重试1次通过    重试2次通过Run Keyword And Ignore Error    Run Keyword If    ${已执行次数}==1 and '${is passed}'=='True'    remove files    pabot_results\\*\\*${TEST_NAME}*.png    *${TEST_NAME}*.pngRun Keyword And Ignore Error    Run Keyword If Test Failed    通用组件 上传本条调试结果到CTPRun Keyword And Ignore Error    Run Keyword If Test Passed    通用组件 上传本条调试结果到CTPRun Keyword And Ignore Error    Run Keyword If Test Failed    通用组件 上传脚本维护记录到CTPRun Keyword And Ignore Error    Run Keyword If Test Passed    通用组件 上传脚本维护记录到CTPRun Keyword And Ignore Error    通用组件 循环删除n秒前的图片Run Keyword If    ${是否新增脚本}    通用组件 新增CTP脚本并关联用例    # ${是否新增脚本}全局变量 0 不上传,1 上传Run Keyword And Ignore Error    Run Keyword If Test Passed    通用组件 循环展示当前用例截图Run Keyword And Ignore Error    Run Keyword If Test Failed    通用组件 循环展示当前用例截图
通用组件 循环展示当前用例截图[Documentation]......    实现功能:...    连续展示所有轮次的截图通用组件 循环展示轮次测试截图    Round1通用组件 循环展示轮次测试截图    Round2通用组件 循环展示轮次测试截图    Round3
通用组件 循环展示轮次测试截图[Arguments]    ${轮次}[Documentation]......    实现功能:...    连续展示单轮次的截图${图片根目录}    Get Screenshot Root Directory${文件名列表}    List Files In Directory    ${图片根目录}    *${TEST_NAME}_${轮次}.png${带文件路径的文件名列表}    Create ListFOR    ${文件名}    IN    @{文件名列表}log    ${图片根目录}\\${文件名}Append To List    ${带文件路径的文件名列表}    ${图片根目录}\\${文件名}ENDDisplay Img All    ${文件名列表}
@keyword('Display Img all')
def display_image_all(self, filename):"""在log中显示图片"""for file in filename:# 获取文件保存路径file = self._get_screenshot_path(file)self._create_directory(file)# 打印图片到logself._embed_to_log_as_file(file, '800')@keyword('get screenshot_root_directory')
def get_screenshot_root_directory(self):if self._screenshot_root_directory != EMBED:directory = self._screenshot_root_directory or self.log_direlse:directory = self.log_dirreturn directory
截图${now}    Get Current Date${sectime}    Subtract date From date    ${now}    ${now-time}${sectime}    Evaluate    int(${sectime})log    ${sectime}Set Test Variable    ${sectime}${sectime补零}    Evaluate    (str('${sectime}')).zfill(4)Capture Page Screenshot    ${sectime补零}_{index}_${TEST_NAME}_Round${已执行次数}.png
通用组件 循环删除n秒前的图片[Arguments]    ${需要保留图片的秒数}=60${delete sec}    Evaluate    ${sectime}-${需要保留图片的秒数}FOR    ${要删除的秒数}    IN RANGE    0    ${delete sec}${要删除的秒数补零}    Evaluate    (str('${要删除的秒数}')).zfill(4)remove files    pabot_results\\*\\${要删除的秒数补零}_*_${TEST_NAME}_Round${已执行次数}.png    ${要删除的秒数补零}_*_${TEST_NAME}_Round${已执行次数}.pngEND

第七次主动优化---Selenium不好处理的页面元素:

无论是echart的canvas,还是富文本编辑器(如果是frame的还好些),还是一些外部控件,或者我们最常见到的上传文件,下载文件,都可以通过一种方式解决:

AutoItLibrary+ImageHorizonLibrary(sikuli也可以)

缺点就是如果用了这个,就要考虑单机上不能并发了:)

如果在windows窗体内容能被识别的情况下(比如上传下载),优先使用AutoIt进行操作,基本操作只有两种:点击(鼠标)和发送(键盘):

通用组件 点击[Arguments]    ${目标定位}    ${目标窗口标题}=    ${点击次数}=1Control Click    ${目标窗口标题}    \    ${目标定位}    \    ${点击次数}
通用组件 发送[Arguments]    ${发送内容}    ${是否原文本输出}=1    #${是否原文本输出}为0时,可以使用特殊转义字符Send    ${发送内容}    ${是否原文本输出}

这个发送,是可以发送非字符的,还可以做循环等操作,具体方法如下:

附录—send使用指南:

“Send” 命令的语法跟 ScriptIt 以及 Visual Basic 的 “SendKeys” 命令类似。字符序列将按原文发送,但下列字符除外:'!'表示告知 AutoIt 要发送一个 ALT 键击动作,因此语句 Send("This is text!a") 的意思是按序发送按键 "This is text" 然后在按下"ALT+a"。注意,有些程序对大小写字符和 ALT 键相当挑剔,举例来说,"!A" 可能会被认为不同于 "!a";第一个代表 ALT+SHIFT+A,而第二个则代表 ALT+a。如果拿不准的话最好使用小写!'+'表示告知 AutoIt 要发送一个 SHIFT 键击动作,因此语句 Send("Hell+o") 的意思是按序发送按键 "HellO"。Send("!+a") 表示发送 "ALT+SHIFT+a"。'^'表示告知 AutoIt 要发送一个 CONTROL 键击动作,因此语句 Send("^!a") 的意思是发送按键 "CTRL+ALT+a"。注意,有些程序对大小写字符和 CTRL 键相当挑剔,举例来说,"^A" 可能会被认为不同于 "^a";第一个代表 CTRL+SHIFT+A,而第二个则代表 CTRL+a. 如果拿不准的话最好使用小写!'#'井号将发送一个 Windows 徽标键,因此语句 Send("#r") 将发送 Win+r,这将打开“运行”对话框。您可以通过设置 SendCapslockMode 从而在 Send 函数开始操作前关闭大小写切换键(大写锁,CAPS LOCK)并在完成操作后恢复。但是,如果在 Send 函数开始执行的时候用户就按住 Shift 键,那么发送的文本可能会是小写字符。一个解决办法是在每次执行其它 Send 操作前使用语句 Send("{SHIFTDOWN}{SHIFTUP}")。某些特殊按键必须用花括号括住才能发送:注意,Windows 不允许模拟 "CTRL-ALT-DEL" 组合键!Send 命令(无标志参数) 键击结果{!} !{#} #{+} +{^} ^{{} {{}} }{SPACE} 空格{ENTER} 主键盘区的 回车键{ALT} ALT{BACKSPACE} 或 {BS} 退格{DELETE} 或 {DEL} 删除(DELETE){UP} 向上箭头{DOWN} 向下箭头{LEFT} 向左箭头{RIGHT} 向右箭头{HOME} HOME{END} END{ESCAPE} 或 {ESC} ESC键{INSERT} 或 {INS} INS(Insert){PGUP} PageUp{PGDN} PageDown{F1} - {F12} 功能键{TAB} TAB{PRINTSCREEN} Print Screen key{LWIN} 左徽标键{RWIN} 右徽标键{NUMLOCK on} NUMLOCK (on/off/toggle)(开/关/切换){CAPSLOCK off} CAPSLOCK (on/off/toggle)(开/关/切换){SCROLLLOCK toggle} SCROLLLOCK (on/off/toggle)(开/关/切换){CTRLBREAK} Ctrl+Break{PAUSE} PAUSE{NUMPAD0} - {NUMPAD9} 数字键盘上的 数字键{NUMPADMULT} 数字键盘上的 乘号{NUMPADADD} 数字键盘上的 加号{NUMPADSUB} 数字键盘上的 减号{NUMPADDIV} 数字键盘上的 除号{NUMPADDOT} 数字键盘上的 点号{NUMPADENTER} 数字键盘上的 回车键{APPSKEY} Windows 应用程序键{LALT} 左 ALT 键{RALT} 右 ALT 键{LCTRL} 左 CTRL 键{RCTRL} 右 CTRL 键{LSHIFT} 左 Shift 键{RSHIFT} 右 Shift 键{SLEEP} 系统休眠(SLEEP)键{ALTDOWN} 按住 ALT 键直到发送 {ALTUP} 为止{SHIFTDOWN} 按住 SHIFT 键直到发送 {SHIFTUP} 为止{CTRLDOWN} 按住 CTRL 键直到发送 {CTRLUP} 为止{LWINDOWN} 按住左徽标键直到发送 {LWINUP} 为止{RWINDOWN} 按住右徽标键直到发送 {RWINUP} 为止{ASC nnnn} 发送 ALT+nnnn 组合键{BROWSER_BACK} 仅支持2000/XP:按下浏览器中的“后退”按钮{BROWSER_FORWARD} 仅支持2000/XP:按下浏览器中的“前进”按钮{BROWSER_REFRESH} 仅支持2000/XP:按下浏览器中的“刷新”按钮{BROWSER_STOP} 仅支持2000/XP:按下浏览器中的“停止”按钮{BROWSER_SEARCH} 仅支持2000/XP:按下浏览器中的“搜索”按钮{BROWSER_FAVORITES} 仅支持2000/XP:按下浏览器中的“收藏夹”按钮{BROWSER_HOME} 仅支持2000/XP:运行浏览器并转到主页{VOLUME_MUTE} 仅支持2000/XP:切换系统静音状态{VOLUME_DOWN} 仅支持2000/XP:减小系统音量{VOLUME_UP} 仅支持2000/XP:增大系统音量{MEDIA_NEXT} 仅支持2000/XP:在播放器中选择播放下一个轨道(影音媒体){MEDIA_PREV} 仅支持2000/XP:在播放器中选择播放上一个轨道{MEDIA_STOP} 仅支持2000/XP:使播放器停止播放{MEDIA_PLAY_PAUSE} 仅支持2000/XP:使播放器播放/暂停{LAUNCH_MAIL} 仅支持2000/XP:运行邮件客户端程序{LAUNCH_MEDIA} 仅支持2000/XP:运行播放器(Media player){LAUNCH_APP1} 仅支持2000/XP:运行用户程序1(我的电脑){LAUNCH_APP2} 仅支持2000/XP:运行用户程序2(计算器)如果要发送 ASCII 字符 A 则参考下例(相当于 ALT+065,按住ALT键并在数字键盘上顺序按下065)Send("{ASC 065}")(在使用两位数的 ASCII 码时必须在前面加一个 0,否则将使用 437号代码页)。如果要发送 UNICODE 字符则输入该字符代码,例如下例将发送一个中文字符Send("{ASC 2709}")可参考下例重复发送某按键:Send("{DEL 4}") ;连续4次按下 DEL 键Send("{S 30}") ;发送30个字符“S”Send("+{TAB 4}) ;连续4次按下 SHIFT+TAB如果要按住(保持按下状态)某个按键(通常用于游戏中)Send("{a down}") ;按住按键 ASend("{a up}") ;松开按键 A如果要改变 capslock、numlock 和 scrolllock 键的状态,可参考下例:Send("{NumLock on}") ;打开 NumLockSend("{CapsLock off}") ;关闭 CapsLockSend("{ScrollLock toggle}") ;切换 ScrollLock 的状态如果要用变量来指定重复发送的次数,参考下例:$n = 4Send("+{TAB " & $n & "}")如果要用变量来指定要重复发送的 ASCII 字符(比如 A),参考下例:$x = Chr(65)Send("{" & $x & " 4}")大多数笔记本电脑的键盘上都会有一个特殊的 Fn 键,此键无法被模拟。注意,若把标志参数的值设为1则“按键”参数将被原样发送。如果某些文本是从变量里拷贝而来,而您又希望完全按原样发送这些文本的话,就应该使用这一设置。例如,先打开 文件夹选项窗口(位于控制面板),然后请尝试执行下面这些语句:Send("{TAB}") 切换到(焦点切换)下一个控件(按钮、复选框等)Send("+{TAB}") 切换到上一个控件Send("^{TAB}") 切换到下一个窗口标签Send("^+{TAB}") 切换到上一个窗口标签Send("{SPACE}") 可用来切换复选框的选中状态或点击某个按钮Send("{+}") 通常用来选中某个复选框(如果它“确实是”复选框的话)Send("{-}") 通常用来取消选中某个复选框Send("{NumPadMult}") 完全展开 SysTreeView32 控件内显示的文件夹组合 Alt 键使用可访问菜单项,请打开记事本窗口然后尝试执行下面这些语句:Send("!f") 表示发送 Alt+f,这是打开记事本的文件菜单的快捷键,您还可以试试其它的!Send("{DOWN}") 移动焦点到下一个菜单项Send("{UP}") 移动焦点到上一个菜单项Send("{LEFT}") 切换到左边的菜单或收缩子菜单Send("{RIGHT}") 切换到右边的菜单或展开子菜单如果您对快捷键(Alt+F4、PrintScreen、Ctrl+C等等)的重要性还不太了解,请查看 Windows 的帮助信息(按下热键 Win+F1即可)以获得关于快捷键的完整列表。相关SendAttachmode(选项), SendKeyDelay(选项), SendKeyDownDelay(选项), ControlSend示例Send("#r")WinWaitActive("运行")Send("notepad.exe{Enter}")WinWaitActive("无标题 - ")Send("现在的时间/日期是 {F5}") ;Send函数无法发送中文字符,这里仅为方便理解

ImageHorizonLibrary这个不用详细说,主要就是把Selenium的元素定位改成了图片比对,比如你截一个“确定按钮”的图,然后ImageHorizonLibrary会获取这个图片在屏幕上的坐标,然后进行点击操作,基本就够用了。

但是,即使是截图,也有可能有多个符合的对象,比如这个页面上有两个“确定按钮”,原生的库并没有提供方法去点击第二个按钮,需要我们做二次开发:

需求:由于ImageHorizonLibrary只支持获取第一个符合图片的坐标,所以需要修改对应的代码,实现定位所有符合图片坐标的支持。

思路:ag.locateAllOnScreen可以获取所有符合图片生成器,我们需要转成列表,结果是[Box(left=352, top=1044, width=29, height=29)]这样的数据结构,然后使用"结果[0][0]"来获取left的值“352”

目标文件地址:\Lib\site-packages\ImageHorizonLibrary\recognition\_recognize_images.py

def _locate_all(self, reference_image, log_it=True):is_dir = Falsetry:if isdir(self.__normalize(reference_image)):is_dir = Trueexcept InvalidImageException:passis_file = Falsetry:if isfile(self.__normalize(reference_image)):is_file = Trueexcept InvalidImageException:passreference_image = self.__normalize(reference_image)reference_images = []if is_file:reference_images = [reference_image]elif is_dir:for f in listdir(self.__normalize(reference_image)):if not isfile(self.__normalize(path_join(reference_image, f))):raise InvalidImageException(self.__normalize(reference_image))reference_images.append(path_join(reference_image, f))def try_locate_all(ref_image):location = Nonelocation_list = []with self._suppress_keyword_on_failure():try:if self.has_cv and self.confidence:location = ag.locateOnScreen(ref_image,confidence=self.confidence)else:if self.confidence:LOGGER.warn("Can't set confidence because you don't ""have OpenCV (python-opencv) installed ""or a confidence level was not given.")location = list(ag.locateAllOnScreen(ref_image))# for ne in location:#     print(ne)#     location_list.append(ne)#     print(location_list)except ImageNotFoundException as ex:LOGGER.info(ex)passreturn locationlocation = Nonefor ref_image in reference_images:location = try_locate_all(ref_image)if location != None:breakif location is None:if log_it:LOGGER.info('Image "%s" was not found ''on screen.' % reference_image)self._run_on_failure()raise ImageNotFoundException(reference_image)if log_it:LOGGER.info('Image "%s" found at %r' % (reference_image, location))# center_point = ag.center(location)# x = center_point.x# y = center_point.y# if self.has_retina:#     x = x / 2#     y = y / 2print(location)return location

当然,如果要做的特别好,还需要有其它的一些改造,在这里我就不展开了,有需要可以看我关于C/S自动化的博客,牵扯到OCR,中文支持等等。

---未完待续,更新频率基本每月更新

Robot Framework Selenium UI自动化测试 --- 实战篇相关推荐

  1. Robot Framework Selenium UI自动化测试 --- 进阶篇

    回顾:          如果您对Robot Framework Selenium(以下简称RFS)没有基础概念和使用经验,请先阅读入门篇,入门篇对RFS有基础的介绍和使用教程. 展望:       ...

  2. python3.7界面设计_基于selenium+Python3.7+yaml+Robot Framework的UI自动化测试框架

    前端自动化测试框架 项目说明 本框架是一套基于selenium+Python3.7+yaml+Robot Framework而设计的数据驱动UI自动化测试框架,Robot Framework 作为执行 ...

  3. Selenium UI自动化测试(三)IDE—百度个人中心录制实例

    Selenium UI自动化测试(三)百度个人中心查看评分实例详解 录制脚本 菜单点击+添加新的测试用例,弹出测试网址输入百度网址,输入完后点击开始录制 点击右边REC开始录制,进入首页右上角用户名, ...

  4. Python+uiautomator2手机UI自动化测试实战

    Python+uiautomator2手机UI自动化测试实战 -- 2.(原文地址):https://blog.csdn.net/ricky_yangrui/article/details/81415 ...

  5. 〖Python接口自动化测试实战篇⑩〗- 测试框架 unittest 的小实战案例

    说明:该文属于 Python全栈白宝书专栏,免费阶段订阅数量4300+,购买任意白宝书体系化专栏可加入TFS-CLUB 私域社区. 福利:除了通过订阅"白宝书系列专栏"加入社区获取 ...

  6. docker+robot framework+selenium并发web应用UI自动化测试实践

    自己在日常测试中,会搭建UI自动化测试框架来进行web应用的回归测试,在这过程中遇到了许多问题,如测试脚本和执行机不分离,串行测试效率低下,环境搭建麻烦等问题.在这个过程中,自己也在网上看一些前辈的搭 ...

  7. Robot Framework + Selenium library + IEDriver环境搭建

    转载:https://www.cnblogs.com/Ming8006/p/4998492.html#c.d 目录: 1 安装文件准备 2 Robot框架结构 3 环境搭建   3.1 安装Pytho ...

  8. Robot Framework + Selenium 框架,关键字封装,知识点记录

    目录 框架概述: 通用关键字: 等待类关键字: 通用组件 等待 元素: 通用组件 获取 元素居中滚动像素: 通用组件 页面滚动 : 通用组件 等待包含文字: 通用组件 自动切换frame : 通用组件 ...

  9. Python Selenium UI自动化测试

    1.自动化测试基础 1.1 自动化测试的定义 将人为的测试行为转化为机器自动执行的过程 1.2 自动化测试的目的 减少成本,提高测试效率 减少人为因素对测试的影响 1.3 什么项目适合做自动化测试 项 ...

  10. 〖Python WEB 自动化测试实战篇⑧〗- 实战 - 利用 selenium 处理弹出框

    订阅 Python全栈白宝书-零基础入门篇 可报销!白嫖入口-请点击我.推荐他人订阅,可获取扣除平台费用后的35%收益,文末名片加V! 说明:该文属于 Python全栈白宝书专栏,免费阶段订阅数量43 ...

最新文章

  1. datacamp自然语言处理免费教程
  2. C#里面Console.Write与Console.WriteLine有什么区别????
  3. set和multiset容器
  4. UA PHYS515A 电磁理论V 电磁波与辐射8 单个粒子的辐射 匀速运动与匀加速运动的情况
  5. 简述get 和 post 的主要区别——计算机网络
  6. Asp.Net前台调用后台变量
  7. python装饰器class_PYTHON里的装饰器能装饰类吗
  8. 2299元 OPPO K3 8GB+256GB版本线上线下同步开售
  9. 员工邮箱能收不能发的解决方法
  10. [转载]Linux shell中的竖线(|)——管道符号
  11. 如何对散列查找进行asl分析计算?_Python数据结构与算法——散列(Hash)
  12. 大型网站技术架构:核心原理与案例分析——大型网站架构演化
  13. c51单片机矩阵键盘1602计算器_单片机做简易计算器源码(矩阵键盘输入+1602显示)...
  14. Go官方依赖包管理工具dep的安装及使用
  15. Beginning Lua with World of Warcraft Add-ons第三章翻译总结及一些工具
  16. 智能注塑工艺与模流分析技术研讨会暨上海大学Moldex3D实训基地开幕式圆满结束
  17. SV学习(8)——随机约束和分布、约束块控制
  18. ajax的get json数据格式,jQuery / 用getJSON()方法加载JSON格式数据 - 汇智网
  19. sqoop import 数据同步到hive的用法
  20. 一个典型的语音识别系统

热门文章

  1. RIdeogram 染色体图谱可视化R包
  2. sessionbean+entitybean 在 jbx+wl7中调试笔记。
  3. postgresql 手动启动_PostGreSql 手动安装
  4. 2018医学考博英语阅读理解解题技巧
  5. 2021最新版上传透明头像易语言源码
  6. worldPress数据库
  7. svchost.exe 大量占用的问题
  8. 对PX4参数THR_MDL_FAC的理解
  9. L1-8 雀魂majsoul (20 分)
  10. r5 3500u和r5 4500u的区别