liangjielaoshi 2019-06-27
前端测试主要分五大方向测试,而这五大方向也分很多小方向测试,首先简单的介绍每个方向的概念
界面样式测试
功能测试
多浏览器测试
性能测试
质量测试
如上图所示,真正工作中无法全部满足以上条件,所以需要作出权衡,一般来说,只需要满足以下几点,就可以对项目开展自动化测试(1.需求稳定
不会频繁变更。2.多平台运行,组合遍历型,大量的重复任务。3.软件维护周期长,有生命力。4.被测系统开发较为规范,可测试性强。):
自动化测试最大的挑战就是需求的变化,而自动化脚本本身就需要修改、扩展、debug,去适应新的功能,如果投入产出比太低,那么自动化测试也失去了其价值和意义;
折中的做法是选择相对稳定的模块和功能进行自动化测试,变动较大、需求变更较频繁的部分用手工测试;
测试数据、测试用例、自动化脚本的重用性和移植性较强,降低成本,提高效率和价值;
自动化测试的需求稳定性要求、自动化框架的设计、脚本开发与调试均需要时间,这其实也是一个软件开发过程,如果项目周期较短,没有足够的时间去支持这一过程,那自动化测试也就不需要了;
主要出于这几点考虑:被测试系统的架构差异、测试技术和工具的适应性、测试人员的能力能否设计开发出适应差异的自动化测试框架;
对于界面布局,传统的测试都是由人工对比设计图和产品界面。当界面有修改之后,再由人通过肉眼去检查修改(包括正确的和错误的修改),这样即费时而且测试结果又不稳定,因为人工对比测试存在两个巨坑:1.效率低;2.人的不确定性。对于拥有大量复杂界面的Web应用,界面布局的测试的数量巨大,再加上这两个问题,导致这类应用的界面布局测试/回归测试时间很长,成本很高,所以很多基于Agile(敏捷开发)项目基本不可能在迭代周期内高质量的完成其视觉测试。对于每天做一次,那更是不可能完成的任务。
为了解决上面提到的各种问题,视觉感知测试孕育而生。它使用传统的对图片进行二进制比较的办法,结合敏捷迭代开发的理念,产生的一种针对界面布局的自动化测试方法。
视觉感知测试就是对第一个版本的所有界面进行第一次测试。
视觉感知测试包含以下几个主要的测试步骤:
需要注意的是!
通过配对URL,对所有的截图按照相同的URL进行分组。当然有时候会出现新的界面,有时候老的界面会被删除。对于新的界面就需要人工进行首次验证测试 。
对于分组之后的截图进行像素级别的比较并生产差别图。有时候为了降噪,可以只对局部关心的组件进行比较。
最后通过人工审查差别图报告完成测试。
视觉感知测试结果:
预期(expected) | 实际(actual) | 比较结果(diff) |
---|---|---|
我们认为如果一个界面通过第一次的人工验证并发布之后,它就是一个正确的标准界面,并且是包含了人工测试价值的资产。当下一次测试的时候,这部分价值就应该被保留并重用起来,用于减少新的一次测试的时间,从而实现界面的快速回归测试。
视觉回归测试包含以下几个主要的测试步骤:
回归和感知测试流程差不多只是差异值要更小一点,并且只有效果图需要替换内容。
要进行视觉自动测试,有三种方式。
这三种各有明显的优势和不足。 第二种方式强绑定了实现,从而变得可能比较脆弱。 第一种方式离设计太近了,当页面中有可变内容时就会有问题。
第三种方式,无法进行视觉感知测试结果只能进行视觉回归测试和上一版的dom继续比较差异。
我更倾向与第一种截图对比;它的测试基于用户所见而不是用户所见的抽象。当然第三种也是非常好的 page-monitor 有兴趣的朋友可以自行了解。为什么第三种那么好为什么不使用呢?因为上面这个库是基于phantomjs并且它的实现方式过于复杂不适合新手玩玩。
名称 | 地址 |
---|---|
PhantomCSS | https://github.com/HuddleEng/... |
GhostStory | https://github.com/thingsinja... |
Cactus | https://github.com/winston/ca... |
Needle | https://github.com/python-nee... |
CSSCritic | https://github.com/cburgmer/c... |
sikuli | http://www.sikuli.org/ |
Mogo | http://mogotest.com/ |
pixelmatch | https://github.com/mapbox/pix... |
pixel-diff | https://github.com/koola/pixe... |
好了介绍了那么多,怎么选一个合适的Headless Browser呢?
Headless Browser???我是视觉测试要无界面浏览器干嘛?
因为有了像素对比工具我们还需要一个浏览器进行截图和设计图进行像素比较。
名称 | 内核 | 地址 |
---|---|---|
Puppeteer | Webkit | https://github.com/GoogleChro... |
PhantomJS | Webkit | http://phantomjs.org/ |
SlimerJS | Gecko | https://github.com/laurentj/s... |
TrifleJS | IE | https://github.com/sdesalas/t... |
PhantomJS 基于 Webkit 内核,不支持 Flash 的播放;SlimerJS 基于火狐的 Gecko 内核,支持 Flash播放,并且执行过程会有页面展示。
我们这里呢就只讲Webkit内核的,其他的我就不讲了。
PhantomJS
是一个基于webkit的JavaScript API。它使用QtWebKit作为它核心浏览器的功能,使用webkit来编译解释执行JavaScript代码。任何你可以在基于webkit浏览器做的事情,它都能做到。它不仅是个隐形的浏览器,提供了诸如CSS选择器、支持Web标准、DOM操作、JSON、HTML5、Canvas、SVG等,同时也提供了处理文件I/O的操作,从而使你可以向操作系统读写文件等。PhantomJS的用处可谓非常广泛,诸如网络监测、网页截屏、无需浏览器的 Web 测试、页面访问自动化等。
但是 PhantomJS
因为毕竟不是真实的用户浏览器环境,使用起来还是有不少的诟病。之前一直在使用 PhantomJS
,功能虽然够用,不过和在真实的浏览器里面访问的界面来对比差别还是比较大的。
Puppeteer
是Chrome团队开发的一个Node库。它提供了一个高级API来控制无头或完整的Chrome。它通过使用Chrome无界面模式 (Headless Chrome
)和DevTools
协议的组合来实现这一点。它使用一个更上层的API来封装其功能,让用户界面测试自动化变得轻而易举。
人们基于Chrome DevTools协议开发了一系列Google Chrome工具。你在浏览器中点击更多工具 ->开发工具,打开的就是DevTools。DevTools协议是DevTools的动力基础,我们现在可以使用Chrome中的DevTools来做更多的事情。
好了简介讲完了,我们来对比一下这两个Headless Browser的区别。
PhantomJS:
var page = require('webpage').create(); page.viewportSize = { width: 400, height: 400 }; page.open("http://localhost:8899/VS", function(status) { if (status === "success") { page.render("a.jpg"); } else { console.log("Page failed to load."); } phantom.exit(0); });
Puppeteer:
const puppeteer = require('puppeteer'); (async() => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://localhost:8899/VS'); await page.setViewport({ width: 400, height: 400 }) //保存图片 const images = await page.screenshot({ path: 'VS.jpg', fullPage: true, omitBackground: true }); //关闭浏览器 await browser.close(); })();
Puppeteer | PhantomJS | Chrome浏览器 |
---|---|---|
浏览器效果(大图):
好了看到这里已经可以分别出来胜负了。
我们来做一个简单的dome
我们这里拿掘金来做一个视觉感知测试的例子。
1.首先创建项目名字就叫“PerceptionTest”把。
2.安装依赖
安装Puppeteer
npm install puppeteer --save
像素对比工具我就选我最常用的blink-dif了
npm install blink-diff --save
3.依赖安完了我们来创建一个js文件“app.js”
4.加载依赖
const puppeteer = require('puppeteer'),//无头浏览器 BlinkDiff = require('blink-diff'),//像素对比 imgUrl = __dirname + "/blink-diff_img/";//图片目录
5.使用puppeteer进行截图
(async () => { //创建puppeteer const browser = await puppeteer.launch({ headless: true }); //new 一个新的tab页面 const page = await browser.newPage(); //设置浏览器的尺寸 await page.setViewport({ width: 1920, height: 945 }); //打开url await page.goto('https://juejin.im/'); //保存截图 await page.screenshot({ path: imgUrl + 'Screenshots.png', fullPage: true }); //关闭浏览器 await browser.close(); })();
6.分析页面找到要替换的内容
为什么要替换内容呢,因为我们UI测试指的是测试界面样式而不是去匹配里面的内容,如果不替换里面的内容那像素对比工具比较出来的相似度肯定很低。所以我们要替换掉内容,要让内容完整统一,我们才好更加精确的去比较差异。
好了我们来分析一下要替换的内容。
要替换的内容如下:
7.替换内容
puppeteer提供了非常丰富的api,其中有个api叫page.evaluate可以向页面插入一段js。
await page.evaluate(async () => { //列表 var Lists = document.querySelectorAll("div.feed.welcome__feed > ul > li > div > a > div"); Lists.forEach(function (element, index, array) { element.querySelector("a.title").innerHTML = "测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试"; //替换标签 element.querySelector("ul > li.item.category > span").innerHTML = "测试"; //替换作者 element.querySelector("ul > li.item.username.clickable > div > a").innerHTML = "测试"; //替换发布时间 element.querySelector("div.info-row.meta-row > ul > li:nth-child(3)").innerHTML = "9999天前"; //替换发布时间 element.querySelector("div.info-row.meta-row > ul > li:nth-child(4)").innerHTML = "99999999999 次阅读"; //列表图片 if (element.querySelectorAll("div.lazy.thumb.thumb.loaded").length==1) { element.querySelector("div.lazy.thumb.thumb.loaded").style.background = "#fdedc9"; } else { var loaded=document.createElement("div"); loaded.className=" lazy thumb thumb loaded"; loaded.style.background = "#fdedc9"; loaded.setAttribute("data-v-b2db8566",""); loaded.setAttribute("data-v-009ea7bb",""); loaded.setAttribute("data-v-f2ca14b0",""); element.appendChild(loaded); } }); });
8.使用Blink-Diff进行像素对比较
const diff = new BlinkDiff({ imageAPath: imgUrl + 'example.png', // 设计图 imageBPath: imgUrl + 'Screenshots.png',//页面截图 //低于其中差异的像素数/ p(默认值:500) - 百分比阈值:1 = 100%,0.2 = 20% threshold: 0.02, // 1% threshold imageOutputPath: imgUrl + 'Diff.png'//Diff路径 }); diff.run(function (error, result) { if (error) { throw error; } else { console.log(diff.hasPassed(result.code) ? '通过' : '失败'); console.log('总像素:' + result.dimension); console.log('发现:' + result.differences + ' 差异.'); } });
完整代码:
const puppeteer = require('puppeteer'), BlinkDiff = require('blink-diff'), imgUrl = __dirname + "/blink-diff_img/"; (async () => { const browser = await puppeteer.launch({ headless: true }); const page = await browser.newPage(); await page.setViewport({ width: 1920, height: 945 }); await page.goto('https://juejin.im/'); await page.evaluate(async () => { //列表 var Lists = document.querySelectorAll("div.feed.welcome__feed > ul > li > div > a > div"); Lists.forEach(function (element, index, array) { element.querySelector("a.title").innerHTML = "测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试"; //替换标签 element.querySelector("ul > li.item.category > span").innerHTML = "测试"; //替换作者 element.querySelector("ul > li.item.username.clickable > div > a").innerHTML = "测试"; //替换发布时间 element.querySelector("div.info-row.meta-row > ul > li:nth-child(3)").innerHTML = "9999天前"; //替换发布时间 element.querySelector("div.info-row.meta-row > ul > li:nth-child(4)").innerHTML = "99999999999 次阅读"; //列表图片 if (element.querySelectorAll("div.lazy.thumb.thumb.loaded").length==1) { element.querySelector("div.lazy.thumb.thumb.loaded").style.background = "#fdedc9"; } else { var loaded=document.createElement("div"); loaded.className=" lazy thumb thumb loaded"; loaded.style.background = "#fdedc9"; loaded.setAttribute("data-v-b2db8566",""); loaded.setAttribute("data-v-009ea7bb",""); loaded.setAttribute("data-v-f2ca14b0",""); element.appendChild(loaded); } }); }); await page.screenshot({ path: imgUrl + 'Screenshots.png', fullPage: true }); const diff = new BlinkDiff({ imageAPath: imgUrl + 'example.png', // 设计图 imageBPath: imgUrl + 'Screenshots.png',//页面截图 threshold: 0.02, // 1% threshold imageOutputPath: imgUrl + 'Diff.png'//Diff路径 }); diff.run(function (error, result) { if (error) { throw error; } else { console.log(diff.hasPassed(result.code) ? '通过' : '失败'); console.log('总像素:' + result.dimension); console.log('发现:' + result.differences + ' 差异.'); } }); //关闭puppeteer await browser.close(); })();
差异图
有差异 | 无差异 |
---|---|
git完整代码:https://github.com/my07ke/Per...
好了,欲知后事如何,请听下回分解。
点击链接加入群聊【前端|WEB|CSS|Javascript|HTML】:https://jq.qq.com/?_wv=1027&k...
4.启动命令windows:chrome --remote-debugging-port=9222启动命令mac:Google\ Chrome --remote-debugging-port=9222