Merge pull request #5 from winsonwq/image_generator

implementation of image_generator. Qiu
This commit is contained in:
Wang Qiu
2013-08-01 01:16:32 -07:00
13 changed files with 356 additions and 43 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
chromedriver.log
report.html
.DS_Store
/node_modules/
/lib/

View File

@@ -28,10 +28,12 @@ module.exports = function(grunt) {
'lib/reporter.js': 'src/reporter.coffee',
'lib/html.report.template.js': 'src/html.report.template.coffee',
'lib/process.argv.js': 'src/process.argv.coffee',
'lib/image.generator.js': 'src/image.generator.coffee',
'test/build/comparison_test.js': 'test/src/comparison_test.coffee',
'test/build/viff_test.js': 'test/src/viff_test.coffee',
'test/build/reporter_test.js': 'test/src/reporter_test.coffee',
'test/build/process_argv_test.js': 'test/src/process_argv_test.coffee'
'test/build/process_argv_test.js': 'test/src/process_argv_test.coffee',
'test/build/image_generator_test.js': 'test/src/image_generator_test.coffee'
}
}
},

View File

@@ -2,11 +2,11 @@
module.exports = {
seleniumHost: 'http://localhost:4444/wd/hub',
browsers: ['firefox'],
browsers: ['firefox', 'safari', 'chrome', 'opera'],
envHosts: {
build: 'http://localhost:4000',
prod: 'http://ishouldbeageek.me'
},
paths: require('./links.js'),
reportFormat: 'html'
reportFormat: 'file'
};

View File

@@ -4,9 +4,10 @@ module.exports = (function initLinks() {
return [
'/404.html',
['/', function (driver, webdriver) {
driver.findElement(webdriver.By.partialLinkText('Drupal初体验')).click();
}]
'/'
// ['/', function (driver, webdriver) {
// driver.findElement(webdriver.By.partialLinkText('Drupal初体验')).click();
// }]
];
})();

View File

