(function(){ "use strict;"; var wd = require('wd'), http = require("http"), https = require("https"), url = require("url"), path = require("path"), base64_arraybuffer = require('base64-arraybuffer'), PNG = require('png-js'), Promise = require('bluebird'), _ = require('lodash'), humanizeDuration = require("humanize-duration"), fs = require("fs"); Promise.promisifyAll(fs); var port = 8080, colors = { red: "\x1b[1;31m", blue: "\x1b[1;36m", violet: "\x1b[0;35m", green: "\x1b[0;32m", clear: "\x1b[0m" }; function getTests(path) { return fs.readdirAsync(path).map(function(name) { var filename = path + "/" + name; return fs.statAsync(filename).then(function(stat) { return stat.isDirectory() ? getTests(filename) : filename; }); }); } function getPixelArray(base64) { return new Promise(function(resolve) { var arraybuffer = base64_arraybuffer.decode(base64); (new PNG(arraybuffer)).decodePixels(resolve); }); } function calculateDifference(h2cPixels, screenPixels) { var len = h2cPixels.length, index = 0, diff = 0; for (; index < len; index++) { if (screenPixels[index] - h2cPixels[index] !== 0) { diff++; } } return (100 - (Math.round((diff/h2cPixels.length) * 10000) / 100)); } function initBrowser(settings) { var browser = wd.remote({ hostname: 'localhost', port: 4445, user: process.env.SAUCE_USERNAME, pwd: process.env.SAUCE_ACCESS_KEY }, 'promiseChain'); if (process.env.TRAVIS_JOB_NUMBER) { settings["tunnel-identifier"] = process.env.TRAVIS_JOB_NUMBER; settings["name"] = process.env.TRAVIS_COMMIT.substring(0, 10); settings["build"] = process.env.TRAVIS_BUILD_NUMBER; } else { settings["name"] = "Manual run"; } return browser.resolve(Promise).init(settings).setImplicitWaitTimeout(15000).then(function() { return settings; }); } function loadTestPage(browser, test) { return function(settings) { return browser.get("http://localhost:" + port + "/" + test + "?selenium").then(function() { return settings; }); }; } function captureScreenshots(browser) { return function() { return Promise.props({ dataUrl: browser.elementByCss(".html2canvas").then(function(node) { return browser.execute("return arguments[0].toDataURL('image/png').substring(22)", [node]); }), screenshot: browser.takeScreenshot() }); }; } function analyzeResults(test) { return function(result) { return Promise.all([getPixelArray(result.dataUrl), getPixelArray(result.screenshot)]).spread(calculateDifference).then(function(accuracy) { return { testCase: test, accuracy: accuracy, dataUrl: result.dataUrl, screenshot: result.screenshot }; }); }; } function runTestWithRetries(browser, test, retries) { retries = retries || 0; return runTest(browser, test) .timeout(60000) .catch(Promise.TimeoutError, function() { if (retries < 3) { console.log(colors.violet, "Retry", (retries + 1), test); return runTestWithRetries(browser, test, retries + 1); } else { throw new Error("Couldn't run test after 3 retries"); } }); } function runTest(browser, test) { return Promise.resolve(browser .then(loadTestPage(browser, test)) .then(captureScreenshots(browser)) .then(analyzeResults(test))).cancellable(); } exports.tests = function(browsers, singleTest) { var path = "tests/cases"; return (singleTest ? Promise.resolve([singleTest]) : getTests(path)).then(function(t) { var tests = _.flatten(t); return Promise.map(browsers, function(settings) { var browser = initBrowser(settings); var name = [settings.browserName, settings.version, settings.platform].join("-"); var count = 0; return Promise.map(tests, function(test, index, total) { console.log(colors.green, "STARTING", "(" + (++count) + "/" + total + ")", name, test, colors.clear); var start = Date.now(); return runTestWithRetries(browser, test).then(function(result) { console.log(colors.green, "COMPLETE", humanizeDuration(Date.now() - start), "(" + count + "/" + total + ")", name, result.testCase.substring(path.length), result.accuracy.toFixed(2) + "%", colors.clear); }); }, {concurrency: 1}) .settle() .catch(function(error) { console.log(colors.red, "ERROR", name, error.message); throw error; }) .finally(function() { return browser.quit(); }); }, {concurrency: 3}); }); }; })();