基于casperjs和resemble.js实现一个像素对比服务详解


Posted in Javascript onJanuary 10, 2018

前言

本次分享一个提供设计稿与前端页面进行像素对比的node服务,旨在为测试或者前端人员自己完成一个辅助性测试。相信我,在像素级别的对比下,网页对设计稿的还原程度一下子就会凸显出来。下面话不多说了,来一起看看详细的介绍吧。

效果预览

基于casperjs和resemble.js实现一个像素对比服务详解

前置知识

本次用到了以下两个库作为辅助工具:

  • casperjs:基于PhantomJS的编写。其内部提供了一个无界面浏览器,简单来说用它你可以以代码的形式来完成模拟人来操作浏览器的操作,其中涉及鼠标各种事件,等等非常多的功能,本次主要使用其附带的截图功能。
  • resemble.js:图片像素对比工具。调用方法简单理解为,传入两张图,返回一张合成图并附带对比参数如差别度等等。基本实现思路可以理解为通过将图片转为canvas后,获取其图像像素点,之后对每个像素点进行一次比对。

所以整个服务我们应该已经有了大题的思路即通过casperjs来进入某个网站截取某个页面,再将其与设计图进行比对得出结果。

整体思路

基于casperjs和resemble.js实现一个像素对比服务详解

通过上图我们应该能整理出一个大概的流程:

  • 从前端页面接收设计稿图片及需要截取的网站地址与节点信息
  • 将设计稿保存到images文件夹
  • 开启子进程,启动casperjs,完成对目标网站的截取
  • 截取后请求form.html将图片地址信息填入并重新传回服务器
  • 服务端获取图片信息通过resemblejs将截取图与设计稿进行比对
  • 结果传回前端页面

这其中有一个问题可能会有人注意到就是:为什么在casperjs中对目标网站截图了不能直接把信息传回服务器中,而是选择了再去打开一个表单页面通过表单的形式来提交信息?

答:首先我对casperjs和node了解都不那么深入,我理解的是首先casperjs不是一个node模块,它是跑在操作系统中的,我尚且没有发现怎么在casperjs中建立与node服务的通信,如果有方法一定要告诉我,因为我真的不太了解casper!其次由于无法建立通信,我只能退而求其次,通过casper快速打开一个我写好的表单页面并且填写好图片信息传回服务器,这么做是可以完成最初的诉求。所以就有了上面from.html那段的操作。

实现细节

实现一个简易静态服务器

因为涉及到index.html与form.html页面的返回,故需要实现一个超级简易的静态服务器。代码如下:

const MIME_TYPE = {
 "css": "text/css",
 "gif": "image/gif",
 "html": "text/html",
 "ico": "image/x-icon",
 "jpeg": "image/jpeg",
 "jpg": "image/jpg",
 "js": "text/javascript",
 "json": "application/json",
 "pdf": "application/pdf",
 "png": "image/png",
 "svg": "image/svg+xml",
 "swf": "application/x-shockwave-flash",
 "tiff": "image/tiff",
 "txt": "text/plain",
 "wav": "audio/x-wav",
 "wma": "audio/x-ms-wma",
 "wmv": "video/x-ms-wmv",
 "xml": "text/xml"
}
function sendFile(filePath, res) {
 fs.open(filePath, 'r+', function(err){ //根据路径打开文件
  if(err){
   send404(res)
  }else{
   let ext = path.extname(filePath)
   ext = ext ? ext.slice(1) : 'unknown'
   let contentType = MIME_TYPE[ext] || "text/plain" //匹配文件类型
   fs.readFile(filePath,function(err,data){
    if(err){
     send500(res)
    }else{
     res.writeHead(200,{'content-type':contentType})
     res.end(data)
    }
   })
  }
 })
}

解析表单并将图片存储到images文件夹

const multiparty = require('multiparty') //解析表单
let form = new multiparty.Form()
 form.parse(req, function (err, fields, files) {
  let filename = files['file'][0].originalFilename,
   targetPath = __dirname + '/images/' + filename,
  if(filename){
   fs.createReadStream(files['file'][0].path).pipe(fs.createWriteStream(targetPath))
   ...
  } 
 })