@@ -29,7 +29,9 @@
"underscore": "*",
"selenium-webdriver": ">=2.33.0",
"handlebars": "~1.0.12",
"resemble": "~1.0.3"
"resemble": "~1.0.3",
"wrench": "*",
"colors": "*"
},
"devDependencies": {
"grunt": "~0.4.1",

View File

@@ -1,26 +1,30 @@
path = require 'path'
_ = require 'underscore'
mr = require 'Mr.Async'
spawn = require('child_process').spawn
fs = require('fs')
resemble = require('resemble').resemble
class Comparison
constructor: (imgWithEnvs) ->
_.extend(@, imgWithEnvs)
@images = imgWithEnvs
diff: (callback) ->
defer = mr.Deferred()
defer.done callback
that = @
fileData = _.map _.values(@), (base64Img) ->
fileData = _.map _.values(@images), (base64Img) ->
new Buffer base64Img, 'base64'
Comparison.compare fileData[0], fileData[1], (diffObj) ->
if diffObj
that.DIFF = diffObj.getImageDataUrl().replace('data:image/png;base64,', '')
defer.resolve that.DIFF
that.images.diff = diffObj.getImageDataUrl().replace('data:image/png;base64,', '')
_.extend that,
isSameDimensions: diffObj.isSameDimensions
misMatchPercentage: Number diffObj.misMatchPercentage
analysisTime: diffObj.analysisTime
defer.resolve that.images.diff
defer.promise()

View File

@@ -10,7 +10,8 @@ template = """
body{font: normal normal 14px/1.5 'Open Sans', sans-serif; }
body,ul,h1,h2,h3,h4{margin:0;padding:0;font-weight:300;font-style:normal;}
body{margin: 20px;}
h2{font-size: 3em;}
h1{font-size: 3em;}
h2{font-size: 2.4em;}
ul{overflow: hidden;}
li{list-style-type: none; margin-bottom: 20px; overflow: hidden;}
a {float: left;height: 100px;width: 100px; overflow: hidden; margin-right:1%; position: relative;}
@@ -37,13 +38,14 @@ template = """
</style>
</head>
<body>
<h1>Viff Report - ({{sameCount}} same in {{caseCount}} cases) {{totalAnalysisTime}}ms</h1>
{{#each compares}}
<h2>{{@key}}</h2>
<ul>
{{#each this}}
<li>
<h3>{{@key}}</h3>
{{#each this}}
<h3>{{@key}} - {{this.misMatchPercentage}}% mismatch {{this.analysisTime}}ms</h3>
{{#each this.images}}
<a href="data:image/png;base64,{{this}}" data-env="{{@key}}">
<img src="data:image/png;base64,{{this}}"/>
</a>

View File

@@ -0,0 +1,64 @@
path = require 'path'
_ = require 'underscore'
mr = require 'Mr.Async'
fs = require 'fs'
wrench = require 'wrench'
EventEmitter = require('events').EventEmitter
preprocessFolderName = (name) ->
encodeURIComponent name
events =
CREATE_FOLDER: 'createFolder'
CREATE_FILE: 'createFile'
ImageGenerator = Object.create EventEmitter.prototype
_.extend ImageGenerator, events
_.extend ImageGenerator,
resetFolderAndFile: (screenshotPath, reportObjPath) ->
wrench.rmdirSyncRecursive screenshotPath if fs.existsSync screenshotPath
fs.unlinkSync reportObjPath if fs.existsSync reportObjPath
wrench.mkdirSyncRecursive screenshotPath
ImageGenerator.emit ImageGenerator.CREATE_FOLDER, screenshotPath
createImageFile: (imagePath, base64Img) ->
fs.writeFileSync imagePath, new Buffer(base64Img, 'base64')
ImageGenerator.emit ImageGenerator.CREATE_FILE, imagePath
createFolder: (folderPath) ->
fs.mkdirSync folderPath
ImageGenerator.emit ImageGenerator.CREATE_FOLDER, folderPath
generateFoldersAndImages: (basePath, compares) ->
# would modify the compares object
_.each compares, (urls, browser) ->
browserFolderPath = path.join basePath, browser
ImageGenerator.createFolder browserFolderPath
_.each urls, (properties, url) ->
urlFolderPath = path.join browserFolderPath, preprocessFolderName(url)
ImageGenerator.createFolder urlFolderPath
_.each properties.images, (base64Img, env) ->
imagePath = path.join(urlFolderPath, env + '.png')
ImageGenerator.createImageFile imagePath, base64Img
properties.images[env] = path.relative path.dirname(__dirname), imagePath
generateReportJsonFile: (reportJsonPath, reportObj) ->
fs.writeFileSync reportJsonPath, JSON.stringify reportObj
ImageGenerator.emit ImageGenerator.CREATE_FILE, reportJsonPath
generate: (reportObj) ->
throw new Error('compares cannot be null.') if reportObj is null
reportObj = _.clone reportObj
screenshotPath = path.join __dirname, '../screenshots'
reportJsonPath = path.join __dirname, '../report.json'
ImageGenerator.resetFolderAndFile screenshotPath, reportJsonPath
ImageGenerator.generateFoldersAndImages screenshotPath, reportObj.compares
ImageGenerator.generateReportJsonFile reportJsonPath, reportObj
module.exports = ImageGenerator

View File

@@ -1,5 +1,5 @@
Viff = require './viff.js'
reporter = require './reporter.js'
Reporter = require './reporter.js'
processArgs = require './process.argv.js'
config = processArgs process.argv
@@ -7,6 +7,6 @@ config = processArgs process.argv
viff = new Viff config.seleniumHost
viff.takeScreenshots(config.browsers, config.envHosts, config.paths).done (compares)->
Viff.diff compares, (compares) ->
console.log reporter.generate config.reportFormat, compares
console.log new Reporter(compares).to config.reportFormat

View File

@@ -1,11 +1,54 @@
_ = require 'underscore'
handlebars = require 'handlebars'
template = require('./html.report.template.js')
colors = require 'colors'
template = require './html.report.template.js'
ImageGenerator = require './image.generator.js'
render = handlebars.compile template
reporter =
generate: (format, data) ->
return render { compares: data } if format is 'html'
return JSON.stringify(data) if format is 'json'
colors.setTheme
info: 'green'
prompt: 'magenta'
greyColor: 'grey'
module.exports = reporter
class Reporter
constructor: (@compares) ->
@cases = []
@differences = []
@totalAnalysisTime = 0
for browser, urls of @compares
for url, diff of urls
diffCase = {}
diffCase[url] = diff
@cases.push diffCase
@differences.push(diffCase) if diff.misMatchPercentage isnt 0
@totalAnalysisTime += diff.analysisTime
@caseCount = @cases.length
@diffCount = @differences.length
to: (format = 'html') ->
reportObj =
compares: @compares
caseCount: @caseCount
sameCount: @caseCount - @diffCount
diffCount: @diffCount
totalAnalysisTime: @totalAnalysisTime
return render reportObj if format is 'html'
return JSON.stringify(reportObj) if format is 'json'
if format is 'file'
ImageGenerator.on ImageGenerator.CREATE_FOLDER, (folerPath) ->
console.log "#{ 'viff'.greyColor } #{ 'create'.info } #{ 'folder'.prompt } #{folerPath}"
ImageGenerator.on ImageGenerator.CREATE_FILE, (filePath) ->
console.log "#{ 'viff'.greyColor } #{ 'create'.info } #{ ' file '.prompt } #{filePath}"
ImageGenerator.generate reportObj
return ''
module.exports = Reporter

View File

@@ -11,7 +11,10 @@ module.exports =
build: 'abcd'
prod: 'efgh'
@diffObj =
@diffObj =
isSameDimensions: true
misMatchPercentage: "2.84"
analysisTime: 54
getImageDataUrl: () ->
'ABCD'
@@ -28,8 +31,8 @@ module.exports =
callback()
'it should have correct properties': (test) ->
test.equals @comparison.build, 'abcd'
test.equals @comparison.prod, 'efgh'
test.equals @comparison.images.build, 'abcd'
test.equals @comparison.images.prod, 'efgh'
test.done()
@@ -38,6 +41,13 @@ module.exports =
@comparison.diff callback
test.ok callback.calledOnce
test.equals @comparison.DIFF, 'ABCD'
test.equals @comparison.images.diff, 'ABCD'
test.done()
'it should get correct diff property': (test) ->
@comparison.diff =>
test.ok @comparison.isSameDimensions
test.strictEqual @comparison.misMatchPercentage, 2.84
test.equals @comparison.analysisTime, 54
test.done()

View File

@@ -0,0 +1,139 @@
_ = require 'underscore'
sinon = require 'sinon'
path = require 'path'
fs = require 'fs'
wrench = require 'wrench'
ImageGenerator = require '../../lib/image.generator.js'
module.exports =
setUp: (callback) ->
@compares =
"firefox":
"/404.html?a=1":
isSameDimensions: true
misMatchPercentage: 3
analysisTime: 51
images:
"build": "base64_build_1"
"prod": "base64_prod_1"
"diff": "base64_diff_1"
"/":
isSameDimensions: true
misMatchPercentage: 3
analysisTime: 52
images:
"build": "base64_build_2"
"prod": "base64_prod_2"
"diff": "base64_diff_2"
"chrome":
"/404.html":
isSameDimensions: true
misMatchPercentage: 11
analysisTime: 53
images:
"build": "base64_build_3"
"prod": "base64_prod_3"
"diff": "base64_diff_3"
"/":
isSameDimensions: true
misMatchPercentage: 0
analysisTime: 54
images:
"build": "base64_build_4"
"prod": "base64_prod_4"
"DIFF": "base64_diff_4"
@reporterObj =
compares: @compares
caseCount: 3
sameCount: 2
diffCount: 1
totalAnalysisTime: 210
@mkdirSync = sinon.stub(fs, 'mkdirSync').returns 1
@existsSync = sinon.stub(fs, 'existsSync').returns true
@writeFileSync = sinon.stub(fs, 'writeFileSync').returns undefined
@unlinkSync = sinon.stub(fs, 'unlinkSync').returns undefined
@rmdirSyncRecursive = sinon.stub(wrench, 'rmdirSyncRecursive').returns undefined
@mkdirSyncRecursive = sinon.stub(wrench, 'mkdirSyncRecursive').returns undefined
callback()
tearDown: (callback) ->
method.restore() for method in [
fs.mkdirSync
fs.existsSync
fs.writeFileSync
fs.unlinkSync
wrench.rmdirSyncRecursive
wrench.mkdirSyncRecursive
]
callback()
'it should remove "screenshots" folder if exist': (test) ->
ImageGenerator.generate @reporterObj
test.ok @rmdirSyncRecursive.lastCall.args[0].indexOf('/screenshots') >= 0
test.done()
'it should remove report.json file if exist': (test) ->
ImageGenerator.generate @reporterObj
test.ok @unlinkSync.lastCall.args[0].indexOf('/report.json') >= 0
test.done()
'it should generate correct directories': (test) ->
ImageGenerator.generate @reporterObj
test.equals @mkdirSync.callCount, 6
test.ok @mkdirSync.getCall(1).args[0].indexOf('/screenshots/firefox/%2F404.html%3Fa%3D1') >= 0
test.ok @mkdirSync.getCall(3).args[0].indexOf('/screenshots/chrome') >= 0
test.done()
'it should always create new "screenshots" folder': (test) ->
ImageGenerator.generate @reporterObj
test.ok @mkdirSyncRecursive.lastCall.args[0].indexOf('/screenshots') >= 0
test.done()
'it should always create new report.json file': (test) ->
ImageGenerator.generate @reporterObj
test.ok @writeFileSync.lastCall.args[0].indexOf('/report.json') >= 0
test.done()
'it should generate correct images': (test) ->
ImageGenerator.generate @reporterObj
test.equals @writeFileSync.callCount, 13
test.done()
'it should generate file-based json file': (test) ->
ImageGenerator.generate @reporterObj
parsedCompares = JSON.parse @writeFileSync.lastCall.args[1]
test.equals parsedCompares.compares.firefox['/404.html?a=1'].images.build, 'screenshots/firefox/%2F404.html%3Fa%3D1/build.png'
test.done()
'it should fire create folder handler': (test) ->
createFolderCallback = sinon.spy()
ImageGenerator.on ImageGenerator.CREATE_FOLDER, createFolderCallback
ImageGenerator.generate @reporterObj
test.ok createFolderCallback.getCall(0).args[0].indexOf('/screenshots') >= 0
test.ok createFolderCallback.getCall(1).args[0].indexOf('/screenshots/firefox') >= 0
test.ok createFolderCallback.getCall(3).args[0].indexOf('/screenshots/firefox/%2F') >= 0
test.ok createFolderCallback.getCall(5).args[0].indexOf('/screenshots/chrome/%2F404.html') >= 0
test.done()
'it should fire create file handler': (test) ->
createFileCallback = sinon.spy()
ImageGenerator.on ImageGenerator.CREATE_FILE, createFileCallback
ImageGenerator.generate @reporterObj
test.equals createFileCallback.callCount, 13
test.ok createFileCallback.getCall(0).args[0].indexOf('/screenshots/firefox/%2F404.html%3Fa%3D1/build.png') >= 0
test.ok createFileCallback.getCall(6).args[0].indexOf('/screenshots/chrome/%2F404.html/build.png') >= 0
test.ok createFileCallback.lastCall.args[0].indexOf('/report.json') >= 0
test.done()

View File

@@ -1,45 +1,90 @@
_ = require 'underscore'
reporter = require '../../lib/reporter.js'
sinon = require 'sinon'
Reporter = require '../../lib/reporter.js'
ImageGenerator = require '../../lib/image.generator.js'
module.exports =
setUp: (callback) ->
@compares =
chrome:
'/404.html':
build: 'aaa'
prod: 'bbb'
isSameDimensions: true
misMatchPercentage: 2.5
analysisTime: 51
images:
build: 'aaa'
prod: 'bbb'
'/strict-mode':
build: 'ccc'
prod: 'ddd'
isSameDimensions: false
misMatchPercentage: 4
analysisTime: 52
images:
build: 'ccc'
prod: 'ddd'
firefox:
'/404.html':
build: 'eee'
prod: 'fff'
isSameDimensions: true
misMatchPercentage: 3
analysisTime: 53
images:
build: 'eee'
prod: 'fff'
'/strict-mode':
build: 'ggg'
prod: 'hhh'
isSameDimensions: false
misMatchPercentage: 0
analysisTime: 54
images:
build: 'ggg'
prod: 'hhh'
callback()
tearDown: (callback) ->
callback()
'it should generate correct html': (test) ->
html = reporter.generate 'html', @compares
'it should contains 4 diffs': (test) ->
reporter = new Reporter @compares
test.equals reporter.caseCount, 4
test.done()
'it should count how mamy diff is there': (test) ->
reporter = new Reporter @compares
test.equals reporter.diffCount, 3
test.done()
'it should return total analysisTime': (test) ->
reporter = new Reporter @compares
test.equals reporter.totalAnalysisTime, 210
test.done()
'it should generate correct html': (test) ->
html = new Reporter(@compares).to 'html'
test.ok html.indexOf('<h2>chrome</h2>') > 0
test.ok html.indexOf('<h3>/404.html</h3>') > 0
test.ok html.indexOf('<h3>/404.html - 3% mismatch 53ms</h3>') > 0
test.ok html.indexOf('data:image/png;base64,ggg') > 0
test.ok html.indexOf('data-env="build"') > 0
test.ok html.indexOf('<h1>Viff Report - (1 same in 4 cases) 210ms</h1>') > 0
test.done()
'it should generate correct json': (test) ->
jsonStr = reporter.generate 'json', @compares
jsonStr = new Reporter(@compares).to 'json'
json = JSON.parse jsonStr
test.ok _.isEqual _.keys(json), ['chrome', 'firefox']
test.ok _.isEqual _.keys(json.chrome['/404.html']), ['build', 'prod']
test.ok _.isEqual _.values(json.chrome['/404.html']), ['aaa', 'bbb']
test.equals json.caseCount, 4
test.equals json.diffCount, 3
test.equals json.totalAnalysisTime, 210
test.ok _.isEqual _.keys(json.compares), ['chrome', 'firefox']
test.ok _.isEqual _.keys(json.compares.chrome['/404.html'].images), ['build', 'prod']
test.ok _.isEqual _.values(json.compares.chrome['/404.html'].images), ['aaa', 'bbb']
test.done()
'it should generate correct file-based json': (test) ->
generate = sinon.stub(ImageGenerator, 'generate').returns 'undefined'
new Reporter(@compares).to 'file'
test.ok generate.calledOnce
test.equals generate.lastCall.args[0].compares, @compares
test.done()