2019-03-20 01:32:05 -04:00
|
|
|
/*eslint no-console: ["error", { allow: ["log", "error"] }] */
|
|
|
|
|
2017-12-19 02:59:41 -05:00
|
|
|
// Chrome QUnit Test Runner
|
|
|
|
// Author: David Taylor
|
|
|
|
// Requires chrome-launcher and chrome-remote-interface from npm
|
|
|
|
// An up-to-date version of chrome is also required
|
2013-06-18 13:44:20 -04:00
|
|
|
|
2017-12-19 02:59:41 -05:00
|
|
|
/* globals Promise */
|
2013-06-18 13:44:20 -04:00
|
|
|
|
2017-12-19 02:59:41 -05:00
|
|
|
var args = process.argv.slice(2);
|
2015-08-25 04:42:19 -04:00
|
|
|
|
2018-04-23 00:41:29 -04:00
|
|
|
if (args.length < 1 || args.length > 3) {
|
|
|
|
console.log("Usage: node run-qunit.js <URL> <timeout> <result_file>");
|
2017-12-19 02:59:41 -05:00
|
|
|
process.exit(1);
|
2013-06-18 13:44:20 -04:00
|
|
|
}
|
|
|
|
|
2018-11-27 01:40:55 -05:00
|
|
|
const chromeLauncher = require("chrome-launcher");
|
|
|
|
const CDP = require("chrome-remote-interface");
|
2013-06-18 13:44:20 -04:00
|
|
|
|
2018-04-23 00:41:29 -04:00
|
|
|
const QUNIT_RESULT = args[2];
|
2018-11-27 01:40:55 -05:00
|
|
|
const fs = require("fs");
|
2018-04-23 00:41:29 -04:00
|
|
|
|
|
|
|
if (QUNIT_RESULT) {
|
|
|
|
(async () => {
|
|
|
|
await fs.stat(QUNIT_RESULT, (err, stats) => {
|
2019-03-20 01:32:05 -04:00
|
|
|
if (stats && stats.isFile())
|
|
|
|
fs.unlink(QUNIT_RESULT, unlinkErr => {
|
|
|
|
if (unlinkErr) {
|
|
|
|
console.log("Error deleting " + QUNIT_RESULT + " " + unlinkErr);
|
|
|
|
}
|
|
|
|
});
|
2018-04-23 00:41:29 -04:00
|
|
|
});
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
async function runAllTests() {
|
2017-12-19 02:59:41 -05:00
|
|
|
function launchChrome() {
|
2018-04-11 22:10:45 -04:00
|
|
|
const options = {
|
2019-07-25 05:38:05 -04:00
|
|
|
chromeFlags: [
|
|
|
|
"--disable-gpu",
|
|
|
|
"--headless",
|
|
|
|
"--no-sandbox",
|
|
|
|
"--disable-dev-shm-usage",
|
|
|
|
"--mute-audio",
|
|
|
|
"--window-size=1440,900"
|
|
|
|
]
|
2018-04-23 00:41:29 -04:00
|
|
|
};
|
2018-04-11 22:10:45 -04:00
|
|
|
|
|
|
|
if (process.env.REMOTE_DEBUG) {
|
|
|
|
options.port = 9222;
|
|
|
|
}
|
|
|
|
|
|
|
|
return chromeLauncher.launch(options);
|
2013-06-18 13:44:20 -04:00
|
|
|
}
|
|
|
|
|
2018-04-23 00:41:29 -04:00
|
|
|
let chrome = await launchChrome();
|
2019-12-12 08:24:58 -05:00
|
|
|
|
|
|
|
let protocol = null;
|
|
|
|
let connectAttempts = 0;
|
|
|
|
while (!protocol) {
|
|
|
|
// Workaround for intermittent CI error caused by
|
|
|
|
// https://github.com/GoogleChrome/chrome-launcher/issues/145
|
|
|
|
try {
|
|
|
|
protocol = await CDP({ port: chrome.port });
|
|
|
|
} catch (e) {
|
2019-12-12 09:06:06 -05:00
|
|
|
if (e.message === "No inspectable targets" && connectAttempts < 50) {
|
2019-12-12 08:24:58 -05:00
|
|
|
connectAttempts++;
|
|
|
|
console.log(
|
|
|
|
"Unable to establish connection to chrome target - trying again..."
|
|
|
|
);
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-04-23 00:41:29 -04:00
|
|
|
|
2019-10-01 16:17:53 -04:00
|
|
|
const { Inspector, Page, Runtime } = protocol;
|
2018-04-23 00:41:29 -04:00
|
|
|
|
2019-10-01 16:17:53 -04:00
|
|
|
await Promise.all([Inspector.enable(), Page.enable(), Runtime.enable()]);
|
2019-05-16 12:00:57 -04:00
|
|
|
|
|
|
|
Inspector.targetCrashed(entry => {
|
|
|
|
console.log("Chrome target crashed:");
|
|
|
|
console.log(entry);
|
|
|
|
});
|
2018-04-23 00:41:29 -04:00
|
|
|
|
2019-05-16 02:48:08 -04:00
|
|
|
Runtime.exceptionThrown(exceptionInfo => {
|
|
|
|
console.log(exceptionInfo.exceptionDetails.exception.description);
|
|
|
|
});
|
|
|
|
|
2018-11-27 01:40:55 -05:00
|
|
|
Runtime.consoleAPICalled(response => {
|
|
|
|
const message = response["args"][0].value;
|
2018-04-23 00:41:29 -04:00
|
|
|
|
2020-08-05 13:43:14 -04:00
|
|
|
// Not finished yet, don't add a newline
|
|
|
|
if (message && message.startsWith && message.startsWith("↪")) {
|
2018-04-23 00:41:29 -04:00
|
|
|
process.stdout.write(message);
|
2018-11-27 01:40:55 -05:00
|
|
|
} else if (
|
|
|
|
message &&
|
|
|
|
message.startsWith &&
|
|
|
|
message.startsWith("AUTOSPEC:")
|
|
|
|
) {
|
2018-04-23 00:41:29 -04:00
|
|
|
fs.appendFileSync(QUNIT_RESULT, `${message.slice(10)}\n`);
|
|
|
|
} else {
|
|
|
|
console.log(message);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
console.log("navigate to " + args[0]);
|
2018-11-27 01:40:55 -05:00
|
|
|
Page.navigate({ url: args[0] });
|
2018-04-23 00:41:29 -04:00
|
|
|
|
2019-05-20 10:35:23 -04:00
|
|
|
Page.loadEventFired(async () => {
|
2020-08-05 16:27:06 -04:00
|
|
|
let qff = process.env.QUNIT_FAIL_FAST;
|
2019-05-20 10:35:23 -04:00
|
|
|
await Runtime.evaluate({
|
2020-08-05 13:43:14 -04:00
|
|
|
expression:
|
|
|
|
`const QUNIT_FAIL_FAST = ` +
|
2020-08-05 16:27:06 -04:00
|
|
|
(qff === "1" || qff === "true").toString() +
|
2020-08-05 13:43:14 -04:00
|
|
|
";"
|
|
|
|
});
|
|
|
|
await Runtime.evaluate({
|
|
|
|
expression: `(${qunit_script})();`
|
2019-05-20 10:35:23 -04:00
|
|
|
});
|
2020-04-02 01:01:38 -04:00
|
|
|
|
|
|
|
if (args[0].indexOf("report_requests=1") > -1) {
|
|
|
|
await Runtime.evaluate({
|
|
|
|
expression: "QUnit.config.logAllRequests = true"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-04-23 00:41:29 -04:00
|
|
|
const timeout = parseInt(args[1] || 300000, 10);
|
|
|
|
var start = Date.now();
|
|
|
|
|
|
|
|
var interval;
|
|
|
|
|
|
|
|
let runTests = async function() {
|
|
|
|
if (Date.now() > start + timeout) {
|
2020-08-07 09:32:57 -04:00
|
|
|
console.error("\n\nTests timed out\n");
|
2018-04-23 00:41:29 -04:00
|
|
|
protocol.close();
|
|
|
|
chrome.kill();
|
|
|
|
process.exit(124);
|
|
|
|
}
|
|
|
|
|
2018-11-27 01:40:55 -05:00
|
|
|
let numFails = await Runtime.evaluate({
|
|
|
|
expression: `(${check_script})()`
|
|
|
|
});
|
2018-04-23 00:41:29 -04:00
|
|
|
|
2018-11-27 01:40:55 -05:00
|
|
|
if (numFails && numFails.result && numFails.result.type !== "undefined") {
|
2018-04-23 00:41:29 -04:00
|
|
|
clearInterval(interval);
|
|
|
|
protocol.close();
|
|
|
|
chrome.kill();
|
|
|
|
|
|
|
|
if (numFails.result.value > 0) {
|
|
|
|
process.exit(1);
|
|
|
|
} else {
|
|
|
|
process.exit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
interval = setInterval(runTests, 250);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-12-11 11:50:31 -05:00
|
|
|
runAllTests().catch(e => {
|
2018-04-23 00:41:29 -04:00
|
|
|
console.log("Failed to run tests: " + e);
|
|
|
|
process.exit(1);
|
2019-12-11 11:50:31 -05:00
|
|
|
});
|
2017-12-19 02:59:41 -05:00
|
|
|
|
|
|
|
// The following functions are converted to strings
|
|
|
|
// And then sent to chrome to be evalaluated
|
2013-06-18 13:44:20 -04:00
|
|
|
function logQUnit() {
|
2020-08-05 13:43:14 -04:00
|
|
|
let testErrors = [];
|
|
|
|
let assertionErrors = [];
|
2013-06-18 13:44:20 -04:00
|
|
|
|
|
|
|
console.log("\nRunning: " + JSON.stringify(QUnit.urlParams) + "\n");
|
|
|
|
|
2016-11-16 05:02:16 -05:00
|
|
|
QUnit.config.testTimeout = 10000;
|
|
|
|
|
2018-04-23 00:41:29 -04:00
|
|
|
let durations = {};
|
|
|
|
|
2020-08-05 13:43:14 -04:00
|
|
|
let inTest = false;
|
2019-06-11 17:14:42 -04:00
|
|
|
QUnit.testStart(function(context) {
|
2020-08-05 13:43:14 -04:00
|
|
|
console.log("↪ " + context.module + "::" + context.name);
|
|
|
|
inTest = true;
|
2019-06-11 17:14:42 -04:00
|
|
|
});
|
|
|
|
|
2013-06-18 13:44:20 -04:00
|
|
|
QUnit.testDone(function(context) {
|
2018-04-23 00:41:29 -04:00
|
|
|
durations[context.module + "::" + context.name] = context.runtime;
|
|
|
|
|
2013-06-18 13:44:20 -04:00
|
|
|
if (context.failed) {
|
2020-08-05 13:43:14 -04:00
|
|
|
const msg =
|
|
|
|
" Test Failed: " +
|
|
|
|
context.name +
|
|
|
|
assertionErrors.join(" ") +
|
|
|
|
"\n" +
|
|
|
|
context.source;
|
2013-06-18 13:44:20 -04:00
|
|
|
testErrors.push(msg);
|
|
|
|
assertionErrors = [];
|
2020-08-05 13:43:14 -04:00
|
|
|
|
|
|
|
// Pass QUNIT_FAIL_FAST on the command line to quit after the first failure
|
|
|
|
if (QUNIT_FAIL_FAST) {
|
|
|
|
QUnit.config.queue.length = 0;
|
|
|
|
}
|
|
|
|
if (inTest) {
|
|
|
|
console.log(" [✘]");
|
|
|
|
}
|
2013-06-18 13:44:20 -04:00
|
|
|
} else {
|
2020-08-05 13:43:14 -04:00
|
|
|
if (inTest) {
|
|
|
|
console.log(" [✔]");
|
|
|
|
}
|
2013-06-18 13:44:20 -04:00
|
|
|
}
|
2020-08-05 13:43:14 -04:00
|
|
|
inTest = false;
|
2013-06-18 13:44:20 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
QUnit.log(function(context) {
|
2018-11-27 01:40:55 -05:00
|
|
|
if (context.result) {
|
|
|
|
return;
|
|
|
|
}
|
2013-06-18 13:44:20 -04:00
|
|
|
|
|
|
|
var msg = "\n Assertion Failed:";
|
|
|
|
if (context.message) {
|
|
|
|
msg += " " + context.message;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (context.expected) {
|
2018-11-27 01:40:55 -05:00
|
|
|
msg +=
|
|
|
|
"\n Expected: " + context.expected + ", Actual: " + context.actual;
|
2013-06-18 13:44:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
assertionErrors.push(msg);
|
|
|
|
});
|
|
|
|
|
|
|
|
QUnit.done(function(context) {
|
2015-08-25 04:42:19 -04:00
|
|
|
console.log("\n");
|
2013-06-18 13:44:20 -04:00
|
|
|
|
2020-08-05 13:43:14 -04:00
|
|
|
if (testErrors.length) {
|
|
|
|
console.log("Test Errors");
|
|
|
|
console.log("----------------------------------------------");
|
|
|
|
testErrors.forEach(e => {
|
|
|
|
console.error(e);
|
|
|
|
});
|
|
|
|
console.log("\n");
|
2013-06-18 13:44:20 -04:00
|
|
|
}
|
|
|
|
|
2018-04-23 00:41:29 -04:00
|
|
|
console.log("Slowest tests");
|
|
|
|
console.log("----------------------------------------------");
|
2018-11-27 01:40:55 -05:00
|
|
|
let ary = Object.keys(durations).map(key => ({
|
|
|
|
key: key,
|
|
|
|
value: durations[key]
|
|
|
|
}));
|
|
|
|
ary.sort((p1, p2) => p2.value - p1.value);
|
2018-04-23 00:41:29 -04:00
|
|
|
ary.slice(0, 30).forEach(pair => {
|
|
|
|
console.log(pair.key + ": " + pair.value + "ms");
|
|
|
|
});
|
|
|
|
|
2013-06-18 13:44:20 -04:00
|
|
|
var stats = [
|
|
|
|
"Time: " + context.runtime + "ms",
|
|
|
|
"Total: " + context.total,
|
|
|
|
"Passed: " + context.passed,
|
|
|
|
"Failed: " + context.failed
|
|
|
|
];
|
|
|
|
console.log(stats.join(", "));
|
2018-04-23 00:41:29 -04:00
|
|
|
|
2020-08-05 12:20:28 -04:00
|
|
|
if (context.failed) {
|
|
|
|
console.log("\nUse this filter to run in the same order:");
|
2020-08-05 13:43:14 -04:00
|
|
|
console.log(
|
2020-08-05 16:27:06 -04:00
|
|
|
"QUNIT_FAIL_FAST=1 QUNIT_SEED=" +
|
2020-08-05 13:43:14 -04:00
|
|
|
QUnit.config.seed +
|
|
|
|
" rake qunit:test\n"
|
|
|
|
);
|
2020-08-05 12:20:28 -04:00
|
|
|
console.log("If you have a web environment running, you can visit:");
|
|
|
|
console.log(
|
|
|
|
"http://localhost:3000/qunit?hidepassed&seed=" +
|
|
|
|
QUnit.config.seed +
|
|
|
|
"\n\n"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-06-18 13:44:20 -04:00
|
|
|
window.qunitDone = context;
|
|
|
|
});
|
|
|
|
}
|
2018-04-23 00:41:29 -04:00
|
|
|
let qunit_script = logQUnit.toString();
|
|
|
|
|
|
|
|
if (QUNIT_RESULT) {
|
2018-11-27 01:40:55 -05:00
|
|
|
qunit_script = qunit_script.replace(
|
|
|
|
"/* QUNIT_RESULT */",
|
|
|
|
"console.log(`AUTOSPEC: ${context.module}:::${context.testId}:::${context.name}`);"
|
|
|
|
);
|
2018-04-23 00:41:29 -04:00
|
|
|
}
|
2017-12-19 02:59:41 -05:00
|
|
|
|
|
|
|
function check() {
|
2018-11-27 01:40:55 -05:00
|
|
|
if (window.qunitDone) {
|
2017-12-19 02:59:41 -05:00
|
|
|
return window.qunitDone.failed;
|
|
|
|
}
|
|
|
|
}
|
2018-04-23 00:41:29 -04:00
|
|
|
|
2017-12-19 02:59:41 -05:00
|
|
|
const check_script = check.toString();
|