通过创建可读流读出文件内容,再通过pipe写入到制定路径下即可保存上传来的图片。

运行casperjs

const { spawn } = require('child_process')
spawn('casperjs', ['casper.js', filename, captureUrl, selector, id])
casperjs.stdout.on('data', (data) => {
 ...
})

通过spawn可以创建子进程来启动casperjs,同样也可以使用exec等。

截图并提交数据到form.html

const system = require('system')
const host = 'http://10.2.45.110:3033'
const casper = require('casper').create({
 // 浏览器窗口大小
 viewportSize: {
  width: 1920,
  height: 4080
 }
})
const fileName = decodeURIComponent(system.args[4])
const url = decodeURIComponent(system.args[5])
const selector = decodeURIComponent(system.args[6])
const id = decodeURIComponent(system.args[7])
const time = new Date().getTime()
casper.start(url)
casper.then(function() {
  console.log('正在截图请稍后')
  this.captureSelector('./images/casper'+ id + time +'.png', selector)
})
casper.then(function() {
 casper.start(host + '/form.html', function() {
  this.fill('form#contact-form', {
   'diff': './images/casper'+ id + time +'.png',
   'point': './images/' + fileName,
   'id': id
  }, true)
 })
})
casper.run()

代码还是比较简单的,主要过程就是打开一个页面,然后在then中传入你的操作,最后执行run。在这个过程里我不太知道如何与node服务通信,故选择了再开一个页面。。想深入研究的可以去看casperjs的官网非常详尽!

通过resemble.js进行像素比对并返回数据

function complete(data) {
  let imgName = 'diff'+ new Date().getTime() +'.png',
   imgUrl,
   analysisTime = data.analysisTime,
   misMatchPercentage = data.misMatchPercentage,
   resultUrl = './images/' + imgName
  fs.writeFileSync(resultUrl, data.getBuffer())
  imgObj = {
   ...
  }
  let resEnd = resObj[id] // 找回最开始的res返回给页面数据
  resEnd.writeHead(200, {'Content-type':'application/json'})
  resEnd.end(JSON.stringify(imgObj))
 }
let result = resemble(diff).compareTo(point).ignoreColors().onComplete(complete)

这其中涉及到了一个点,即我现在所得到的结果要返回给最初的请求里,而从一开始的请求到现在我已经中转了多次,导致我现在找不到我最初的返回体res了。想了很久只能暂时采用了设定全局对象,在接收最初的请求后将请求者的ip和时间戳设定为唯一id存为该对象的key,value为当前的res。同时整个中转流程中时刻传递id,最后通过调用resObj[id]来得到一开始的返回体,返回数据。这个方法我不认为是最优解,但是鉴于我现在想不出来好方法为了跑通整个服务不得已。。如果有新的思路请务必告知!!

部署

安装PhantomJS(osx)

官网下载: phantomjs-2.1.1-macosx.zip

解压路径:/User/xxx/phantomjs-2.1.1-macosx

添加环境变量:~/.bash_profile 文件中添加

export PATH="$PATH:/Users/xxx/phantomjs-2.1.1-macosx/bin"

terminal输入:phantomjs --version

能看到版本号即安装成功

安装casperjs

brew update && brew install casperjs

安装resemble.js

cnpm i resemblejs //已写进packjson可不用安装
brew install pkg-config cairo libpng jpeg giflib
cnpm i canvas //node内运行canvas

node服务

git clone https://github.com/Aaaaaaaty/gui-auto-test.git
cd gui-auto-test
cnpm i
cd pxdiff
nodemon server.js

打开http://localhost:3033/index.html

惯例po作者的博客,不定时更新中——

