From d6ddb7e29d37f956567acb16db0ac48724e44ca0 Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Sat, 22 Dec 2012 16:28:34 +0200 Subject: [PATCH] Added automated testing with selenium --- src/Parse.js | 55 ++++----- src/plugins/jquery.plugin.html2canvas.js | 150 ++++++++++++----------- src/renderers/Canvas.js | 2 +- tests/background.html | 39 +++--- tests/borders.html | 11 +- tests/selenium.js | 94 ++++++++++++++ 6 files changed, 217 insertions(+), 134 deletions(-) create mode 100644 tests/selenium.js diff --git a/src/Parse.js b/src/Parse.js index d82b5be..a361141 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -1,4 +1,4 @@ -_html2canvas.Parse = function ( images, options ) { +_html2canvas.Parse = function (images, options) { window.scroll(0,0); var support = { @@ -96,16 +96,16 @@ _html2canvas.Parse = function ( images, options ) { } var getCSS = _html2canvas.Util.getCSS; + function getCSSInt(element, attribute) { var val = parseInt(getCSS(element, attribute), 10); return (isNaN(val)) ? 0 : val; // borders in old IE are throwing 'medium' for demo.html } - // Drawing a rectangle function renderRect (ctx, x, y, w, h, bgcolor) { if (bgcolor !== "transparent"){ ctx.setVariable("fillStyle", bgcolor); - ctx.fillRect (x, y, w, h); + ctx.fillRect(x, y, w, h); numDraws+=1; } } @@ -144,6 +144,7 @@ _html2canvas.Parse = function ( images, options ) { var container = doc.createElement('div'), img = doc.createElement('img'), span = doc.createElement('span'), + sampleText = 'Hidden Text', baseline, middle, metricsObj; @@ -170,13 +171,13 @@ _html2canvas.Parse = function ( images, options ) { span.style.margin = 0; span.style.padding = 0; - span.appendChild(doc.createTextNode('Hidden Text')); + span.appendChild(doc.createTextNode(sampleText)); container.appendChild(span); container.appendChild(img); baseline = (img.offsetTop - span.offsetTop) + 1; container.removeChild(span); - container.appendChild(doc.createTextNode('Hidden Text')); + container.appendChild(doc.createTextNode(sampleText)); container.style.lineHeight = "normal"; img.style.verticalAlign = "super"; @@ -222,6 +223,7 @@ _html2canvas.Parse = function ( images, options ) { ctx.setVariable("fillStyle", color); ctx.setVariable("font", [font_style, font_variant, bold, size, family].join(" ")); ctx.setVariable("textAlign", (align) ? "right" : "left"); + if (text_decoration !== "none"){ return fontMetrics(family, size); } @@ -327,8 +329,9 @@ _html2canvas.Parse = function ( images, options ) { } - - drawText(textValue, bounds.left, bounds.bottom, ctx); + if (textValue !== null) { + drawText(textValue, bounds.left, bounds.bottom, ctx); + } renderTextDecoration(text_decoration, bounds, metrics, color); textOffset += renderList[c].length; @@ -341,21 +344,21 @@ _html2canvas.Parse = function ( images, options ) { function listPosition (element, val) { var boundElement = doc.createElement( "boundelement" ), - type, + originalType, bounds; boundElement.style.display = "inline"; - type = element.style.listStyleType; + originalType = element.style.listStyleType; element.style.listStyleType = "none"; - boundElement.appendChild( doc.createTextNode( val ) ); + boundElement.appendChild(doc.createTextNode(val)); element.insertBefore(boundElement, element.firstChild); - bounds = _html2canvas.Util.Bounds( boundElement ); - element.removeChild( boundElement ); - element.style.listStyleType = type; + bounds = _html2canvas.Util.Bounds(boundElement); + element.removeChild(boundElement); + element.style.listStyleType = originalType; return bounds; } @@ -406,9 +409,7 @@ _html2canvas.Parse = function ( images, options ) { } function renderListItem(element, stack, elBounds) { - var position = getCSS(element, "listStylePosition"), - x, - y, + var x, text, type = getCSS(element, "listStyleType"), listBounds; @@ -418,16 +419,14 @@ _html2canvas.Parse = function ( images, options ) { listBounds = listPosition(element, text); setTextVariables(ctx, element, "none", getCSS(element, "color")); - if (position === "inside") { + if (getCSS(element, "listStylePosition") === "inside") { ctx.setVariable("textAlign", "left"); x = elBounds.left; } else { return; } - y = listBounds.bottom; - - drawText(text, x, y, ctx); + drawText(text, x, listBounds.bottom, ctx); } } @@ -731,15 +730,13 @@ _html2canvas.Parse = function ( images, options ) { height, add; - // if (typeof background_image !== "undefined" && /^(1|none)$/.test(background_image) === false && /^(-webkit|-moz|linear-gradient|-o-)/.test(background_image)===false){ - if ( !/data:image\/.*;base64,/i.test(background_image) && !/^(-webkit|-moz|linear-gradient|-o-)/.test(background_image) ) { background_image = background_image.split(",")[0]; } - if ( typeof background_image !== "undefined" && /^(1|none)$/.test( background_image ) === false ) { - background_image = _html2canvas.Util.backgroundImage( background_image ); - image = loadImage( background_image ); + if ( typeof background_image !== "undefined" && /^(1|none)$/.test(background_image) === false ) { + background_image = _html2canvas.Util.backgroundImage(background_image); + image = loadImage(background_image); bgp = _html2canvas.Util.BackgroundPosition(el, bounds, image); @@ -748,11 +745,11 @@ _html2canvas.Parse = function ( images, options ) { if ( image ){ switch ( background_repeat ) { case "repeat-x": - renderBackgroundRepeatX( ctx, image, bgp, bounds.left, bounds.top, bounds.width, bounds.height ); + renderBackgroundRepeatX(ctx, image, bgp, bounds.left, bounds.top, bounds.width, bounds.height); break; case "repeat-y": - renderBackgroundRepeatY( ctx, image, bgp, bounds.left, bounds.top, bounds.width, bounds.height ); + renderBackgroundRepeatY(ctx, image, bgp, bounds.left, bounds.top, bounds.width, bounds.height); break; case "no-repeat": @@ -890,18 +887,16 @@ _html2canvas.Parse = function ( images, options ) { ctx.setVariable("globalAlpha", stack.opacity); - // draw element borders + borders = renderBorders(el, ctx, bounds, false); stack.borders = borders; - // let's modify clip area for child elements, so borders dont get overwritten if (ignoreElementsRegExp.test(el.nodeName) && options.iframeDefault !== "transparent"){ bgcolor = (options.iframeDefault === "default") ? "#efefef" : options.iframeDefault; } - // draw base element bgcolor bgbounds = { left: x + borders[3].width, diff --git a/src/plugins/jquery.plugin.html2canvas.js b/src/plugins/jquery.plugin.html2canvas.js index 8584fea..fd446ba 100644 --- a/src/plugins/jquery.plugin.html2canvas.js +++ b/src/plugins/jquery.plugin.html2canvas.js @@ -2,79 +2,83 @@ * jQuery helper plugin for examples and tests */ (function( $ ){ - $.fn.html2canvas = function(options) { - if (options && options.profile && window.console && window.console.profile) { - console.profile(); - } - var date = new Date(), - html2obj, - $message = null, - timeoutTimer = false, - timer = date.getTime(); - options = options || {}; - - options.onrendered = options.onrendered || function( canvas ) { - var $canvas = $(canvas), - finishTime = new Date(); - - if (options && options.profile && window.console && window.console.profileEnd) { - console.profileEnd(); - } - $canvas.css({ - position: 'absolute', - left: 0, - top: 0 - }).appendTo(document.body); - $canvas.siblings().toggle(); - - $(window).click(function(){ - $canvas.toggle().siblings().toggle(); - throwMessage("Canvas Render " + ($canvas.is(':visible') ? "visible" : "hidden")); - }); - throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)) + " ms
",4000); - - // test if canvas is read-able - try { - $canvas[0].toDataURL(); - } catch(e) { - if ($canvas[0].nodeName.toLowerCase() === "canvas") { - // TODO, maybe add a bit less offensive way to present this, but still something that can easily be noticed - alert("Canvas is tainted, unable to read data"); - } - } - }; - - html2obj = html2canvas(this, options); - - function throwMessage(msg,duration){ - window.clearTimeout(timeoutTimer); - timeoutTimer = window.setTimeout(function(){ - $message.fadeOut(function(){ - $message.remove(); - $message = null; - }); - },duration || 2000); - if ($message) - $message.remove(); - $message = $('
').html(msg).css({ - margin:0, - padding:10, - background: "#000", - opacity:0.7, - position:"fixed", - top:10, - right:10, - fontFamily: 'Tahoma', - color:'#fff', - fontSize:12, - borderRadius:12, - width:'auto', - height:'auto', - textAlign:'center', - textDecoration:'none', - display:'none' - }).appendTo(document.body).fadeIn(); - html2obj.log(msg); + $.fn.html2canvas = function(options) { + if (options && options.profile && window.console && window.console.profile) { + console.profile(); + } + var date = new Date(), + html2obj, + $message = null, + timeoutTimer = false, + timer = date.getTime(); + options = options || {}; + + options.onrendered = options.onrendered || function( canvas ) { + var $canvas = $(canvas), + finishTime = new Date(); + + if (options && options.profile && window.console && window.console.profileEnd) { + console.profileEnd(); + } + $canvas.addClass("html2canvas") + .css({ + position: 'absolute', + left: 0, + top: 0 + }).appendTo(document.body); + + if (window.location.search !== "?selenium") { + $canvas.siblings().toggle(); + $(window).click(function(){ + $canvas.toggle().siblings().toggle(); + throwMessage("Canvas Render " + ($canvas.is(':visible') ? "visible" : "hidden")); + }); + throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)) + " ms
",4000); + } else { + $canvas.css('display', 'none'); + } + // test if canvas is read-able + try { + $canvas[0].toDataURL(); + } catch(e) { + if ($canvas[0].nodeName.toLowerCase() === "canvas") { + // TODO, maybe add a bit less offensive way to present this, but still something that can easily be noticed + alert("Canvas is tainted, unable to read data"); } + } }; + + html2obj = html2canvas(this, options); + + function throwMessage(msg,duration){ + window.clearTimeout(timeoutTimer); + timeoutTimer = window.setTimeout(function(){ + $message.fadeOut(function(){ + $message.remove(); + $message = null; + }); + },duration || 2000); + if ($message) + $message.remove(); + $message = $('
').html(msg).css({ + margin:0, + padding:10, + background: "#000", + opacity:0.7, + position:"fixed", + top:10, + right:10, + fontFamily: 'Tahoma', + color:'#fff', + fontSize:12, + borderRadius:12, + width:'auto', + height:'auto', + textAlign:'center', + textDecoration:'none', + display:'none' + }).appendTo(document.body).fadeIn(); + html2obj.log(msg); + } + }; })( jQuery ); diff --git a/src/renderers/Canvas.js b/src/renderers/Canvas.js index c123886..f19d353 100644 --- a/src/renderers/Canvas.js +++ b/src/renderers/Canvas.js @@ -94,7 +94,7 @@ _html2canvas.Renderer.Canvas = function( options ) { canvas.height = canvas.style.height = (!usingFlashcanvas) ? options.height || zStack.ctx.height : Math.min(flashMaxSize, (options.height || zStack.ctx.height) ); fstyle = ctx.fillStyle; - ctx.fillStyle = zStack.backgroundColor; + ctx.fillStyle = (zStack.backgroundColor === "transparent") ? "#fff" : zStack.backgroundColor; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = fstyle; diff --git a/tests/background.html b/tests/background.html index 4219f2d..7ad8618 100644 --- a/tests/background.html +++ b/tests/background.html @@ -1,8 +1,3 @@ - @@ -18,7 +13,7 @@ } .small div{ width:100px; - height:100px; + height:100px; float:left; margin:10px; border:1px solid #000; @@ -26,7 +21,7 @@ .medium div{ width:200px; - height:200px; + height:200px; float:left; margin:10px; border:1px solid #000; @@ -36,8 +31,8 @@ clear:both; } - div{ - display:block; + div{ + display:block; } .encoded { @@ -47,14 +42,14 @@ .linearGradient { /*background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(255, 0, 0)), to(rgb(0, 0, 255)));*/ - background: -webkit-linear-gradient(top left, #f00, #00f, #BADA55, rgba(0, 0, 255,0.5)); + background: -webkit-linear-gradient(top left, #f00, #00f, #BADA55, rgba(0, 0, 255,0.5)); background: -moz-linear-gradient(top right, #f00, #00f, #BADA55, rgba(0, 0, 255,0.5)); } .linearGradient2 { background: -webkit-gradient(linear, 0% 0, 0% 100%, from(rgb(252, 252, 252)), to(rgb(232, 232, 232))); } - + .linearGradient3 { /* FF 3.6+ */ background: -moz-linear-gradient(left, #ff0000, #ffff00, #00ff00); @@ -69,7 +64,7 @@ /* W3C */ background: linear-gradient(left, #ff0000, #ffff00, #00ff00); } - + .linearGradient4 { /* FF 3.6+ */ background: -moz-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%); @@ -84,7 +79,7 @@ /* W3C */ background: linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%); } - + .linearGradient5 { /* FF 3.6+ */ background: -moz-linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%); @@ -99,7 +94,7 @@ /* W3C */ background: linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%); } - + .radialGradient { /* FF 3.6+ */ background: -moz-radial-gradient(center, ellipse cover, #959595 0%, #0d0d0d 48%, #2f7bd8 50%, #0a0a0a 64%, #4e4e4e 80%, #383838 87%, #1b1b1b 100%); @@ -114,32 +109,32 @@ /* W3C */ background: radial-gradient(center, ellipse cover, #959595 0%,#0d0d0d 48%,#2f7bd8 50%,#0a0a0a 64%,#4e4e4e 80%,#383838 87%,#1b1b1b 100%); } - + .radialGradient2 { /* thx to https://twitter.com/#!/markjmclaren/status/13067366701 */ - + background-color: #FFF; - + background-image: -webkit-gradient(radial, 45 45, 10, 52 50, 30, from(#A7D30C), to(rgba(1,159,98,0)), color-stop(90%, #019F62)), -webkit-gradient(radial, 105 105, 20, 112 120, 50, from(#ff5f98), to(rgba(255,1,136,0)), color-stop(75%, #ff0188)), -webkit-gradient(radial, 95 15, 15, 102 20, 40, from(#00c9ff), to(rgba(0,201,255,0)), color-stop(80%, #00b5e2)), -webkit-gradient(radial, 0 150, 50, 0 140, 90, from(#f4f201), to(rgba(228, 199,0,0)), color-stop(80%, #e4c700)); - + background-image: -moz-radial-gradient(42px 42px, circle farthest-corner, #A7D30C 0%, #A7D30C 3%, rgba(1,159,98,0) 11%), -moz-radial-gradient(45px 45px, circle farthest-corner, #019F62 0%, #019F62 13%, rgba(255,255,255,0) 16%, rgba(255,255,255,0) 100%), - + -moz-radial-gradient(102px 102px, circle farthest-corner, #ff5f98 0%, #ff5f98 15%, rgba(255,1,136,0) 27%), -moz-radial-gradient(105px 105px, circle farthest-corner, #FF0188 0%, #FF0188 28%, rgba(255,255,255,0) 32%, rgba(255,255,255,0) 100%), - + -moz-radial-gradient(92px 12px, circle farthest-corner, #00c9ff 0%, #00c9ff 10%, rgba(0,181,226,0) 26%), -moz-radial-gradient(95px 15px, circle farthest-corner, #00b5e2 0%, #00b5e2 28%, rgba(255,255,255,0) 31%, rgba(255,255,255,0) 100%), - + -moz-radial-gradient(0px 150px, circle farthest-corner, #f4f201 0%, #f4f201 25%, rgba(228,199,0,0) 45%), -moz-radial-gradient(0px 150px, circle farthest-corner, #e4c700 0%, #e4c700 47%, rgba(255,255,255,0) 50%, rgba(255,255,255,0) 100%); } - + .radialGradient3 { background: -moz-radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%); background: -webkit-radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%); diff --git a/tests/borders.html b/tests/borders.html index 4957ff0..3d62423 100644 --- a/tests/borders.html +++ b/tests/borders.html @@ -1,14 +1,9 @@ - border tests - + diff --git a/tests/selenium.js b/tests/selenium.js new file mode 100644 index 0000000..793767e --- /dev/null +++ b/tests/selenium.js @@ -0,0 +1,94 @@ +var webdriver = require("webdriver.js").webdriver, +http = require("http"), +url = require("url"), +path = require("path"), +base64_arraybuffer = require('base64-arraybuffer'), +PNG = require('png-js'), +fs = require("fs"), +port = 5555; + +var server = http.createServer(function(request, response) { + var uri = url.parse(request.url).pathname, + filename = path.join(process.cwd(), "../" + uri); + + fs.exists(filename, function(exists) { + if(!exists) { + response.writeHead(404, { + "Content-Type": "text/plain" + }); + response.write("404 Not Found\n"); + response.end(); + return; + } + + if (fs.statSync(filename).isDirectory()) filename += '/index.html'; + + fs.readFile(filename, "binary", function(err, file) { + if(err) { + response.writeHead(500, { + "Content-Type": "text/plain" + }); + response.write(err + "\n"); + response.end(); + return; + } + + response.writeHead(200); + response.write(file, "binary"); + response.end(); + }); + }); + +}).listen(port); + + +function getPixelArray(base64, func) { + var arraybuffer = base64_arraybuffer.decode(base64); + (new PNG(arraybuffer)).decode(func); +} + +var browser = new webdriver({ + logging:false +}); + + +function testPage(url, done) { + browser.url("http://localhost:" + port + "/tests/" + url + "?selenium") + .$(".html2canvas", 5000, function(){ + this.execute(function(){ + var canvas = $('.html2canvas')[0]; + return canvas.toDataURL("image/png").substring(22); + },[], function(dataurl) { + getPixelArray(dataurl, function(h2cPixels) { + browser.screenshot(function(base64){ + getPixelArray(base64, function(screenPixels) { + var len = h2cPixels.length, index = 0, diff = 0; + for (; index < len; index++) { + if (screenPixels[index] - h2cPixels[index] !== 0) { + diff++; + } + } + done(100 - (Math.round((diff/h2cPixels.length) * 10000) / 100)); + }); + }) + }); + }); + }); +} + +(function(pages) { + + (function processPage(page) { + testPage(page, function(result) { + if (pages.length > 0) { + processPage(pages.shift()); + } else { + browser.close(function(){ + server.close(); + }); + } + console.log(page, result); + }); + })(pages.shift()); + +})(["overflow.html", "forms.html", "lists.html"]); \ No newline at end of file