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 chromedriver.log
report.html report.html
.DS_Store
/node_modules/ /node_modules/
/lib/ /lib/

View File

@@ -28,10 +28,12 @@ module.exports = function(grunt) {
'lib/reporter.js': 'src/reporter.coffee', 'lib/reporter.js': 'src/reporter.coffee',
'lib/html.report.template.js': 'src/html.report.template.coffee', 'lib/html.report.template.js': 'src/html.report.template.coffee',
'lib/process.argv.js': 'src/process.argv.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/comparison_test.js': 'test/src/comparison_test.coffee',
'test/build/viff_test.js': 'test/src/viff_test.coffee', 'test/build/viff_test.js': 'test/src/viff_test.coffee',
'test/build/reporter_test.js': 'test/src/reporter_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 = { module.exports = {
seleniumHost: 'http://localhost:4444/wd/hub', seleniumHost: 'http://localhost:4444/wd/hub',
browsers: ['firefox'], browsers: ['firefox', 'safari', 'chrome', 'opera'],
envHosts: { envHosts: {
build: 'http://localhost:4000', build: 'http://localhost:4000',
prod: 'http://ishouldbeageek.me' prod: 'http://ishouldbeageek.me'
}, },
paths: require('./links.js'), paths: require('./links.js'),
reportFormat: 'html' reportFormat: 'file'
}; };

View File

@@ -4,9 +4,10 @@ module.exports = (function initLinks() {
return [ return [
'/404.html', '/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": "*", "underscore": "*",
"selenium-webdriver": ">=2.33.0", "selenium-webdriver": ">=2.33.0",
"handlebars": "~1.0.12", "handlebars": "~1.0.12",
"resemble": "~1.0.3" "resemble": "~1.0.3",
"wrench": "*",
"colors": "*"
}, },
"devDependencies": { "devDependencies": {
"grunt": "~0.4.1", "grunt": "~0.4.1",

View File

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

View File

@@ -10,7 +10,8 @@ template = """
body{font: normal normal 14px/1.5 'Open Sans', sans-serif; } 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,ul,h1,h2,h3,h4{margin:0;padding:0;font-weight:300;font-style:normal;}
body{margin: 20px;} body{margin: 20px;}
h2{font-size: 3em;} h1{font-size: 3em;}
h2{font-size: 2.4em;}
ul{overflow: hidden;} ul{overflow: hidden;}
li{list-style-type: none; margin-bottom: 20px; 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;} a {float: left;height: 100px;width: 100px; overflow: hidden; margin-right:1%; position: relative;}
@@ -37,13 +38,14 @@ template = """
</style> </style>
</head> </head>
<body> <body>
<h1>Viff Report - ({{sameCount}} same in {{caseCount}} cases) {{totalAnalysisTime}}ms</h1>
{{#each compares}} {{#each compares}}
<h2>{{@key}}</h2> <h2>{{@key}}</h2>
<ul> <ul>
{{#each this}} {{#each this}}
<li> <li>
<h3>{{@key}}</h3> <h3>{{@key}} - {{this.misMatchPercentage}}% mismatch {{this.analysisTime}}ms</h3>
{{#each this}} {{#each this.images}}
<a href="data:image/png;base64,{{this}}" data-env="{{@key}}"> <a href="data:image/png;base64,{{this}}" data-env="{{@key}}">
<img src="data:image/png;base64,{{this}}"/> <img src="data:image/png;base64,{{this}}"/>
</a> </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' Viff = require './viff.js'
reporter = require './reporter.js' Reporter = require './reporter.js'
processArgs = require './process.argv.js' processArgs = require './process.argv.js'
config = processArgs process.argv config = processArgs process.argv
@@ -7,6 +7,6 @@ config = processArgs process.argv
viff = new Viff config.seleniumHost viff = new Viff config.seleniumHost
viff.takeScreenshots(config.browsers, config.envHosts, config.paths).done (compares)-> viff.takeScreenshots(config.browsers, config.envHosts, config.paths).done (compares)->
Viff.diff compares, (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' 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 render = handlebars.compile template
reporter = colors.setTheme
generate: (format, data) -> info: 'green'
return render { compares: data } if format is 'html' prompt: 'magenta'
return JSON.stringify(data) if format is 'json' 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' build: 'abcd'
prod: 'efgh' prod: 'efgh'
@diffObj = @diffObj =
isSameDimensions: true
misMatchPercentage: "2.84"
analysisTime: 54
getImageDataUrl: () -> getImageDataUrl: () ->
'ABCD' 'ABCD'
@@ -28,8 +31,8 @@ module.exports =
callback() callback()
'it should have correct properties': (test) -> 'it should have correct properties': (test) ->
test.equals @comparison.build, 'abcd' test.equals @comparison.images.build, 'abcd'
test.equals @comparison.prod, 'efgh' test.equals @comparison.images.prod, 'efgh'
test.done() test.done()
@@ -38,6 +41,13 @@ module.exports =
@comparison.diff callback @comparison.diff callback
test.ok callback.calledOnce test.ok callback.calledOnce
test.equals @comparison.DIFF, 'ABCD' test.equals @comparison.images.diff, 'ABCD'
test.done() 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' _ = require 'underscore'
reporter = require '../../lib/reporter.js' sinon = require 'sinon'
Reporter = require '../../lib/reporter.js'
ImageGenerator = require '../../lib/image.generator.js'
module.exports = module.exports =
setUp: (callback) -> setUp: (callback) ->
@compares = @compares =
chrome: chrome:
'/404.html': '/404.html':
build: 'aaa' isSameDimensions: true
prod: 'bbb' misMatchPercentage: 2.5
analysisTime: 51
images:
build: 'aaa'
prod: 'bbb'
'/strict-mode': '/strict-mode':
build: 'ccc' isSameDimensions: false
prod: 'ddd' misMatchPercentage: 4
analysisTime: 52
images:
build: 'ccc'
prod: 'ddd'
firefox: firefox:
'/404.html': '/404.html':
build: 'eee' isSameDimensions: true
prod: 'fff' misMatchPercentage: 3
analysisTime: 53
images:
build: 'eee'
prod: 'fff'
'/strict-mode': '/strict-mode':
build: 'ggg' isSameDimensions: false
prod: 'hhh' misMatchPercentage: 0
analysisTime: 54
images:
build: 'ggg'
prod: 'hhh'
callback() callback()
tearDown: (callback) -> tearDown: (callback) ->
callback() callback()
'it should generate correct html': (test) -> 'it should contains 4 diffs': (test) ->
html = reporter.generate 'html', @compares 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('<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:image/png;base64,ggg') > 0
test.ok html.indexOf('data-env="build"') > 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() test.done()
'it should generate correct json': (test) -> 'it should generate correct json': (test) ->
jsonStr = reporter.generate 'json', @compares jsonStr = new Reporter(@compares).to 'json'
json = JSON.parse jsonStr json = JSON.parse jsonStr
test.ok _.isEqual _.keys(json), ['chrome', 'firefox'] test.equals json.caseCount, 4
test.ok _.isEqual _.keys(json.chrome['/404.html']), ['build', 'prod'] test.equals json.diffCount, 3
test.ok _.isEqual _.values(json.chrome['/404.html']), ['aaa', 'bbb'] 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() test.done()