参考文献

  • PhantomJS 安装
  • casperjs 文档
  • resemble.js 文档

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
锋利的jQuery 要点归纳(一) jQuery选择器
Mar 21 Javascript
jQuery基于当前元素进行下一步的遍历
May 20 Javascript
jquery滚动特效集锦
Jun 03 Javascript
Bootstrap中的表单验证插件bootstrapValidator使用方法整理(推荐)
Jun 21 Javascript
jquery插件autocomplete用法示例
Jul 01 Javascript
vue制作加载更多功能的正确打开方式
Oct 12 Javascript
js定时器+简单的动画效果实例
Nov 10 Javascript
vue观察模式浅析
Sep 25 Javascript
简单了解Javscript中兄弟ifream的方法调用
Jun 17 Javascript
Vue路由对象属性 .meta $route.matched详解
Nov 04 Javascript
解决ele ui 表格表头太长问题的实现
Nov 13 Javascript
Vue Render函数原理及代码实例解析
Jul 30 Javascript
JavaScript实现快速排序的方法分析
Jan 10 #Javascript
jQuery第一次运行页面默认触发点击事件的实例
Jan 10 #jQuery
js推箱子小游戏步骤代码解析
Jan 10 #Javascript
vue select二级联动第二级默认选中第一个option值的实例
Jan 10 #Javascript
AngularJS使用ui-route实现多层嵌套路由的示例
Jan 10 #Javascript
Vue+jquery实现表格指定列的文字收缩的示例代码
Jan 09 #jQuery
基于Vue、Vuex、Vue-router实现的购物商城(原生切换动画)效果
Jan 09 #Javascript
You might like
PHP中使用mpdf 导出PDF文件的实现方法
2018/10/22 PHP
另类调用flash无须激活的方法
2006/12/27 Javascript
9个javascript语法高亮插件 推荐
2009/07/18 Javascript
腾讯与新浪的通过IP地址获取当前地理位置(省份)的接口
2010/07/26 Javascript
JavaScript高级程序设计 扩展--关于动态原型
2010/11/09 Javascript
JavaScript字符串插入、删除、替换函数使用示例
2013/07/25 Javascript
javascript单引号和双引号的区别和处理
2014/05/14 Javascript
DOM基础教程之事件对象
2015/01/20 Javascript
javascript变量声明实例分析
2015/04/25 Javascript
js强制把网址设为默认首页
2015/09/29 Javascript
JavaScript通过HTML的class来获取HTML元素的方法总结
2016/05/24 Javascript
html判断当前页面是否在iframe中的实例
2016/11/30 Javascript
javascript实现鼠标点击页面 移动DIV
2016/12/02 Javascript
快速掌握jQuery插件开发
2017/01/19 Javascript
Node.js中看JavaScript的引用
2017/04/22 Javascript
Angular.js自动化测试之protractor详解
2017/07/07 Javascript
react路由配置方式详解
2017/08/07 Javascript
微信小程序request请求后台接口php的实例详解
2017/09/20 Javascript
利用Vue2.x开发实现JSON树的方法
2018/01/04 Javascript
Angular angular-file-upload文件上传的示例代码
2018/08/23 Javascript
使用mixins实现elementUI表单全局验证的解决方法
2019/04/02 Javascript
jquery多级树形下拉菜单的实例代码
2019/07/09 jQuery
node使用mysql获取数据库数据中文乱码问题的解决
2019/12/02 Javascript
Vue+Spring Boot简单用户登录(附Demo)
2020/11/12 Javascript
浅谈终端直接执行py文件,不需要python命令
2017/01/23 Python
Python三种遍历文件目录的方法实例代码
2018/01/19 Python
Python全栈之列表数据类型详解
2019/10/01 Python
pytorch::Dataloader中的迭代器和生成器应用详解
2020/01/03 Python
python多线程实现同时执行两个while循环的操作
2020/05/02 Python
运动会开幕式邀请函
2014/01/22 职场文书
导游词格式
2015/02/13 职场文书
道歉的话怎么说
2015/05/12 职场文书
健康教育主题班会
2015/08/14 职场文书
新郎父母婚礼答谢词
2015/09/29 职场文书
2016大学生毕业实习心得体会
2016/01/23 职场文书
python自然语言处理之字典树知识总结
2021/04/25 Python