From 1c43e891f7b86045f84ff7bae968cc046045bbab Mon Sep 17 00:00:00 2001 From: John Bouchard Date: Tue, 22 Sep 2020 18:46:36 -0700 Subject: [PATCH 1/6] initial-implmentation --- package.json | 2 +- src/cli.js | 11 ++- src/reporters/page/HarCaptureEngine.js | 105 +++++++++++++++++++++++++ src/reporters/page/reporter.js | 103 ++++++++++++++++++++++++ 4 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 src/reporters/page/HarCaptureEngine.js create mode 100644 src/reporters/page/reporter.js diff --git a/package.json b/package.json index 4bb8e3d..a73a1c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "testarmada-magellan", - "version": "11.0.15", + "version": "11.0.16", "description": "Massively parallel automated testing", "main": "src/main", "directories": { diff --git a/src/cli.js b/src/cli.js index 59075bd..135586a 100644 --- a/src/cli.js +++ b/src/cli.js @@ -29,6 +29,9 @@ const logger = require("./logger"); const BailStrategy = require("./strategies/bail"); const ResourceStrategy = require("./strategies/resource"); +const PageReporter = require("./reporters/page/reporter"); +const SerialReporter = require("./reporters/stdout/reporter"); + module.exports = { initialize() { @@ -304,10 +307,16 @@ module.exports = { // Serial Mode Reporter (enabled with --serial) // if (opts.argv.serial) { - const SerialReporter = require("./reporters/stdout/reporter"); listeners.push(new SerialReporter()); } + // + // Serial Mode Reporter (enabled with --serial) + // + if (opts.argv.pageDebugger) { + listeners.push(new PageReporter()); + } + // intiialize listeners return new Promise((resolve, reject) => { async.each(listeners, (listener, done) => { diff --git a/src/reporters/page/HarCaptureEngine.js b/src/reporters/page/HarCaptureEngine.js new file mode 100644 index 0000000..2d09db8 --- /dev/null +++ b/src/reporters/page/HarCaptureEngine.js @@ -0,0 +1,105 @@ +"use strict"; + +const getPort = require("get-port"); +const {spawn} = require("child_process"); +const CHC = require("chrome-har-capturer"); +const logger = require("../../logger"); + +const CHROME_CMD = { + MAC: "\"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\"", + LINUX: "google-chrome" +}; + +const CAPTURE_TIMEOUT = 10000; // 10 seconds + +class HarCaptureEngine { + + async start() { + if (!this.chromeProcess) { + const portRange = []; + const maxPort = 9999; + let i = 9223; + while (i <= maxPort) { + portRange.push(i++); + } + this.port = await getPort({port: portRange}); + const args = [ + "--headless", + "--crash-dumps-dir=/tmp", + "--disable-gpu", + `--remote-debugging-port=${this.port}` + ]; + const isMac = process.platform === "darwin"; + const cmd = isMac ? CHROME_CMD.MAC : CHROME_CMD.LINUX; + this.chromeProcess = spawn(cmd, args, {shell: true}); + this.chromeProcess.stdout.on("data", (data) => { + data = data.toString().trim(); + if (data.length) { + logger.log(`HarCaptureEngine.chromeProcess.stdout: ${data}`); + } + }); + this.chromeProcess.stderr.on("data", (data) => { + data = data.toString().trim(); + if (data.length) { + logger.log(`HarCaptureEngine.chromeProcess.stderr: ${data}`); + } + }); + this.chromeProcess.on("error", err => { + logger.err(`HarCaptureEngine.chromeProcess.error: ${err}`); + }); + this.cache = {}; + } + } + + getCacheKey(url) { + return url.toString("base64") + .split("=").join("_") + .split("+").join("-") + .split("/").join("$"); + } + + async capture(url, onHar, onFail) { + const cacheKey = this.getCacheKey(url); + if (this.cache[cacheKey]) { + onHar(this.cache[cacheKey]); + } + CHC.run([url], { + port: this.port, + timeout: CAPTURE_TIMEOUT + }) + .on("fail", onFail) // onFail(url, err) + .on("har", har => { + this.cache[cacheKey] = har; + onHar(this.cache[cacheKey]); + }); + } + + stop() { + return new Promise((resolve, reject) => { + if (this.chromeProcess) { + this.chromeProcess.on("exit", (code, signal) => { + // eslint-disable-next-line no-sequences + logger.log(`HarCaptureEngine.chromeProcess.exit: ${code, signal}`); + resolve(); + }); + this.chromeProcess.on("close", (code, signal) => { + // eslint-disable-next-line no-sequences + logger.log(`HarCaptureEngine.chromeProcess.close: ${code, signal}`); + resolve(); + }); + this.chromeProcess.on("disconnect", () => { + logger.err("HarCaptureEngine.chromeProcess.disconnect"); + resolve(); + }); + this.chromeProcess.on("error", (err) => reject(err)); + this.chromeProcess.kill("SIGINT"); + this.chromeProcess = undefined; + } else { + resolve(); + } + }); + } + +} + +module.exports = HarCaptureEngine; diff --git a/src/reporters/page/reporter.js b/src/reporters/page/reporter.js new file mode 100644 index 0000000..a5f1b4d --- /dev/null +++ b/src/reporters/page/reporter.js @@ -0,0 +1,103 @@ +"use strict"; + +const StreamSlicer = require("stream-slic3r"); +const { Transform } = require("stream"); +const logger = require("../../logger"); +const HarCaptureEngine = require("./HarCaptureEngine"); + +/* +* Stdout Reporter +* +* This reporter streams the output from a test run directly to stdout/stderr, to allow +* for easier live debugging at the console. +*/ + +const BaseReporter = require("../reporter"); + +const logHarEntryToScreen = (entry, url) => { + // to make things easier to read ie taking less screen space... + // reduce the querystring and headers to find on justa few lines + const reducer = (result, item) => { + result.push(`${item.name} = ${item.value}`); + return result; + }; + entry.request.queryString = entry.request.queryString.reduce(reducer, []); + entry.request.headers = entry.request.headers.reduce(reducer, []); + entry.response.headers = entry.response.headers.reduce(reducer, []); + // report the error to screeen to help user debug + const tag = "[PageReporter]"; + /* eslint-disable no-console, no-magic-numbers */ + console.log(); + console.log(tag, "============== !!! FAILED_REQUEST_FOUND_ON_PAGE !!! =============="); + console.log(tag, "== PAGE_URL:", url); + console.log(tag, "== FAILED_REQUEST:\n", JSON.stringify(entry.request, null, 2)); + console.log(tag, "== FAILED_RESPONSE:\n", JSON.stringify(entry.response, null, 2)); + console.log(tag, "=================================================================="); + console.log(); + /* eslint-enable no-console, no-magic-numbers */ +}; + +const onHarEntry = (entry, url) => { + if (entry.response && entry.response.status) { + // if the response is in the 400/500 range then we log it to the screen + const failedCodeBegin = 400; + const failedCodeEnd = 600; + if (entry.response.status >= failedCodeBegin && entry.response.status < failedCodeEnd) { + logHarEntryToScreen(entry, url); + } + } +}; + +class Reporter extends BaseReporter { + + async initialize() { + if (!this.harCaptureEngine) { + this.harCaptureEngine = new HarCaptureEngine(); + await this.harCaptureEngine.start(); + } + } + + async flush() { + if (this.harCaptureEngine) { + await this.harCaptureEngine.stop(); + } + } + + listenTo(testRun, test, source) { + if (source.infoSlicer) { + const sliceOn = "\"url\":\""; + const urlSlicer = new StreamSlicer(sliceOn); + const urlParser = new Transform({ + transform(data, encoding, callback) { + data = data.toString(); + // StreamSlicer has no data loss, + // the first slice might not contain any url + // thus we have ths check + if (data.startsWith(sliceOn)) { + const endIndex = data.indexOf("\"", sliceOn.length + 1); + const url = data.substring(sliceOn.length, endIndex); + this.push(url); + } + callback(); + } + }); + const harCaptureEngine = this.harCaptureEngine; + const urlHandler = new Transform({ + transform(data, encoding, callback) { + const _url = data.toString(); + const onHar = har => har.log.entries.forEach(entry => onHarEntry(entry, _url)); + // eslint-disable-next-line no-sequences + const onFail = (url, err) => logger.err(`HarCaptureEngine.capture.onFail: ${url, err}`); + harCaptureEngine.capture(_url, onHar, onFail); + callback(); + } + }); + source.infoSlicer.pipe(urlSlicer).pipe(urlParser).pipe(urlHandler); + } else { + logger.err("PageReporder: source.infoSlicer does not exist!"); + } + } + +} + +module.exports = Reporter; From 8952fba643ec531ceee50958e3ea606bd4112ef1 Mon Sep 17 00:00:00 2001 From: John Bouchard Date: Tue, 22 Sep 2020 18:48:33 -0700 Subject: [PATCH 2/6] added-required-dependencies --- package-lock.json | 75 ++++++++++++++++++++++++++++++++++++++--------- package.json | 2 ++ 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98084b0..a8bacbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "testarmada-magellan", - "version": "11.0.14", + "version": "11.0.16", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -202,8 +202,7 @@ "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, "anymatch": { "version": "2.0.0", @@ -601,8 +600,7 @@ "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, "asynckit": { "version": "0.4.0", @@ -1090,7 +1088,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -1105,6 +1102,47 @@ "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", "dev": true }, + "chrome-har-capturer": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/chrome-har-capturer/-/chrome-har-capturer-0.13.8.tgz", + "integrity": "sha512-ZkUbpWp29/oDXdjY0TIEjSSrfaCpKEziDFYnRVur5JXtYLwKeuribcUaHTrIiqZzdihPRZVafodv0BO0xujOyg==", + "requires": { + "chalk": "1.x.x", + "chrome-remote-interface": "^0.25.7", + "commander": "2.x.x" + } + }, + "chrome-remote-interface": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.25.7.tgz", + "integrity": "sha512-6zI6LbR2IiGmduFZededaerEr9hHXabxT/L+fRrdq65a0CfyLMzpq0BKuZiqN0Upqcacsb6q2POj7fmobwBsEA==", + "requires": { + "commander": "2.11.x", + "ws": "3.3.x" + }, + "dependencies": { + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } + } + }, "ci-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", @@ -1623,8 +1661,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.8.1", @@ -2250,9 +2287,9 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==" }, "get-stream": { "version": "3.0.0", @@ -2371,7 +2408,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6309,8 +6345,7 @@ "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, "symbol-tree": { "version": "3.2.4", @@ -6328,6 +6363,13 @@ "get-port": "^3.1.0", "http-response-object": "^1.1.0", "then-request": "^2.2.0" + }, + "dependencies": { + "get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" + } } }, "table": { @@ -6740,6 +6782,11 @@ "dev": true, "optional": true }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", diff --git a/package.json b/package.json index a73a1c4..e2eac91 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,11 @@ }, "dependencies": { "async": "^2.1.4", + "chrome-har-capturer": "^0.13.8", "cli-color": "^1.1.0", "co": "^4.6.0", "fs-extra": "^7.0.1", + "get-port": "^5.1.1", "glob": "^7.1.1", "http-proxy": "^1.18.1", "lodash": "^4.6.1", From 31d5a0e0223b8062f12252cd87d941ecb356c5ce Mon Sep 17 00:00:00 2001 From: John Bouchard Date: Tue, 22 Sep 2020 18:56:27 -0700 Subject: [PATCH 3/6] fix-comments --- src/cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli.js b/src/cli.js index 135586a..a85ef89 100644 --- a/src/cli.js +++ b/src/cli.js @@ -311,7 +311,7 @@ module.exports = { } // - // Serial Mode Reporter (enabled with --serial) + // Page Reporter to help find pages with errors (enabled with --pageDebugger) // if (opts.argv.pageDebugger) { listeners.push(new PageReporter()); From 87cff072d13d275787af1d795ef9a1fbbaba7c47 Mon Sep 17 00:00:00 2001 From: John Bouchard Date: Tue, 22 Sep 2020 18:58:48 -0700 Subject: [PATCH 4/6] fix-comments --- src/reporters/page/reporter.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/reporters/page/reporter.js b/src/reporters/page/reporter.js index a5f1b4d..1b034e5 100644 --- a/src/reporters/page/reporter.js +++ b/src/reporters/page/reporter.js @@ -6,10 +6,9 @@ const logger = require("../../logger"); const HarCaptureEngine = require("./HarCaptureEngine"); /* -* Stdout Reporter +* Page Reporter * -* This reporter streams the output from a test run directly to stdout/stderr, to allow -* for easier live debugging at the console. +* reports any pages with failed requests to the stdout */ const BaseReporter = require("../reporter"); From 31be17673e4de9f58151e5f07b623be4cfc68a01 Mon Sep 17 00:00:00 2001 From: John Bouchard Date: Tue, 22 Sep 2020 19:00:10 -0700 Subject: [PATCH 5/6] fix-typo --- src/reporters/page/reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reporters/page/reporter.js b/src/reporters/page/reporter.js index 1b034e5..c69323a 100644 --- a/src/reporters/page/reporter.js +++ b/src/reporters/page/reporter.js @@ -71,7 +71,7 @@ class Reporter extends BaseReporter { data = data.toString(); // StreamSlicer has no data loss, // the first slice might not contain any url - // thus we have ths check + // thus we have this check if (data.startsWith(sliceOn)) { const endIndex = data.indexOf("\"", sliceOn.length + 1); const url = data.substring(sliceOn.length, endIndex); From 78eec6150a35d4d531500358b7d6865585638d40 Mon Sep 17 00:00:00 2001 From: John Bouchard Date: Tue, 22 Sep 2020 19:03:23 -0700 Subject: [PATCH 6/6] fix-comments --- src/reporters/page/reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reporters/page/reporter.js b/src/reporters/page/reporter.js index c69323a..e8115a1 100644 --- a/src/reporters/page/reporter.js +++ b/src/reporters/page/reporter.js @@ -15,7 +15,7 @@ const BaseReporter = require("../reporter"); const logHarEntryToScreen = (entry, url) => { // to make things easier to read ie taking less screen space... - // reduce the querystring and headers to find on justa few lines + // reduce the querystring and headers usew fewer lines const reducer = (result, item) => { result.push(`${item.name} = ${item.value}`); return result;