前端测试 基于 python 的简单视觉测试回归

duckness · 2018年03月05日 · 最后由 hongwingkai-github 回复于 2019年04月12日 · 4646 次阅读

一开始接触到视觉测试的时候还是去京东参加沙龙的时候,第一次接触到了这个概念。可能是命运的捉弄吧,去完回来第二天,我们公司就上马了一个商城的项目(这个商城的项目做得我痛不欲生😭 😭 )一测就是快 4 个月,然后各种基于商城的业务如雨后春笋般层出不穷,导致我需要去看太多太多的图片,经常会漏掉一些很小的但又很重要的地方,外加开发更改细微布局也不会提前告知我们,导致第一遍看过去有错,第二遍的时候这个错找不到了。所以在开发人员的一再逼迫下,我决定去试试视觉测试解决下看看,结果收效显著,现在我只用去 report 就可以了,不过现在还是有些弊端的(我会在结尾处说明)。好的我们就不多介绍了,我们先上截图。
首先,我们来主要讲下思路
思路很简单的,截图的工具我 PC 端用的是 selenium,移动端用的是 appium。
根据这个流程,大概的操作步骤是这个样的在,第一步使用 selenium 或者 appium 进行截图,将第一次的截图定位母图,存放在 bitmaps_reference 目录下,然后更改下存放图片的路径,再次运行截图,本次截取的是测试的图片,我们将图片存放在 bitmaps_test 目录下,然后触发运行图像对比模块,进行图像比较,并判断两张图片是否不同,如果不同的话生成两张图片不同的位置的图片,存放在 diff_image 目录下,然后将所有数据进行收集,收集后生成一份 html_report 报告,这个报告会存放在 html_report 目录下。到此结束了,我们只用去看报告就知道,母图与测试图片哪里不一致(显示不同图片的功能还需要改善,正在改善中.....),在报告中添加了一些筛选,搜索,和拉杆功能,方便去观察两张图片的不一致性。下面是一些操作的截图。
第一张目录存放位置

第二张测试图存放位置

第三张对比图存放位置

第四张报告存放位置

报告样式图

报告功能图(1)

功能图(2)

功能图(3)

至此截图部分就展示完毕了。
下面我们上一些代码,看看功能实现(小白一个,代码较丑,大神勿喷,妈咪妈咪哄)

上面的代码就是一个简单的比较,同 pillow 包里面的 difference 方法进行图片的比较,然后生成一张对比图片,如下图所示:

我们可以看到黑黢黢的一片,一点不好看,而且受到多像素的限制,这个方法在对比底色跨度的时候经常会出现,一片马赛克,如下图:

不知道的话还以为是空白的,但是结果还是好的,是比较出了不同,但是不能满足我们追求完美的决心,所以我暂时在报告中加上了滑竿进行一下细致的对比。
当然我也在改进那个黑黢黢的图片尽量做到,在原图上标注出不同图片的位置,在这里必须说明一下,简单的同底色图片已经可以做到我说的这些(我会在下面进行简单的展示,基于还没有彻底完善代码,在一些值的计算上还存在一些误差和问题,我就不把全部代码放出来丢人现眼了),我先讲下思路,其实就是通过对生成的对比图片进行二值转换,将全部的 x 轴和 y 轴的每个坐标上的值进行输出,输出之后进行重组在一个坐标系内,在这个重组坐标系内会出现一些区域为(1,1)的坐标,而这些坐标就是我们图像上的不同部分,通过这个方法找到全部的不同位置,比产出全部的矩形区域坐标,当然我们会发现在产生的矩形区域坐标中存在一些无用区域坐标,我们在这里用像素管道对比的方法将这些无用区域坐标删除,删除后就剩下我们可用的坐标了,在进行标注,就可以实现我想要的效果了。
代码如下:


改进后的截图和之前的图片对比

这样就基本上满足了我们的需要,可以看到不同而且不是黑黢黢的了,下面是新产生的报告截图

