Aaaaaaaty / blog

趁还能折腾的时候多读书——前端何时是个头
1.78k stars 272 forks source link

基于casperjs、resemble.js实现一个像素对比服务 #21

Open Aaaaaaaty opened 7 years ago

Aaaaaaaty commented 7 years ago

写在最前

本次分享一个提供设计稿与前端页面进行像素对比的node服务,旨在为测试或者前端人员自己完成一个辅助性测试。相信我,在像素级别的对比下,网页对设计稿的还原程度一下子就会凸显出来。。如果哪位童鞋用这个功能测了某位前端小哥哥的页面发现了问题,请不要说是我提供的这个工具=。= 欢迎关注我的博客,不定期更新中——

效果预览

1

前置知识

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

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

整体思路

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

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

这其中有一个问题可能会有人注意到就是:为什么在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

参考文献

nuansediao commented 6 years ago

CasperError: Errors encountered while filling form: form not found??