转转QA 基于图片比对的 UI 自动化测试在运营系统中的应用

笑哼 for 转转QA · 2018年11月23日 · 122 次阅读

作者 | 刘晓琳

前言

转转运营系统是一个主要为转转平台提供各类促销活动、各种个性玩法的中台能力支撑系统,它每天承载着各种各样促销活动的正常运作;然而最为直接与用户接触的莫过于一个个酷炫的活动页面。
在转转运营系统中,一个个酷炫的活动 M 页是由一个叫魔方的系统不断的由运营人员自主式 “生产” 而出,运营人员可根据活动需求配置出各种不同展示效果的页面。那么问题来了,转转平台每日有如此多的活动,运营人员要在魔方系统配置如此多的活动页面的同时,魔方系统也在不断的迭代添加新的功能,或优化旧的功能,如何保障在活动运作期间,魔方系统能够在不影响用户体验及使用的前提下进行正常功能迭代呢?

思考

基于上面的问题,我们对活动魔方系统及 M 页分以下三个部分测试:
业务逻辑测试
功能回归测试
功能兼容测试
业务功能逻辑测试不必说,新增或修改的功能模块必然要进行重点测试。
主要讲一下业务功能回归测试阶段,这一环节效率最低的测试方案莫过于手工测试:评估所有可能影响的功能模块,将这些功能模块构成的 M 页进行人工回归测试;
测试方式:
页面展示数据正确性
页面展示样式正确性;
测试量:每次魔方系统稍有改动,为保障活动页面的正常运作,将进行接近 20 套页面的测试,换算一下相当于:1 个人不停隙的测试 3 小时;但是每次魔方系统的功能迭代都要进行一次这样工作量的测试哦,而且眼一花,一些细节方面的展示问题就测试不出了,想一想工作量可是不小。而且测试方式也仅仅是 “点点点”。

方案

思考了那么久,到底要怎么做!!!
经过笔者的调研以及与团队激烈的讨论,最终在回归测试阶段产生以下自动化回归测试方案,本方案主旨在于高效率,低操作成本,低维护成本,各种……低成本,从而体现自动化价值所在。下面切入正题:
运作流程
笔者给自己的这套自动化测试方案起了一个洋气的名字 “监控狗”,它主要采用当前主流的 ui 自动化测试框架 selenium+chrome( headless) 进行 M 页快速自动化回归测试。
对于操作人员来讲主要进行以下三个步骤的操作即可完成一套页面的测试:

  • 步骤一:配置被回归测试 url
  • 步骤二:审核基准图片(当 url 判断为新 url 时候需要进行此步骤)
  • 步骤三:查看测试报告(页面存在问题时才会产出) 备注:不需要操作人员手动写脚本 case,是不是很爽!

流程说明

  • 新 url 判断标准:在数据库相应表中不存在当前需要被回归的 url 或存在当前 url 但页面元素总数信息不一致;
  • 页面基准数据获取:获取当前页面首页图片、页面所有可点击元素(根据位置去重)、可点击元素对应的文本信息、可点击元素点击后页面图片、可点击元素位置坐标
  • 页面回归测试:
    1. 从数据库中获取当前 url 所有可点击元素
    2. 依次点击所有可点击元素,同时获取每个元素对应的现场图片
    3. 将每个元素对应的现场图片与此元素的基准图片进行 diff 对比
  • 不同率分析:如果 diff 的不同率大于 10% 将会将此元素的结果加入到测试报告 技术技巧 笔者通过利用 selenium 框架本身的一些特性,或一些规避的方式去解决一些问题,文中的测试网页当然也会使用 mock 数据。以下是在开发过程中遇到的一些突破性的问题及解决方案(linux 和 windows 通用),仅供参考,也许还有其它更好的方法,欢迎交流。
    • 获取元素总数:在新 url 判断阶段通过获取页面元素总数来判断当前页面是否发生了页面结构的变化,一般情况下页面结构的变化会直接导致页面元素个数的变化,此处获取到的元素会进行统一编号存入数据库,只要页面结构不发生变化,这里每个元素的编号顺序是不变的。 driver . findElementsByTagName ( "*" ); //获取页面元素总数
    • 获取页面全网页完整图片: 获取页面全网页完整图片,这里重写了一下 getFullScreenshotAs 方法,可以将完整的网页进行截取,不仅仅是当前屏幕(格式自己整理哈)。
