Files
KaTeX/dockers/Screenshotter/screenshotter.js
Martin von Gagern 9752d02748 Automatically start a development server
This avoids one of the few requirements we have left: you no longer have to
start a KaTeX development server, the script will do it for you, using a
random port number.

To reproduce the old behaviour, explicitely state --katex-port=7936.
2015-07-15 15:41:29 +02:00

292 lines
8.5 KiB
JavaScript

"use strict";
var childProcess = require("child_process");
var fs = require("fs");
var http = require("http");
var path = require("path");
var net = require("net");
var selenium = require("selenium-webdriver");
var app = require("../../server");
var data = require("../../test/screenshotter/ss_data.json");
var dstDir = path.normalize(
path.join(__dirname, "..", "..", "test", "screenshotter", "images"));
//////////////////////////////////////////////////////////////////////
// Process command line arguments
var opts = require("nomnom")
.option("browser", {
abbr: "b",
"default": "firefox",
help: "Name of the browser to use"
})
.option("container", {
abbr: "c",
type: "string",
help: "Name or ID of a running docker container to contact"
})
.option("seleniumURL", {
full: "selenium-url",
help: "Full URL of the Selenium web driver"
})
.option("seleniumIP", {
full: "selenium-ip",
help: "IP address of the Selenium web driver"
})
.option("seleniumPort", {
full: "selenium-port",
"default": 4444,
help: "Port number of the Selenium web driver"
})
.option("katexURL", {
full: "katex-url",
help: "Full URL of the KaTeX development server"
})
.option("katexIP", {
full: "katex-ip",
"default": "localhost",
help: "Full URL of the KaTeX development server"
})
.option("katexPort", {
full: "katex-port",
help: "Port number of the KaTeX development server"
})
.option("include", {
abbr: "i",
help: "Comma-separated list of test cases to process"
})
.option("exclude", {
abbr: "x",
help: "Comma-separated list of test cases to exclude"
})
.parse();
var listOfCases;
if (opts.include) {
listOfCases = opts.include.split(",");
} else {
listOfCases = Object.keys(data);
}
if (opts.exclude) {
var exclude = opts.exclude.split(",");
listOfCases = listOfCases.filter(function(key) {
return exclude.indexOf(key) === -1;
});
}
var seleniumURL = opts.seleniumURL;
var seleniumIP = opts.seleniumIP;
var seleniumPort = opts.seleniumPort;
var katexURL = opts.katexURL;
var katexIP = opts.katexIP;
var katexPort = opts.katexPort;
//////////////////////////////////////////////////////////////////////
// Work out connection to selenium docker container
function check(err) {
if (!err) {
return;
}
console.error(err);
console.error(err.stack);
process.exit(1);
}
function dockerCmd() {
var args = Array.prototype.slice.call(arguments);
return childProcess.execFileSync(
"docker", args, { encoding: "utf-8" }).replace(/\n$/, "");
}
if (!seleniumURL && opts.container) {
try {
// When using boot2docker, seleniumIP and katexIP are distinct.
seleniumIP = childProcess.execFileSync(
"boot2docker", ["ip"], { encoding: "utf-8" }).replace(/\n$/, "");
var config = childProcess.execFileSync(
"boot2docker", ["config"], { encoding: "utf-8" });
config = (/^HostIP = "(.*)"$/m).exec(config);
if (!config) {
console.error("Failed to find HostIP");
process.exit(2);
}
katexIP = config[1];
} catch(e) {
seleniumIP = katexIP = dockerCmd(
"inspect", "-f", "{{.NetworkSettings.Gateway}}", opts.container);
}
seleniumPort = dockerCmd("port", opts.container, seleniumPort);
seleniumPort = seleniumPort.replace(/^.*:/, "");
}
if (!seleniumURL && seleniumIP) {
seleniumURL = "http://" + seleniumIP + ":" + seleniumPort + "/wd/hub";
}
if (seleniumURL) {
console.log("Selenium driver at " + seleniumURL);
} else {
console.log("Selenium driver in local session");
}
var toStrip = "http://localhost:7936/"; // remove this from testcase URLs
process.nextTick(startServer);
var attempts = 0;
//////////////////////////////////////////////////////////////////////
// Start up development server
var devServer = null;
var minPort = 32768;
var maxPort = 61000;
function startServer() {
if (katexURL || katexPort) {
process.nextTick(tryConnect);
return;
}
var port = Math.floor(Math.random() * (maxPort - minPort)) + minPort;
var server = http.createServer(app).listen(port);
server.once("listening", function() {
devServer = server;
katexPort = port;
attempts = 0;
process.nextTick(tryConnect);
});
server.on("error", function(err) {
if (devServer !== null) { // error after we started listening
throw err;
} else if (++attempts > 50) {
throw new Error("Failed to start up dev server");
} else {
process.nextTick(startServer);
}
});
}
//////////////////////////////////////////////////////////////////////
// Wait for container to become ready
function tryConnect() {
if (!katexURL) {
katexURL = "http://" + katexIP + ":" + katexPort + "/";
console.log("KaTeX URL is " + katexURL);
}
if (!seleniumIP) {
process.nextTick(buildDriver);
return;
}
var sock = net.connect({
host: seleniumIP,
port: +seleniumPort
});
sock.on("connect", function() {
sock.end();
attempts = 0;
process.nextTick(buildDriver);
}).on("error", function() {
if (++attempts > 50) {
throw new Error("Failed to connect selenium server.");
}
setTimeout(tryConnect, 200);
});
}
//////////////////////////////////////////////////////////////////////
// Build the web driver
var driver;
function buildDriver() {
var builder = new selenium.Builder().forBrowser(opts.browser);
if (seleniumURL) {
builder.usingServer(seleniumURL);
}
driver = builder.build();
setSize(targetW, targetH);
}
//////////////////////////////////////////////////////////////////////
// Set the screen size
var targetW = 1024, targetH = 768;
function setSize(reqW, reqH) {
return driver.manage().window().setSize(reqW, reqH).then(function() {
return driver.takeScreenshot();
}).then(function(img) {
img = imageDimensions(img);
var actualW = img.width;
var actualH = img.height;
if (actualW === targetW && actualH === targetH) {
process.nextTick(takeScreenshots);
return;
}
if (++attempts > 5) {
throw new Error("Failed to set window size correctly.");
}
return setSize(targetW + reqW - actualW, targetH + reqH - actualH);
}, check);
}
function imageDimensions(img) {
var buf = new Buffer(img, "base64");
return {
buf: buf,
width: buf.readUInt32BE(16),
height: buf.readUInt32BE(20)
};
}
//////////////////////////////////////////////////////////////////////
// Take the screenshots
var countdown = listOfCases.length;
function takeScreenshots() {
listOfCases.forEach(takeScreenshot);
}
function takeScreenshot(key) {
var url = data[key];
if (!url) {
console.error("Test case " + key + " not known!");
return;
}
url = katexURL + url.substr(toStrip.length);
driver.get(url);
driver.takeScreenshot().then(function haveScreenshot(img) {
img = imageDimensions(img);
if (img.width !== targetW || img.height !== targetH) {
throw new Error("Excpected " + targetW + " x " + targetH +
", got " + img.width + "x" + img.height);
}
if (key === "Lap" && opts.browser === "firefox" &&
img.buf[0x32] === 0xf8) {
/* There is some strange non-determinism with this case,
* causing slight vertical shifts. The first difference
* is at offset 0x32, where one file has byte 0xf8 and
* the other has something else. By using a different
* output file name for one of these cases, we accept both.
*/
key += "_alt";
}
var file = path.join(dstDir, key + "-" + opts.browser + ".png");
var deferred = new selenium.promise.Deferred();
fs.writeFile(file, img.buf, function(err) {
if (err) {
deferred.reject(err);
} else {
deferred.fulfill();
}
});
return deferred.promise;
}).then(function() {
console.log(key);
if (--countdown === 0) {
// devServer.close(cb) will take too long.
process.exit(0);
}
}, check);
}