这样看上去是不是清爽多了。
讲了这么多,我的初衷就是为了减少人工点点点对的操作,所以在我们完成这些操作之后,必须去想想,这个方法的一些不足核问题:
问题一:对一些特定区域将无法进行区分,比如说广告。举个例子,我在一开始测试自己写的代码时候,用淘宝进行了测试,发现每次截取的广告图片都不一样,导致代码进行识别后全部判定为失败,只能通过人工去进行识别。所以针对与这个的改进思路有两个,第一个是通过 selenium 或 appium 将广告所在位置进行隐藏在截图,第二种就是通过 CSS 进行对比,在脚本中隐藏广告部分。
问题二:一些人为改动,举个例子:一开始我们存在母图中的图片的 title 字体颜色为黑色,但是由于客户喜好,将 title 颜色改为了红色。当遇到这种情况的时候,识别系统也会识别到那里的不同,从而导致判定失败。没有办法只能又去人工识别判断下。解决办法也有:通过跟开发沟通产生一份白名单,在判断的时候去识别下该图片是不是在白名单中,从而忽略这种情况造成的影响。
问题三:如果一个页面不分页,显示会很长很长(手机中常见),用 selenium 截图时,他只能截取到一部分图片不能去全部截取,就会导致对比不全,从而可能漏掉一些 bug。改进思路:第一种是通过拼接将全部图片拼起来再去识别;方法二:是通过 request 把整个网页都扣下来,在用 selenium 去截取,这样就可以截取一张整图;第三种方法:通过 CSS 将图片拉扯下来,截取全部进行比较。
问题 4:一些功能原件,导致截图对比失败,举个例子,我们在测试手机端的时候,进场会遇到一个滚动条,这个滚动条往下滑动后,每次停留在的位置都不一样,导致是识别的时候,就将原本没有问题的图片判定为失败了。改进思路:方法一:用 CSS 隐藏滚动条在截图;方法二:通过 selenium 操作 js 来实现不显示滚动条,在截图。
我现在遇到的就是这些问题啦。希望大家遇到问题后可以给我反馈,我也及时去修改和改进,在我将那些遗留的错误值和 bug 彻底修改后,我会开源这可东西的。
(哎呀说了这么多)顺便把下一步说一下吧,我计划在 appium 的测试上将思寒老师(我是思寒老师的学生哦,顺便帮他宣传下)@seveniruby (思寒) 的那个 appium 自动遍历的嫁接上,直接生成截图直接去对比,从而生成报告,就可以省去一些看截图的痛苦。在 PC 端去嫁接 debugtalk (九毫)@debugtalk (九毫) 大神的一个遍历系统,从而省去写脚本一些不便。
最后的最后给大家推荐一些 css 回归的已经算比较成熟的框架,webdrivecss,PhantomCSS,Gemini。当然特别推荐一位谷歌大神写的对比图片 dpxdt--这个可是 python 做的哦(只不过用的是 2.7 写的,我用 3.6 所以就用不了😂 😭 😂

共收到 20 条回复 时间 点赞

赞,写的很详细,收起来慢慢看

2楼 已删除

不错的实践,建议开放出去,另外,这种对比,需要图片大小保持一致吧,否则会抛错,感觉底层用的是 imageMagic 的 compare。

收到了,思寒老师。

恒温 回复

对的暂时图片大小需要一致,但是如果将 CSS 加进来,就可以不用考虑图片尺寸了(就可以肆意妄为啦,哈哈)

匿名 #15 · 2018年03月05日

可以考虑加上这段
图片大小一致,如果两张照片一样,返回的 differ 为 0 ,differ 越大,两张图片差异越大

def compare(pic1,pic2):
    '''
    :param pic1: 图片1路径
    :param pic2: 图片2路径
    :return: 返回对比的结果
    '''
    image1 = Image.open(pic1)
    image2 = Image.open(pic2)

    histogram1 = image1.histogram()
    histogram2 = image2.histogram()

    differ = math.sqrt(reduce(operator.add, list(map(lambda a,b: (a-b)**2,histogram1, histogram2)))/len(histogram1))

    print differ
    return differ

compare(r'D:\Ptest\Testcase\11.jpg',r'D:\Ptest\Testcase\22.jpg')
14楼 已删除

问下思寒老师那个关联怎么去?第一次发帖,有点小悲剧。

我后面准备接入 CSS 的一些功能进来,就可以避免对比时图片大小不一致的问题,不过你这个也是一种解决思路。

duckness 回复

解决啦。还有几个内部同学的分享也非常不错,我稍后也给他们开放出来。后面我给这些热爱分享的同学发奖励。

谢谢,思寒老师,笔芯

好厉害啊,学院里真是卧虎藏龙。感觉这种图片对比的技术很强大,如果能加入区域对比的条件就更好了

我去催饭 回复

实际再加入 CSS 后是可以进行区域对比的,就是需要去写一些简单 js 脚本隐藏我们不需要对比的地方在进行比较就好了,或者另一种解决思路就是在我们截图的时候就进行图像裁剪。我觉得大致就是这两种方式。如果你有好的方法也可以反馈哦。

duckness 回复

其实我想了解一下,你平时遇到的坑是什么样的,以致于你要用图像对比的方法来做测试,从你列举的图片看来,如果靠人工来判断,工作量会非常大,但是引入自动化的话,平衡容错和执行效率又是个麻烦事,免不了还要人工对 failed 的图片再过一遍。2009 年我在诺基亚外包做测试的时候就是用的类似的这么个工具,很痛苦,是真正的点点点:左边是 pass,右边是 fail。。。。

我去催饭 回复

首先说坑的话,商城里面有大量的图片,文字介绍,和在各种不同地方存在的功能点,我们在测试的时候因为天天看这些图片,导致只能看到我们常用的一些功能,其他的位置介绍语展示啊,简单的翻页啊,还有小的功能会自动忽略,以至于会漏测掉一些功能,所以我才写了这个,这个东西最大的好处就是不用你去判断(除了我在文章末尾写的那几个暂时不能解决的问题以外),你直接可以通过 fail 看到失败的图片,在 diff 里面也会展示出不同位置,并且增加了拉杆对比功能可以更好的查看图片。最后的结果就是我们大致看一遍后,没有特殊问题,就把报告直接发给开发了。极大减少工作量。

duckness 回复

感觉楼上同学引用的 math 库挺不错的,根据 differ 的值来判断要不要 pass,像你在帖子里举的例子我没太看明白,reference 是 9:19,实际是 9:22,就认为 fail 了?实际跑 case 的时候这个时间会那么准确吗?

我去催饭 回复

其实我觉得,问这么多,主要原因还是没有去实践,都是在想,没有太多实际意义,不如你去按照我的思路写一下,其实很简单的。实现功能之后,再来看你提的这些问题。反而会更有用,其次文末我总结了现在这个一些弊端,你也可以看看,有好的实践想法可以交流哦。

duckness 回复

是的,我现在想实践一下。不过需要引用哪些类和方法什么的,还希望能补充一下

#coding=utf-8
import math
from PIL import Image
from functools import reduce
import operator

class ImgCampare():
    def compare(self,pic1, pic2):
        '''
        :param pic1: 图片1路径
        :param pic2: 图片2路径
        :return: 返回对比的结果
        '''
        image1 = Image.open(pic1)
        image2 = Image.open(pic2)

        histogram1 = image1.histogram()
        histogram2 = image2.histogram()

        differ = math.sqrt(reduce(operator.add, list(map(lambda a, b: (a - b) ** 2, histogram1, histogram2))) / len(histogram1))
        print(differ)
        if differ > 0:
            print('图片对比不一致,请检查细节')
        else:
            print('pass')
if __name__ == '__main__':
    case = ImgCampare()
    case.compare(r'D:\test\screen_shot\test\12.png', r'D:\test\screen_shot\test\14.png')


实践了一下,补充一下需要用到的库,我用的是 python3.6 版本。两张图返回的结果是 2.36,实际上区别还是挺大的(平均时长那块我标错了,实际上一样),感觉还是能直接标出不同的地方好一点。

仅楼主可见
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册