Object  metrics =sendEvaluate( "({"+"width: Math.max(window.innerWidth,document.body.scrollWidth,document.documentElement.scrollWidth)|0,"+                   
"height: Math.max(window.innerHeight,document.body.scrollHeight,document.documentElement.scrollHeight)|0,"+"deviceScaleFactor: window.devicePixelRatio || 1,"+"mobile: typeof window.orientation == 'undefined'"+"})");
    sendCommand("Emulation.setDeviceMetricsOverride",metrics);
    Object result =sendCommand("Page.captureScreenshot",ImmutableMap.of("format","png", "fromSurface", true));
    sendCommand("Emulation.clearDeviceMetricsOverride",ImmutableMap.of());
String base64EncodedPng =(String)((Map<Strin,?>) result).get("data");   return outputType.convertFromBase64Png(base64EncodedPng);
  • 获取页面所有可点击元素:此处借助 selenium 框架本身的特点,如:依次点击所有元素,当存在不可点击元素时会抛出异常,那么不抛出异常的即是可点击的元素,此时将此元素对应的序号存储;当然了,这时还有在异常中有一些漏网之鱼的一些过滤处理也很简单就不再说明。
  • 获取可点击元素页面坐标:此处获取可点击元素坐标主要目的在测试报告中体现元素在网页对应的点击位置。
loc =indexElements.get(elementActualNum).getLocation().toString();
  • 图片 diff:在进行元素回归图片与基准图片对比时,此处借助第三方工具 ImageMagick,此工具提供很方便的 java api 接口,且可将两张图片进行非常精细的对比(自己调整格式哈)。
IMOperation imageOperator =new IMOperation();
imageOperator.addImage(StdFile);
//添加基准图片
 imageOperator.addImage(monitorFile);
//添加监控图片   
String  diffFileInfo =compareTargetPath +diffFileName;
//拼装diff图片路径
imageOperator.addImage(diffFileInfo);
//生成diff图片存放路径
CompareCmd  cmd =new CompareCmd();
//实例化图片比较命令
cmd.setSearchPath("C:\\Program Files\\ImageMagick-7.0.8-Q16");
//设置命令所在目录,Windows需要设置,Linux不需要
cmd.run(imageOperator);
  • 不同率:为了过滤一些没有必要的 diff 点,如图片轮巡下方的图片编号 1、2、3、4 或一些页面上时间的展示或者一些商品价格的变化,还是要进行一些小范围的 diff 点的过滤,笔者采用以下方式设置 diff 百分比(格式自行调整哦)。
int[]  rgb =new int[3];   
DecimalFormat  df =new  DecimalFormat("0.00");   
float  pxRed =0f;   
List  <Integer>  write =newArrayList<Integer>();//白色    
List<Integer> red =newArrayList<Integer>();//灰色
File  file =new  File(diffPath);
BufferedImage  bi =null;
  if (null  !=file)
{       
try
{ bi = ImageIO.read(file);       }
catch
 (
IOException  e
)
{
            e.printStackTrace();       
}        
int  width = bi.getWidth();       
int  height =bi.getHeight();       
int minx =bi.getMinX();        
int miny = bi.getMinY();      
for
(
int  i =minx;
 i <width;
 i++) 
{          
for
(
int  j =miny;
 j <height;
 j++)
{                
int  pixel =bi.getRGB
(
i,j
);
  rgb[0]=(pixel &0xff0000)>>16;
  if(rgb[0]>=204 && rgb[0] < 255) {
                    write.add(pixel);              
}else{
            red.add(pixel);          
}               
if  (i ==111&& j ==55)
{                   
System.out.println(rgb[0]);                
}}}      
int total =write.size()+ red.size();       
float  pxWrite = (float) write.size() / total;
pxRed = (float) red.size() /total;    
} 
else 
{        
System.out.println
(
"info*************************未读取到图片*************************"
);

}
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册