2012-12-28 21:41:14 +04:00
|
|
|
(function(){
|
2013-05-29 18:53:47 +04:00
|
|
|
"use strict;";
|
2014-05-18 18:39:24 +04:00
|
|
|
var Bacon = require('baconjs').Bacon,
|
|
|
|
wd = require('wd'),
|
2013-05-29 18:53:47 +04:00
|
|
|
http = require("http"),
|
2013-05-29 22:16:28 +04:00
|
|
|
https = require("https"),
|
2013-05-29 18:53:47 +04:00
|
|
|
url = require("url"),
|
|
|
|
path = require("path"),
|
|
|
|
base64_arraybuffer = require('base64-arraybuffer'),
|
|
|
|
PNG = require('png-js'),
|
2014-05-18 18:39:24 +04:00
|
|
|
fs = require("fs");
|
2013-05-29 18:53:47 +04:00
|
|
|
|
|
|
|
var port = 8080,
|
|
|
|
colors = {
|
|
|
|
red: "\x1b[1;31m",
|
|
|
|
blue: "\x1b[1;36m",
|
|
|
|
violet: "\x1b[0;35m",
|
|
|
|
green: "\x1b[0;32m",
|
|
|
|
clear: "\x1b[0m"
|
|
|
|
};
|
2012-12-28 01:06:47 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
function mapStat(item) {
|
|
|
|
return Bacon.combineTemplate({
|
|
|
|
stat: Bacon.fromNodeCallback(fs.stat, item),
|
|
|
|
item: item
|
2012-12-28 21:41:14 +04:00
|
|
|
});
|
2013-05-29 18:53:47 +04:00
|
|
|
}
|
2012-12-28 21:41:14 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
function isDirectory(item) {
|
|
|
|
return item.stat.isDirectory();
|
|
|
|
}
|
2012-12-28 21:41:14 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
function getItem(item) {
|
|
|
|
return item.item;
|
|
|
|
}
|
2012-12-22 18:28:34 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
function isFile(item) {
|
|
|
|
return !isDirectory(item);
|
|
|
|
}
|
2012-12-28 21:41:14 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
function arrayStream(arr) {
|
|
|
|
return Bacon.fromArray(arr);
|
|
|
|
}
|
2012-12-28 21:41:14 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
function getTests(path) {
|
|
|
|
var items = Bacon.fromNodeCallback(fs.readdir, path).flatMap(arrayStream).map(function(name) {
|
|
|
|
return path + "/" + name;
|
|
|
|
}).flatMap(mapStat);
|
|
|
|
return items.filter(isFile).map(getItem).merge(items.filter(isDirectory).map(getItem).flatMap(getTests));
|
2012-12-28 01:06:47 +04:00
|
|
|
}
|
|
|
|
|
2012-12-28 21:41:14 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
function getPixelArray(base64) {
|
|
|
|
return Bacon.fromCallback(function(callback) {
|
|
|
|
var arraybuffer = base64_arraybuffer.decode(base64);
|
2014-08-27 22:35:48 +04:00
|
|
|
(new PNG(arraybuffer)).decodePixels(callback);
|
2013-05-29 18:53:47 +04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
2012-12-28 21:41:14 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
function findResult(testName, tests) {
|
|
|
|
var item = null;
|
|
|
|
return tests.some(function(testCase) {
|
|
|
|
item = testCase;
|
|
|
|
return testCase.test === testName;
|
|
|
|
}) ? item : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function compareResults(oldResults, newResults, browser) {
|
|
|
|
var improved = [],
|
|
|
|
regressed = [],
|
|
|
|
newItems = [];
|
|
|
|
|
|
|
|
newResults.forEach(function(testCase){
|
|
|
|
var testResult = testCase.result,
|
|
|
|
oldResult = findResult(testCase.test, oldResults),
|
|
|
|
oldResultValue = oldResult ? oldResult.result : null,
|
|
|
|
dataObject = {
|
|
|
|
amount: (Math.abs(testResult - oldResultValue) < 0.01) ? 0 : testResult - oldResultValue,
|
|
|
|
test: testCase.test
|
|
|
|
};
|
|
|
|
if (oldResultValue === null) {
|
|
|
|
newItems.push(dataObject);
|
|
|
|
} else if (dataObject.amount > 0) {
|
|
|
|
improved.push(dataObject);
|
|
|
|
} else if (dataObject.amount < 0) {
|
|
|
|
regressed.push(dataObject);
|
|
|
|
}
|
2012-12-28 21:41:14 +04:00
|
|
|
});
|
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
reportChanges(browser, improved, regressed, newItems);
|
|
|
|
}
|
2012-12-28 21:41:14 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
function reportChanges(browser, improved, regressed, newItems) {
|
|
|
|
if (newItems.length > 0 || improved.length > 0 || regressed.length > 0) {
|
|
|
|
console.log((regressed.length > 0) ? colors.red : colors.green, browser);
|
2012-12-29 17:02:05 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
regressed.forEach(function(item) {
|
|
|
|
console.log(colors.red, item.amount + "%", item.test);
|
|
|
|
});
|
2012-12-28 01:06:47 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
improved.forEach(function(item) {
|
|
|
|
console.log(colors.green, item.amount + "%", item.test);
|
|
|
|
});
|
2012-12-30 18:29:01 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
newItems.forEach(function(item) {
|
|
|
|
console.log(colors.blue, "NEW", item.test);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2013-03-24 04:00:01 +04:00
|
|
|
|
2013-05-29 18:53:47 +04:00
|
|
|
function formatResultName(navigator) {
|
2014-05-18 18:39:24 +04:00
|
|
|
return (navigator.browserName + "-" + ((navigator.version) ? navigator.version : "release") + "-" + navigator.platform).replace(/ /g, "").toLowerCase();
|
|
|
|
}
|
|
|
|
|
|
|
|
function webdriverStream(test) {
|
2014-05-18 20:28:50 +04:00
|
|
|
var browser = wd.remote("localhost", 4445, process.env.SAUCE_USERNAME, process.env.SAUCE_ACCESS_KEY);
|
2014-05-18 18:39:24 +04:00
|
|
|
var browserStream = new Bacon.Bus();
|
2014-05-18 22:37:56 +04:00
|
|
|
if (process.env.TRAVIS_JOB_NUMBER) {
|
|
|
|
test.capabilities["tunnel-identifier"] = process.env.TRAVIS_JOB_NUMBER;
|
2014-08-26 22:03:54 +04:00
|
|
|
test.capabilities["name"] = process.env.TRAVIS_COMMIT.substring(0, 10);
|
|
|
|
test.capabilities["build"] = process.env.TRAVIS_BUILD_NUMBER;
|
|
|
|
} else {
|
|
|
|
test.capabilities["name"] = "Manual run";
|
2014-05-18 22:37:56 +04:00
|
|
|
}
|
2014-05-18 21:25:51 +04:00
|
|
|
|
2014-05-18 18:39:24 +04:00
|
|
|
var resultStream = Bacon.fromNodeCallback(browser, "init", test.capabilities)
|
|
|
|
.flatMap(Bacon.fromNodeCallback(browser, "setImplicitWaitTimeout", 15000)
|
2014-08-26 22:03:54 +04:00
|
|
|
.flatMap(function() {
|
|
|
|
Bacon.later(0, formatResultName(test.capabilities)).onValue(browserStream.push);
|
|
|
|
return Bacon.fromArray(test.cases).zip(browserStream.take(test.cases.length)).flatMap(function(options) {
|
|
|
|
var testCase = options[0];
|
|
|
|
var name = options[1];
|
|
|
|
console.log(colors.green, "STARTING", name, testCase, colors.clear);
|
|
|
|
return Bacon.fromNodeCallback(browser, "get", "http://localhost:" + port + "/" + testCase + "?selenium")
|
|
|
|
.flatMap(Bacon.combineTemplate({
|
|
|
|
dataUrl: Bacon.fromNodeCallback(browser, "elementByCssSelector", ".html2canvas").flatMap(function(canvas) {
|
|
|
|
return Bacon.fromNodeCallback(browser, "execute", "return arguments[0].toDataURL('image/png').substring(22)", [canvas]);
|
|
|
|
}),
|
|
|
|
screenshot: Bacon.fromNodeCallback(browser, "takeScreenshot")
|
|
|
|
}))
|
|
|
|
.flatMap(function(result) {
|
|
|
|
return Bacon.combineTemplate({
|
|
|
|
browser: name,
|
|
|
|
testCase: testCase,
|
|
|
|
accuracy: Bacon.constant(result.dataUrl).flatMap(getPixelArray).combine(Bacon.constant(result.screenshot).flatMap(getPixelArray), calculateDifference),
|
|
|
|
dataUrl: result.dataUrl,
|
|
|
|
screenshot: result.screenshot
|
2014-05-18 18:39:24 +04:00
|
|
|
});
|
2014-08-26 22:03:54 +04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}));
|
2013-09-07 22:10:06 +04:00
|
|
|
|
2014-05-18 18:39:24 +04:00
|
|
|
resultStream.onError(function(error) {
|
2014-08-26 21:01:15 +04:00
|
|
|
var name = formatResultName(test.capabilities);
|
|
|
|
console.log(colors.red, "ERROR", name, error.message);
|
|
|
|
browserStream.push(name);
|
2014-08-26 22:03:54 +04:00
|
|
|
browser.quit();
|
2013-05-29 18:53:47 +04:00
|
|
|
});
|
2013-09-06 23:30:24 +04:00
|
|
|
|
2014-05-18 18:39:24 +04:00
|
|
|
resultStream.onValue(function(result) {
|
|
|
|
console.log(colors.green, "COMPLETE", result.browser, result.testCase, result.accuracy, "%", colors.clear);
|
|
|
|
browserStream.push(result.browser);
|
2013-09-06 23:30:24 +04:00
|
|
|
});
|
|
|
|
|
2014-05-18 18:39:24 +04:00
|
|
|
resultStream.onEnd(function() {
|
|
|
|
browser.quit();
|
2013-09-06 23:30:24 +04:00
|
|
|
});
|
|
|
|
|
2014-05-18 18:39:24 +04:00
|
|
|
return resultStream.fold([], pushToArray);
|
2013-09-06 23:30:24 +04:00
|
|
|
}
|
|
|
|
|
2014-05-18 18:39:24 +04:00
|
|
|
function createImages(data) {
|
|
|
|
var dataurlFileName = "tests/results/" + data.browser + "-" + data.testCase.replace(/\//g, "-") + "-html2canvas.png";
|
|
|
|
var screenshotFileName = "tests/results/" + data.browser + "-" + data.testCase.replace(/\//g, "-") + "-screencapture.png";
|
|
|
|
return Bacon.combineTemplate({
|
|
|
|
name: data.testCase,
|
|
|
|
dataurl: Bacon.fromNodeCallback(fs.writeFile, dataurlFileName, data.dataUrl, "base64").map(function() {
|
|
|
|
return dataurlFileName;
|
|
|
|
}),
|
|
|
|
screenshot: Bacon.fromNodeCallback(fs.writeFile, screenshotFileName, data.screenshot, "base64").map(function() {
|
|
|
|
return screenshotFileName;
|
|
|
|
})
|
2013-09-06 23:30:24 +04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2014-05-18 18:39:24 +04:00
|
|
|
function pushToArray(array, item) {
|
|
|
|
array.push(item);
|
|
|
|
return array;
|
2013-09-06 23:30:24 +04:00
|
|
|
}
|
|
|
|
|
2014-08-26 21:01:15 +04:00
|
|
|
function runWebDriver(browsers, cases) {
|
2014-05-19 00:31:49 +04:00
|
|
|
var availableBrowsers = new Bacon.Bus();
|
|
|
|
var result = Bacon.combineTemplate({
|
|
|
|
capabilities: Bacon.fromArray(browsers).zip(availableBrowsers.take(browsers.length), function(first) { return first; }),
|
2014-05-18 18:39:24 +04:00
|
|
|
cases: cases
|
2014-05-19 00:31:49 +04:00
|
|
|
}).flatMap(webdriverStream).doAction(function() {
|
|
|
|
availableBrowsers.push("ready");
|
|
|
|
});
|
|
|
|
|
|
|
|
Bacon.fromArray([1, 2, 3, 4]).onValue(availableBrowsers.push);
|
|
|
|
|
|
|
|
return result.fold([], pushToArray);
|
2013-05-29 18:53:47 +04:00
|
|
|
}
|
|
|
|
|
2014-08-26 22:03:54 +04:00
|
|
|
exports.tests = function(browsers, singleTest) {
|
|
|
|
return (singleTest ? Bacon.constant([singleTest]) : getTests("tests/cases").fold([], pushToArray)).flatMap(runWebDriver.bind(null, browsers)).mapError(false);
|
2014-05-18 18:39:24 +04:00
|
|
|
};
|
|
|
|
})();
|