From af488e65ff08d09898b97aa982ee318547ef5ac3 Mon Sep 17 00:00:00 2001 From: Jeff McRiffey Date: Thu, 17 Apr 2014 13:35:07 -0400 Subject: [PATCH 001/162] count failed specs --- lib/_patch/jasmine2-plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_patch/jasmine2-plugin.js b/lib/_patch/jasmine2-plugin.js index 852d5d0..a716cdc 100644 --- a/lib/_patch/jasmine2-plugin.js +++ b/lib/_patch/jasmine2-plugin.js @@ -14,7 +14,7 @@ results.passed++; } else { results.tracebacks.push(specs[spec].description); - results.failed = true; + results.failed++; } } From 8f936a400bfc276fd5c20ed34042afb87fb3f1ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Wed, 17 Sep 2014 18:02:35 +0200 Subject: [PATCH 002/162] Fix worker reuse --- bin/cli.js | 20 +++++++------------- lib/config.js | 2 ++ lib/server.js | 22 ++++++++++------------ package.json | 2 +- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 97b89bf..769c0d9 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -32,7 +32,6 @@ var Log = require('../lib/logger'), timeout, activityTimeout, workers = {}, - browserToWorker = {}, workerKeys = {}, logLevel, tunnelingAgent, @@ -105,7 +104,7 @@ function cleanUpAndExit(signal, status) { function getTestBrowserInfo(browserString, path) { var info = browserString; - if(config.multipleTest) { + if (config.multipleTest) { info += ", " + path; } return info; @@ -146,7 +145,7 @@ function launchBrowser(browser, path) { } timeout = parseInt(config.timeout); - if(! isNaN(timeout)) { + if (!isNaN(timeout)) { browser.timeout = timeout; } else { timeout = 300; @@ -167,6 +166,7 @@ function launchBrowser(browser, path) { worker.config = browser; worker.string = browserString; worker.test_path = path; + worker.path_index = 0; workers[key] = worker; workerKeys[worker.id] = {key: key, marked: false}; }); @@ -175,15 +175,9 @@ function launchBrowser(browser, path) { function launchBrowsers(config, browser) { setTimeout(function () { - if(Object.prototype.toString.call(config.test_path) === '[object Array]'){ + if (Object.prototype.toString.call(config.test_path) === '[object Array]'){ config.multipleTest = config.test_path.length > 1? true : false; - if(config.reuseWorker) { - launchBrowser(browser, config.test_path[0]); - } else { - config.test_path.forEach(function(path){ - launchBrowser(browser, path); - }); - } + launchBrowser(browser, config.test_path[0]); } else { config.multipleTest = false; launchBrowser(browser, config.test_path); @@ -282,8 +276,8 @@ function runTests() { launchServer(); tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, function () { statusPoller.start(); - var total_workers = config.browsers.length * (Object.prototype.toString.call(config.test_path) === '[object Array]' ? config.test_path.length : 1); - logger.info("Launching " + total_workers + " workers"); + var total_runs = config.browsers.length * (Object.prototype.toString.call(config.test_path) === '[object Array]' ? config.test_path.length : 1); + logger.info("Launching " + config.browsers.length + " worker(s) for " + total_runs + " run(s)."); browsers.forEach(function(browser) { if (browser.browser_version === "latest") { logger.debug("[%s] Finding version.", utils.browserString(browser)); diff --git a/lib/config.js b/lib/config.js index 06115a0..7ab2fb7 100644 --- a/lib/config.js +++ b/lib/config.js @@ -49,6 +49,8 @@ var commit_id = process.env.TRAVIS_COMMIT; if (commit_id) { config.build = "Commit-" + commit_id.slice(0, commit_id.length / 2); +} { + config.build = "Local run, " + new Date().toISOString(); } ['username', 'key', 'test_path', 'browsers'].forEach(function(param) { diff --git a/lib/server.js b/lib/server.js index 6603f7d..2c38ca1 100644 --- a/lib/server.js +++ b/lib/server.js @@ -158,11 +158,12 @@ exports.Server = function Server(bsClient, workers) { } function checkAndTerminateWorker(worker, callback) { - if(config.reuseWorker && config.multipleTest) { - var next_path = getNextTestPath(worker.test_path); + var next_path = getNextTestPath(worker); + if (next_path) { var new_url = 'http://localhost:' + 8888 + '/' + next_path - + "_worker_key=" + worker._worker_key + "&_browser_string=" + getTestBrowserInfo(worker) ; - bsClient.postNewUrl(worker.id, {url: new_url}, function() { + + "?_worker_key=" + worker._worker_key + "&_browser_string=" + getTestBrowserInfo(worker) ; + worker.test_path = next_path; + bsClient.changeUrl(worker.id, {url: new_url}, function() { callback(true); }); } else { @@ -170,14 +171,11 @@ exports.Server = function Server(bsClient, workers) { } }; - function getNextTestPath(test_path) { - // Someday I'll die for codes like these - for(var i = 0; i < config.test_path.length; i++) { - if(config.test_path[i] == test_path) { - return config.test_path[ i + 1 ]; - } + function getNextTestPath(worker) { + if (!config.multipleTest) { + return null; } - + return config.test_path[ ++worker.path_index ]; } handlers = { @@ -234,7 +232,7 @@ exports.Server = function Server(bsClient, workers) { return; } - if(reusedWorker) { + if (reusedWorker) { logger.debug('[%s] Reused', getTestBrowserInfo(worker)); return; } diff --git a/package.json b/package.json index d49b4c3..0aaf7e8 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "url": "https://github.com/browserstack/browserstack-runner.git" }, "dependencies": { - "browserstack": "1.0.1", + "browserstack": "1.1.0", "chalk": "0.4.0", "tunnel": "0.0.3" }, From 79f8acc8a023560b668e1b62a6c346640f7db9d2 Mon Sep 17 00:00:00 2001 From: Rahul Nawani Date: Wed, 1 Oct 2014 19:21:25 +0530 Subject: [PATCH 003/162] bump version to 0.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0aaf7e8..40b9258 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.1.14", + "version": "0.2.0", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From ec3c4fad7c9dbc14fcb73f52ad1923c2d347aaed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Thu, 16 Oct 2014 18:43:31 +0200 Subject: [PATCH 004/162] Server: Fix query string concatenation If the path already contains the question mark starting the query string, use an ampersand instead. --- lib/server.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/server.js b/lib/server.js index 2c38ca1..d1a9cb3 100644 --- a/lib/server.js +++ b/lib/server.js @@ -160,10 +160,15 @@ exports.Server = function Server(bsClient, workers) { function checkAndTerminateWorker(worker, callback) { var next_path = getNextTestPath(worker); if (next_path) { - var new_url = 'http://localhost:' + 8888 + '/' + next_path - + "?_worker_key=" + worker._worker_key + "&_browser_string=" + getTestBrowserInfo(worker) ; + var url = 'http://localhost:' + 8888 + '/' + next_path; + if (url.indexOf('?') > 0) { + url += '&'; + } else { + url += '?'; + } + url += "_worker_key=" + worker._worker_key + "&_browser_string=" + getTestBrowserInfo(worker) ; worker.test_path = next_path; - bsClient.changeUrl(worker.id, {url: new_url}, function() { + bsClient.changeUrl(worker.id, {url: url}, function() { callback(true); }); } else { From c27a2adb88c02c72842dadb2b6fe4cce17137e28 Mon Sep 17 00:00:00 2001 From: Dhimil Gosalia Date: Sat, 18 Oct 2014 13:04:28 +0530 Subject: [PATCH 005/162] On linux, EACCES open ... BrowserStackLocal error. Fixes #91 --- lib/local.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/local.js b/lib/local.js index 9b08830..9ec3ed1 100644 --- a/lib/local.js +++ b/lib/local.js @@ -4,7 +4,7 @@ var Log = require('./logger'), fs = require('fs'), http = require('http'), windows = ((process.platform.match(/win32/) || process.platform.match(/win64/)) !== null), - localBinary = __dirname + (windows ? '/BrowserStackTunnel.jar' : '/BrowserStackLocal'), + localBinary = process.cwd() + (windows ? '/BrowserStackTunnel.jar' : '/BrowserStackLocal'), utils = require('./utils'), config = require('./config'); From 3944f858d49af3da77c197265d2a518014ea76e2 Mon Sep 17 00:00:00 2001 From: Nakul Date: Sat, 18 Oct 2014 14:24:17 +0530 Subject: [PATCH 006/162] for some reason, local fails intermittently. this fixes it. --- lib/local.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/local.js b/lib/local.js index 9b08830..71d7b25 100644 --- a/lib/local.js +++ b/lib/local.js @@ -56,7 +56,9 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback, err) { if (data.indexOf(runMatcher) >= 0) { running = true; logger.debug("[%s] Tunnel launched", new Date()); - callback(); + setTimeout(function(){ + callback(); + }, 2000); } }); From 743564ea785e39611fe6c22022aaee69dab3af93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Fri, 17 Oct 2014 12:11:43 +0200 Subject: [PATCH 007/162] Server/CLI: Optimize console output Only log screenshot URLs for failed tests, still always create them, can always be accessed on browserstack.com/automate. Degrade the "Exiting" logs to debug, not needed in normal operations. Update logs for finished workers to use less color, slightly more compact format. Fix bug in formatTraceback, where a falsy actual or expected value would cause nothing to be printed. Updates tests/test.rby to match the new finished console output. --- bin/cli.js | 4 ++-- lib/server.js | 17 +++++++++-------- tests/test.rb | 14 +++++++------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 769c0d9..e4541c9 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -92,11 +92,11 @@ function cleanUpAndExit(signal, status) { } if (signal == 'SIGTERM') { - logger.info("Exiting"); + logger.debug("Exiting"); process.exit(status); } else { terminateAllWorkers(function() { - logger.info("Exiting"); + logger.debug("Exiting"); process.exit(1); }); } diff --git a/lib/server.js b/lib/server.js index d1a9cb3..0804627 100644 --- a/lib/server.js +++ b/lib/server.js @@ -144,7 +144,7 @@ exports.Server = function Server(bsClient, workers) { if (details.message) { output += ", " + details.message; } - if (details.actual && details.expected) { + if (details.actual != null && details.expected != null) { output += "\n" + chalk.blue("Expected: ") + details.expected + "\n" + chalk.blue(" Actual: ") + details.actual; } @@ -187,7 +187,7 @@ exports.Server = function Server(bsClient, workers) { "_progress": function progressHandler(uri, body, request, response) { var uuid = request.headers['x-worker-uuid']; var worker = workers[uuid] || {}; - query = ""; + var query = null; try { query = parseBody(body); } catch(e) { @@ -197,14 +197,14 @@ exports.Server = function Server(bsClient, workers) { if (query.tracebacks) { query.tracebacks.forEach(function(traceback) { - logger.info(chalk.red("[%s] Error:"), getTestBrowserInfo(worker), formatTraceback(traceback)); + logger.info("[%s] " + chalk.red("Error:"), getTestBrowserInfo(worker), formatTraceback(traceback)); }); } response.end(); }, "_report": function reportHandler(uri, body, request, response) { - query = null; + var query = null; try { query = parseBody(body); } catch (e) {} @@ -216,20 +216,21 @@ exports.Server = function Server(bsClient, workers) { logger.info("[%s] Null response from remote Browser", request.headers['x-browser-string']); } else { if (query.tracebacks && query.tracebacks.length > 0) { - logger.info(chalk["red"]("[%s] Tracebacks:"), getTestBrowserInfo(worker)); + logger.info("[%s] " + chalk["red"]("Tracebacks:"), getTestBrowserInfo(worker)); query.tracebacks.forEach(function(traceback) { logger.info(traceback); }); } var color = query.failed ? "red" : "green"; - logger.info(chalk[color]("[%s] Completed in %d milliseconds. %d of %d passed, %d failed."), getTestBrowserInfo(worker), query.runtime, query.passed, query.total, query.failed); + logger.info("[%s] " + chalk[color](query.failed ? "Failed:" : "Passed:") + " %d tests, %d passed, %d failed; ran for %dms", getTestBrowserInfo(worker), query.total, query.passed, query.failed, query.runtime); config.status += query.failed; } + // TODO remove this check, must always be true due to `worker` usage above if (worker) { bsClient.takeScreenshot(worker.id, function(error, screenshot) { - if (!error && screenshot.url) { - logger.info('[%s] ' + chalk['yellow']('Screenshot') + ': %s', getTestBrowserInfo(worker), screenshot.url); + if (!error && screenshot.url && query && query.failed) { + logger.info('[%s] ' + chalk.yellow('Screenshot:') + ' %s', getTestBrowserInfo(worker), screenshot.url); } checkAndTerminateWorker(worker, function(reusedWorker) { diff --git a/tests/test.rb b/tests/test.rb index f24a491..c3cfcc3 100644 --- a/tests/test.rb +++ b/tests/test.rb @@ -12,7 +12,7 @@ def runBg(cmd, linestomatch, genre, test_strings) matched_counter = 0 IO.popen(cmd) {|io| io.each {|line| - if line.match(/Completed in /) + if line.match(/ran for/) counter += 1 puts "[#{genre.upcase}] : " + line test_strings.each do |test_string| @@ -45,27 +45,27 @@ def run_project(reponame, test_framework, passed_array, browser_url) sleep(1) execute("cd #{repo_path}#{reponame} && git clean -f -d && git reset --hard") end - + def qunit - run_project("underscorejs", "qunit", ["631 of 631 passed"], "test/index.html") - run_project("Modernizr", "qunit", ["20 of 41 passed"], "test/index.html") + run_project("underscorejs", "qunit", ["631 passed"], "test/index.html") + run_project("Modernizr", "qunit", ["20 passed"], "test/index.html") # https://github.com/bitovi/funcunit/ # https://github.com/twbs/bootstrap end def mocha - run_project("url.js", "mocha", ["35 of 35 passed"], "test.html") + run_project("url.js", "mocha", ["35 passed"], "test.html") # https://github.com/browserstack/microjungle # https://github.com/dhimil/mocha # https://github.com/auth0/auth0.js end def jasmine - run_project("mout", "jasmine", ["978 of 978 passed"], "tests/runner.html") + run_project("mout", "jasmine", ["978 passed"], "tests/runner.html") end def jasmine2 - run_project("Comparatorsjs", "jasmine2", ["6 of 6 passed"], "comparators.spec-runner.html") + run_project("Comparatorsjs", "jasmine2", ["6 passed"], "comparators.spec-runner.html") end def run From f5c44ca8dff384c18ccfd451d483e1a28a0f0b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Fri, 17 Oct 2014 12:24:14 +0200 Subject: [PATCH 008/162] Server: Remove unnecessary worker check --- lib/server.js | 53 ++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/lib/server.js b/lib/server.js index 0804627..ad12060 100644 --- a/lib/server.js +++ b/lib/server.js @@ -226,42 +226,39 @@ exports.Server = function Server(bsClient, workers) { config.status += query.failed; } - // TODO remove this check, must always be true due to `worker` usage above - if (worker) { - bsClient.takeScreenshot(worker.id, function(error, screenshot) { - if (!error && screenshot.url && query && query.failed) { - logger.info('[%s] ' + chalk.yellow('Screenshot:') + ' %s', getTestBrowserInfo(worker), screenshot.url); - } - - checkAndTerminateWorker(worker, function(reusedWorker) { - if (!workers[uuid]) { - return; - } + bsClient.takeScreenshot(worker.id, function(error, screenshot) { + if (!error && screenshot.url && query && query.failed) { + logger.info('[%s] ' + chalk.yellow('Screenshot:') + ' %s', getTestBrowserInfo(worker), screenshot.url); + } - if (reusedWorker) { - logger.debug('[%s] Reused', getTestBrowserInfo(worker)); - return; - } + checkAndTerminateWorker(worker, function(reusedWorker) { + if (!workers[uuid]) { + return; + } - logger.debug('[%s] Terminated', getTestBrowserInfo(worker)); + if (reusedWorker) { + logger.debug('[%s] Reused', getTestBrowserInfo(worker)); + return; + } - clearTimeout(workers[uuid].activityTimeout); - clearTimeout(workers[uuid].testActivityTimeout); - delete workers[uuid]; + logger.debug('[%s] Terminated', getTestBrowserInfo(worker)); - if (utils.objectSize(workers) === 0) { - var color = config.status > 0 ? "red" : "green"; - logger.info(chalk[color]("All tests done, failures: %d."), config.status); + clearTimeout(workers[uuid].activityTimeout); + clearTimeout(workers[uuid].testActivityTimeout); + delete workers[uuid]; - if (config.status > 0) { - config.status = 1; - } + if (utils.objectSize(workers) === 0) { + var color = config.status > 0 ? "red" : "green"; + logger.info(chalk[color]("All tests done, failures: %d."), config.status); - process.kill(process.pid, 'SIGTERM'); + if (config.status > 0) { + config.status = 1; } - }); + + process.kill(process.pid, 'SIGTERM'); + } }); - } + }); response.end(); }, From 1d3f4a3b8318a42a8b78eab2142f556be10b8a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Fri, 17 Oct 2014 12:31:51 +0200 Subject: [PATCH 009/162] Server: Remove unnecessary worker fallback The fallback will never apply, since the previous if already establishes that workers[query._worker_key] is defined. --- lib/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index ad12060..6eea4d3 100644 --- a/lib/server.js +++ b/lib/server.js @@ -36,7 +36,7 @@ exports.Server = function Server(bsClient, workers) { var query = url_parts.query; if (query._worker_key && workers[query._worker_key]) { - worker = workers[query._worker_key] || {}; + var worker = workers[query._worker_key]; worker.acknowledged = true; logger.debug("[%s] Acknowledged", getTestBrowserInfo(worker)); } From 4933de6c82607fc2e589db67ba0bfaffa27fb263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Fri, 17 Oct 2014 12:49:38 +0200 Subject: [PATCH 010/162] Build: Add npm-test script, run mocha and jshint (new), fix lint issues Adds .editorconfig and .jshintrc for better code consistency. Run jshint along with moch tests using `npm test`. Fixes tons of lint issues, like double quotes instead of single quotes (there were more single then double quotes, so I went with single quotes). --- .editorconfig | 12 ++++ .jshintrc | 16 ++++++ bin/cli.js | 96 ++++++++++++++++---------------- bin/init.js | 8 ++- bin/version.js | 4 +- lib/config.js | 14 ++--- lib/configParser.js | 38 ++++++------- lib/local.js | 37 +++++++------ lib/logger.js | 12 ++-- lib/server.js | 131 ++++++++++++++++++++++---------------------- lib/utils.js | 37 +++++++------ package.json | 6 +- 12 files changed, 227 insertions(+), 184 deletions(-) create mode 100644 .editorconfig create mode 100644 .jshintrc diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fd707be --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..036ffc4 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,16 @@ +{ + "boss": true, + "curly": true, + "eqeqeq": true, + "eqnull": true, + "expr": true, + "immed": true, + "noarg": true, + "node": true, + "quotmark": "single", + "smarttabs": true, + "sub": true, + "trailing": true, + "undef": true, + "unused": true +} diff --git a/bin/cli.js b/bin/cli.js index e4541c9..1af389a 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,17 +1,17 @@ #! /usr/bin/env node -todo = process.argv[2]; - -if (todo == '--verbose') - global.logLevel = "debug"; -else - global.logLevel = "info"; +var todo = process.argv[2]; +if (todo === '--verbose') { + global.logLevel = 'debug'; +} else { + global.logLevel = 'info'; +} -if (todo == 'init') { +if (todo === 'init') { require('./init.js'); return; -} else if(todo == '--version') { +} else if (todo === '--version') { require('./version.js'); return; } @@ -29,11 +29,11 @@ var Log = require('../lib/logger'), http = require('http'), ConfigParser = require('../lib/configParser').ConfigParser, serverPort = 8888, + server, timeout, activityTimeout, workers = {}, workerKeys = {}, - logLevel, tunnelingAgent, tunnel; @@ -69,34 +69,36 @@ function terminateAllWorkers(callback) { } } } -}; +} function cleanUpAndExit(signal, status) { try { server.close(); } catch (e) { - logger.debug("Server already closed"); + logger.debug('Server already closed'); } - if (statusPoller) statusPoller.stop(); + if (statusPoller) { + statusPoller.stop(); + } try { process.kill(tunnel.process.pid, 'SIGKILL'); } catch (e) { - logger.debug("Non existent tunnel"); + logger.debug('Non existent tunnel'); } try { fs.unlinkSync(pid_file); } catch (e) { - logger.debug("Non existent pid file."); + logger.debug('Non existent pid file.'); } - if (signal == 'SIGTERM') { - logger.debug("Exiting"); + if (signal === 'SIGTERM') { + logger.debug('Exiting'); process.exit(status); } else { terminateAllWorkers(function() { - logger.debug("Exiting"); + logger.debug('Exiting'); process.exit(1); }); } @@ -105,22 +107,22 @@ function cleanUpAndExit(signal, status) { function getTestBrowserInfo(browserString, path) { var info = browserString; if (config.multipleTest) { - info += ", " + path; + info += ', ' + path; } return info; } function launchServer() { - logger.debug("Launching server on port:", serverPort); + logger.debug('Launching server on port:', serverPort); - var server = new Server(client, workers); + server = new Server(client, workers); server.listen(parseInt(serverPort, 10)); } function launchBrowser(browser, path) { var url = 'http://localhost:' + serverPort.toString() + '/' + path; var browserString = utils.browserString(browser); - logger.debug("[%s] Launching", getTestBrowserInfo(browserString, path)); + logger.debug('[%s] Launching', getTestBrowserInfo(browserString, path)); var key = utils.uuid(); @@ -141,7 +143,7 @@ function launchBrowser(browser, path) { } if(config.tunnelIdentifier) { - browser["tunnel_identifier"] = config.tunnelIdentifier; + browser['tunnel_identifier'] = config.tunnelIdentifier; } timeout = parseInt(config.timeout); @@ -154,9 +156,9 @@ function launchBrowser(browser, path) { client.createWorker(browser, function (err, worker) { if (err || typeof worker !== 'object') { - logger.info("Error from BrowserStack: ", err); - utils.alertBrowserStack("Failed to launch worker", - "Arguments: " + JSON.stringify({ + logger.info('Error from BrowserStack: ', err); + utils.alertBrowserStack('Failed to launch worker', + 'Arguments: ' + JSON.stringify({ err: err, worker: worker }, null, 4)); @@ -191,11 +193,9 @@ var statusPoller = { start: function() { statusPoller.poller = setInterval(function () { client.getWorkers(function (err, _workers) { - _workers = _workers.filter(function(currentValue, index, array) { - return currentValue.status == 'running' && workerKeys[currentValue.id] && !workerKeys[currentValue.id].marked; - }); - for (var i in _workers) { - var _worker = _workers[i]; + _workers.filter(function(currentValue) { + return currentValue.status === 'running' && workerKeys[currentValue.id] && !workerKeys[currentValue.id].marked; + }).forEach(function(_worker) { var workerData = workerKeys[_worker.id]; var worker = workers[workerData.key]; if (worker.launched) { @@ -210,15 +210,15 @@ var statusPoller = { worker.activityTimeout = setTimeout(function () { if (!worker.acknowledged) { - var subject = "Worker inactive for too long: " + worker.string; - var content = "Worker details:\n" + JSON.stringify(worker.config, null, 4); + var subject = 'Worker inactive for too long: ' + worker.string; + var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); utils.alertBrowserStack(subject, content, null, function(){}); delete workers[workerData.key]; delete workerKeys[worker.id]; config.status += 1; if (utils.objectSize(workers) === 0) { - var color = config.status > 0 ? "red" : "green"; - logger.info(chalk[color]("All tests done, failures: %d."), config.status); + var color = config.status > 0 ? 'red' : 'green'; + logger.info(chalk[color]('All tests done, failures: %d.'), config.status); if (config.status > 0) { config.status = 1; @@ -231,15 +231,15 @@ var statusPoller = { worker.testActivityTimeout = setTimeout(function () { if (worker.acknowledged) { - var subject = "Tests timed out on: " + worker.string; - var content = "Worker details:\n" + JSON.stringify(worker.config, null, 4); + var subject = 'Tests timed out on: ' + worker.string; + var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); utils.alertBrowserStack(subject, content, null, function(){}); delete workers[workerData.key]; delete workerKeys[worker.id]; config.status += 1; if (utils.objectSize(workers) === 0) { - var color = config.status > 0 ? "red" : "green"; - logger.info(chalk[color]("All tests done, failures: %d."), config.status); + var color = config.status > 0 ? 'red' : 'green'; + logger.info(chalk[color]('All tests done, failures: %d.'), config.status); if (config.status > 0) { config.status = 1; @@ -250,7 +250,7 @@ var statusPoller = { } }, (activityTimeout * 1000)); } - } + }); }); }, 2000); }, @@ -277,13 +277,13 @@ function runTests() { tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, function () { statusPoller.start(); var total_runs = config.browsers.length * (Object.prototype.toString.call(config.test_path) === '[object Array]' ? config.test_path.length : 1); - logger.info("Launching " + config.browsers.length + " worker(s) for " + total_runs + " run(s)."); + logger.info('Launching ' + config.browsers.length + ' worker(s) for ' + total_runs + ' run(s).'); browsers.forEach(function(browser) { - if (browser.browser_version === "latest") { - logger.debug("[%s] Finding version.", utils.browserString(browser)); + if (browser.browser_version === 'latest') { + logger.debug('[%s] Finding version.', utils.browserString(browser)); client.getLatest(browser, function(err, version) { - logger.debug("[%s] Version is %s.", + logger.debug('[%s] Version is %s.', utils.browserString(browser), version); browser.browser_version = version; // So that all latest logs come in together @@ -307,9 +307,13 @@ try { }); runTests(); var pid_file = process.cwd() + '/browserstack-run.pid'; - fs.writeFileSync(pid_file, process.pid, 'utf-8') - process.on('SIGINT', function() { cleanUpAndExit('SIGINT', 1) }); - process.on('SIGTERM', function() { cleanUpAndExit('SIGTERM', config.status) }); + fs.writeFileSync(pid_file, process.pid, 'utf-8'); + process.on('SIGINT', function() { + cleanUpAndExit('SIGINT', 1); + }); + process.on('SIGTERM', function() { + cleanUpAndExit('SIGTERM', config.status); + }); } catch (e) { console.log(e); console.log('Invalid command.'); diff --git a/bin/init.js b/bin/init.js index d1a4227..57640f4 100755 --- a/bin/init.js +++ b/bin/init.js @@ -9,11 +9,15 @@ var config = { key: 'BROWSERSTACK_KEY', test_path: 'path/to/test/runner', browsers: browsers -} +}; var configString = JSON.stringify(config, null, 4); -fs.writeFile('browserstack.json', configString, function (err, written, buffer) { +fs.writeFile('browserstack.json', configString, function (err) { + if (err) { + console.log('Failed to generate `browserstack.json`', err); + return; + } console.log('Generated `browserstack.json` using preset "%s" having %d browsers.', preset, browsers.length); }); diff --git a/bin/version.js b/bin/version.js index 506e70d..f3615d4 100644 --- a/bin/version.js +++ b/bin/version.js @@ -1,5 +1,5 @@ #! /usr/bin/env node -var packagePath = require('path').resolve(__dirname, "../package.json"), +var packagePath = require('path').resolve(__dirname, '../package.json'), packageJson = require(packagePath); -console.log("browserstack-runner @", packageJson["version"]); +console.log('browserstack-runner @', packageJson['version']); diff --git a/lib/config.js b/lib/config.js index 7ab2fb7..bd00cb4 100644 --- a/lib/config.js +++ b/lib/config.js @@ -4,14 +4,14 @@ var Log = require('./logger'), fs = require('fs'), pwd = process.cwd(); -config_path = process.env.BROWSERSTACK_JSON || 'browserstack.json'; +var config_path = process.env.BROWSERSTACK_JSON || 'browserstack.json'; config_path = path.resolve(path.relative(process.cwd(), config_path)); -logger.debug("Using config:", config_path); +logger.debug('Using config:', config_path); try { var config = require(config_path); } catch (e) { - if (e.code == 'MODULE_NOT_FOUND') { + if (e.code === 'MODULE_NOT_FOUND') { logger.info('Configuration file `browserstack.json` is missing.'); } else { logger.info('Invalid configuration in `browserstack.json` file'); @@ -21,10 +21,10 @@ try { process.exit(1); } +var package_json = {}; try { - var package_json = require(process.cwd() + '/package.json'); + package_json = require(process.cwd() + '/package.json'); } catch (e) { - var package_json = {}; } if (process.env.BROWSERSTACK_KEY) { @@ -48,9 +48,9 @@ if (!config.project) { var commit_id = process.env.TRAVIS_COMMIT; if (commit_id) { - config.build = "Commit-" + commit_id.slice(0, commit_id.length / 2); + config.build = 'Commit-' + commit_id.slice(0, commit_id.length / 2); } { - config.build = "Local run, " + new Date().toISOString(); + config.build = 'Local run, ' + new Date().toISOString(); } ['username', 'key', 'test_path', 'browsers'].forEach(function(param) { diff --git a/lib/configParser.js b/lib/configParser.js index 4ab8fb9..eed503b 100644 --- a/lib/configParser.js +++ b/lib/configParser.js @@ -11,7 +11,7 @@ var ConfigParser = { parse: function(client, browser_config, callback) { client.getBrowsers(function(error, browsers) { if(error) { - logger.info("Error getting browsers list from BrowserStack"); + logger.info('Error getting browsers list from BrowserStack'); logger.info(error); process.exit(1); } @@ -22,12 +22,12 @@ var ConfigParser = { } callback(ConfigParser.finalBrowsers); }); - return + return; }, setBrowserVersion: function(browserObject, verStr) { - var filteredBrowsers = ConfigParser.bsBrowsers.map(function(currentValue, index, array) { - if (currentValue.browser.toLowerCase() == browserObject.browser) { + var filteredBrowsers = ConfigParser.bsBrowsers.map(function(currentValue) { + if (currentValue.browser.toLowerCase() === browserObject.browser) { return (browserObject.mobile ? currentValue.os_version : currentValue.browser_version); } }).filter(function(currentValue, index, array) { @@ -35,10 +35,10 @@ var ConfigParser = { }).sort(function(a, b) { return parseFloat(a) - parseFloat(b); }); - if (verStr == 'current' || verStr == 'latest') { + if (verStr === 'current' || verStr === 'latest') { return filteredBrowsers[filteredBrowsers.length - 1]; } - else if (verStr == 'previous') { + else if (verStr === 'previous') { return filteredBrowsers[filteredBrowsers.length - 2]; } }, @@ -47,20 +47,20 @@ var ConfigParser = { if (!(browserObject.os && browserObject.os_version)) { if (browserObject.mobile) { - var mobileFiltered = ConfigParser.bsBrowsers.filter(function(currentValue, index, array) { - return currentValue.browser.toLowerCase() == browserObject.browser && parseFloat(currentValue.os_version).toPrecision(4) == parseFloat(browserObject.os_version).toPrecision(4); + var mobileFiltered = ConfigParser.bsBrowsers.filter(function(currentValue) { + return currentValue.browser.toLowerCase() === browserObject.browser && parseFloat(currentValue.os_version).toPrecision(4) === parseFloat(browserObject.os_version).toPrecision(4); }); browserObject = mobileFiltered[Math.floor(Math.random() * mobileFiltered.length)]; } else { - var windowsFiltered = ConfigParser.bsBrowsers.filter(function(currentValue, index, array) { - return currentValue.os == 'Windows' && currentValue.browser == browserObject.browser && parseFloat(currentValue.browser_version).toPrecision(4) == parseFloat(browserObject.browser_version).toPrecision(4); + var windowsFiltered = ConfigParser.bsBrowsers.filter(function(currentValue) { + return currentValue.os === 'Windows' && currentValue.browser === browserObject.browser && parseFloat(currentValue.browser_version).toPrecision(4) === parseFloat(browserObject.browser_version).toPrecision(4); }); - var osxFiltered = ConfigParser.bsBrowsers.filter(function(currentValue, index, array) { - return currentValue.os == 'OS X' && currentValue.browser == browserObject.browser && parseFloat(currentValue.browser_version).toPrecision(4) == parseFloat(browserObject.browser_version).toPrecision(4); + var osxFiltered = ConfigParser.bsBrowsers.filter(function(currentValue) { + return currentValue.os === 'OS X' && currentValue.browser === browserObject.browser && parseFloat(currentValue.browser_version).toPrecision(4) === parseFloat(browserObject.browser_version).toPrecision(4); }); browserObject = windowsFiltered.length > 0 ? windowsFiltered[Math.floor(Math.random() * windowsFiltered.length)] : osxFiltered[Math.floor(Math.random() * osxFiltered.length)]; } @@ -73,10 +73,10 @@ var ConfigParser = { var browserObject = {}; var version = null; var sliceStart = 1; - if (typeof(entry) == 'string') { - var browserData = entry.split("_"); + if (typeof(entry) === 'string') { + var browserData = entry.split('_'); var lindex = browserData.length - 1; - if (browserData[0] == 'mobile' || browserData[0] == 'android' || (browserData[0] == 'opera' && browserData[1] == 'browser')) { + if (browserData[0] === 'mobile' || browserData[0] === 'android' || (browserData[0] === 'opera' && browserData[1] === 'browser')) { browserObject.browser = browserData[0] + ' ' + browserData[1]; browserObject.mobile = true; sliceStart = 2; @@ -84,16 +84,16 @@ var ConfigParser = { else { browserObject.browser = browserData[0]; } - if (browserData[lindex] && browserData[lindex].indexOf("+") == -1) { - if (["current", "previous", "latest"].indexOf(browserData[lindex]) != -1) { + if (browserData[lindex] && browserData[lindex].indexOf('+') === -1) { + if (['current', 'previous', 'latest'].indexOf(browserData[lindex]) !== -1) { version = ConfigParser.setBrowserVersion(browserObject, browserData[lindex]); } else { - version = browserData.slice(sliceStart, lindex + 1).join("."); + version = browserData.slice(sliceStart, lindex + 1).join('.'); } } else { - version = browserData.slice(sliceStart, lindex + 1).join("."); + version = browserData.slice(sliceStart, lindex + 1).join('.'); } if (browserObject.mobile) { browserObject.os_version = version; diff --git a/lib/local.js b/lib/local.js index d5cf88b..9142429 100644 --- a/lib/local.js +++ b/lib/local.js @@ -8,12 +8,14 @@ var Log = require('./logger'), utils = require('./utils'), config = require('./config'); -var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback, err) { +var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { var that = {}; function tunnelLauncher() { var tunnelCommand = (windows ? 'java -jar ' : '') + localBinary + ' '; - if (config.debug) tunnelCommand += ' -v '; + if (config.debug) { + tunnelCommand += ' -v '; + } tunnelCommand += key + ' '; tunnelCommand += 'localhost' + ','; tunnelCommand += port.toString() + ','; @@ -25,12 +27,12 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback, err) { callback = function(){}; } - logger.debug("[%s] Launching tunnel", new Date()); + logger.debug('[%s] Launching tunnel', new Date()); var subProcess = exec(tunnelCommand, function(error, stdout, stderr) { logger.debug(stderr); logger.debug(error); if (stdout.indexOf('Error') >= 0 || error) { - logger.debug("[%s] Tunnel launching failed", new Date()); + logger.debug('[%s] Tunnel launching failed', new Date()); logger.debug(stdout); process.kill(process.pid, 'SIGINT'); } @@ -38,11 +40,11 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback, err) { var data = ''; var running = false; - var runMatcher = "You can now access your local server(s)"; + var runMatcher = 'You can now access your local server(s)'; setTimeout(function() { if (!running) { - utils.alertBrowserStack("Tunnel launch timeout", 'Stdout:\n' + data); + utils.alertBrowserStack('Tunnel launch timeout', 'Stdout:\n' + data); } }, 30 * 1000); @@ -55,9 +57,9 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback, err) { if (data.indexOf(runMatcher) >= 0) { running = true; - logger.debug("[%s] Tunnel launched", new Date()); + logger.debug('[%s] Tunnel launched', new Date()); setTimeout(function(){ - callback(); + callback(); }, 2000); } }); @@ -67,15 +69,15 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback, err) { function checkAndAddProxy() { var proxy = config.proxy; - if(typeof proxy == 'undefined') { - return ""; + if (typeof proxy === 'undefined') { + return ''; } - var proxyCommand = ""; - proxyCommand += " -proxyHost " + proxy.host; - proxyCommand += " -proxyPort " + proxy.port; + var proxyCommand = ''; + proxyCommand += ' -proxyHost ' + proxy.host; + proxyCommand += ' -proxyPort ' + proxy.port; if(typeof proxy.username !== 'undefined'){ - proxyCommand += " -proxyUser " + proxy.username; - proxyCommand += " -proxyPass " + proxy.password; + proxyCommand += ' -proxyUser ' + proxy.username; + proxyCommand += ' -proxyPass ' + proxy.password; } return proxyCommand; } @@ -88,8 +90,7 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback, err) { logger.debug('Downloading BrowserStack Local to `%s`', localBinary); var file = fs.createWriteStream(localBinary); - var request = http.get( - (windows ? "http://www.browserstack.com/BrowserStackTunnel.jar" : ("http://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal-" + process.platform + "-" + process.arch)), + http.get(windows ? 'http://www.browserstack.com/BrowserStackTunnel.jar' : ('http://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal-' + process.platform + '-' + process.arch), function(response) { response.pipe(file); @@ -99,7 +100,7 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback, err) { tunnelLauncher(); }, 100); }).on('error', function(e) { - logger.info("Got error while downloading binary: " + e.message); + logger.info('Got error while downloading binary: ' + e.message); process.kill(process.pid, 'SIGINT'); }); }); diff --git a/lib/logger.js b/lib/logger.js index 1b43758..0f0c431 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -2,10 +2,12 @@ var fmt = require('util').format; var logLevels = {ERROR: 3, INFO: 6, DEBUG: 7}; function Log(level){ - if ('string' == typeof level) level = logLevels[level.toUpperCase()]; + if ('string' === typeof level) { + level = logLevels[level.toUpperCase()]; + } this.level = isFinite(level) ? level : logLevels.DEBUG; this.stream = process.stdout; -}; +} Log.prototype = { @@ -17,15 +19,15 @@ Log.prototype = { } }, - error: function(msg){ + error: function(){ this.log('ERROR', arguments); }, - info: function(msg){ + info: function(){ this.log('INFO', arguments); }, - debug: function(msg){ + debug: function(){ this.log('DEBUG', arguments); } }; diff --git a/lib/server.js b/lib/server.js index 6eea4d3..7a0da56 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,29 +1,28 @@ var Log = require('./logger'), logger = new Log(global.logLevel), - http = require("http"), - url = require("url"), - path = require("path"), - fs = require("fs"), - qs = require("querystring"), - utils = require("./utils"), + http = require('http'), + url = require('url'), + path = require('path'), + fs = require('fs'), + qs = require('querystring'), + utils = require('./utils'), config = require('../lib/config'), - exec = require('child_process').exec, chalk = require('chalk'); var mimeTypes = { - "html": "text/html", - "json": "text/json", - "jpeg": "image/jpeg", - "jpg": "image/jpeg", - "png": "image/png", - "js": "text/javascript", - "css": "text/css" + 'html': 'text/html', + 'json': 'text/json', + 'jpeg': 'image/jpeg', + 'jpg': 'image/jpeg', + 'png': 'image/png', + 'js': 'text/javascript', + 'css': 'text/css' }; function getTestBrowserInfo(worker) { var info = worker.string; if(config.multipleTest) { - info += ", " + worker.test_path + info += ', ' + worker.test_path; } return info; } @@ -38,45 +37,45 @@ exports.Server = function Server(bsClient, workers) { if (query._worker_key && workers[query._worker_key]) { var worker = workers[query._worker_key]; worker.acknowledged = true; - logger.debug("[%s] Acknowledged", getTestBrowserInfo(worker)); + logger.debug('[%s] Acknowledged', getTestBrowserInfo(worker)); } fs.exists(filename, function(exists) { if (!exists) { response.writeHead(404, { - "Content-Type": "text/plain" + 'Content-Type': 'text/plain' }); - response.write("404 Not Found\n"); + response.write('404 Not Found\n'); response.end(); return; } if (fs.lstatSync(filename).isDirectory()) { - filename = filename + (filename.lastIndexOf('/') == filename.length - 1 ? "" : "/") + "index.html"; + filename = filename + (filename.lastIndexOf('/') === filename.length - 1 ? '' : '/') + 'index.html'; } fs.readFile(filename, {encoding: 'utf8'}, function(err, file) { if (err) { response.writeHead(500, { - "Content-Type": "text/plain" + 'Content-Type': 'text/plain' }); - response.write(err + "\n"); + response.write(err + '\n'); response.end(); return; } - var mimeType = mimeTypes[path.extname(filename).split(".")[1]]; + var mimeType = mimeTypes[path.extname(filename).split('.')[1]]; response.writeHead(200, { - "Content-Type": mimeType + "; charset=utf-8", + 'Content-Type': mimeType + '; charset=utf-8', }); - scripts = [ + var scripts = [ 'json2.js', 'browserstack.js', ]; - framework_scripts = { + var framework_scripts = { 'qunit': ['qunit-plugin.js'], 'jasmine': ['jasmine-jsreporter.js', 'jasmine-plugin.js'], 'jasmine2': ['jasmine2-plugin.js'], @@ -87,40 +86,40 @@ exports.Server = function Server(bsClient, workers) { var pathMatches; if (typeof config.test_path === 'object') { - pathMatches = (config.test_path.indexOf(filePath) != -1); + pathMatches = (config.test_path.indexOf(filePath) !== -1); } else { - pathMatches = (filePath == config.test_path); + pathMatches = (filePath === config.test_path); } if (pathMatches && mimeType === 'text/html') { var framework = config['test_framework']; - var tag_name = (framework === "mocha") ? "head" : "body"; - var matcher = new RegExp("(.*)<\/" + tag_name + ">"); ///(.*)<\/body>/; - var patch = "$1"; + var tag_name = (framework === 'mocha') ? 'head' : 'body'; + var matcher = new RegExp('(.*)<\/' + tag_name + '>'); ///(.*)<\/body>/; + var patch = '$1'; scripts.forEach(function(script) { - patch += "\n"; + patch += '\n'; }); // adding framework scripts - if (framework === "jasmine") { + if (framework === 'jasmine') { framework_scripts['jasmine'].forEach(function(script) { - patch += "\n"; + patch += '\n'; }); - patch += "\n"; - } else if (framework === "jasmine2") { + patch += '\n'; + } else if (framework === 'jasmine2') { framework_scripts['jasmine2'].forEach(function(script) { - patch += "\n"; + patch += '\n'; }); - } else if (framework === "mocha") { + } else if (framework === 'mocha') { framework_scripts['mocha'].forEach(function(script) { - patch += "\n"; + patch += '\n'; }); - patch += "\n"; + patch += '\n'; } else { framework_scripts['qunit'].forEach(function(script) { - patch += "\n"; + patch += '\n'; }); } - patch += ""; + patch += ''; file = file.replace(matcher, patch); } @@ -140,17 +139,17 @@ exports.Server = function Server(bsClient, workers) { function formatTraceback(details) { // looks like QUnit data if (details.testName) { - var output = "'" + details.testName + "' failed"; + var output = '"' + details.testName + '" failed'; if (details.message) { - output += ", " + details.message; + output += ', ' + details.message; } if (details.actual != null && details.expected != null) { - output += "\n" + chalk.blue("Expected: ") + details.expected + - "\n" + chalk.blue(" Actual: ") + details.actual; + output += '\n' + chalk.blue('Expected: ') + details.expected + + '\n' + chalk.blue(' Actual: ') + details.actual; } if (details.source) { - output += "\n" + chalk.blue(" Source: ") + ""; - output += details.source.split("\n").join("\n\t "); + output += '\n' + chalk.blue(' Source: ') + ''; + output += details.source.split('\n').join('\n\t '); } return output; } @@ -166,7 +165,7 @@ exports.Server = function Server(bsClient, workers) { } else { url += '?'; } - url += "_worker_key=" + worker._worker_key + "&_browser_string=" + getTestBrowserInfo(worker) ; + url += '_worker_key=' + worker._worker_key + '&_browser_string=' + getTestBrowserInfo(worker) ; worker.test_path = next_path; bsClient.changeUrl(worker.id, {url: url}, function() { callback(true); @@ -174,7 +173,7 @@ exports.Server = function Server(bsClient, workers) { } else { bsClient.terminateWorker(worker.id, callback); } - }; + } function getNextTestPath(worker) { if (!config.multipleTest) { @@ -183,27 +182,27 @@ exports.Server = function Server(bsClient, workers) { return config.test_path[ ++worker.path_index ]; } - handlers = { - "_progress": function progressHandler(uri, body, request, response) { + var handlers = { + '_progress': function progressHandler(uri, body, request, response) { var uuid = request.headers['x-worker-uuid']; var worker = workers[uuid] || {}; var query = null; try { query = parseBody(body); } catch(e) { - logger.info("[%s] Exception in parsing log", worker.string); - logger.info("[%s] Log: " + qs.parse(body).data, worker.string); + logger.info('[%s] Exception in parsing log', worker.string); + logger.info('[%s] Log: ' + qs.parse(body).data, worker.string); } if (query.tracebacks) { query.tracebacks.forEach(function(traceback) { - logger.info("[%s] " + chalk.red("Error:"), getTestBrowserInfo(worker), formatTraceback(traceback)); + logger.info('[%s] ' + chalk.red('Error:'), getTestBrowserInfo(worker), formatTraceback(traceback)); }); } response.end(); }, - "_report": function reportHandler(uri, body, request, response) { + '_report': function reportHandler(uri, body, request, response) { var query = null; try { query = parseBody(body); @@ -213,16 +212,16 @@ exports.Server = function Server(bsClient, workers) { worker._worker_key = uuid; if (query === null) { - logger.info("[%s] Null response from remote Browser", request.headers['x-browser-string']); + logger.info('[%s] Null response from remote Browser', request.headers['x-browser-string']); } else { if (query.tracebacks && query.tracebacks.length > 0) { - logger.info("[%s] " + chalk["red"]("Tracebacks:"), getTestBrowserInfo(worker)); + logger.info('[%s] ' + chalk['red']('Tracebacks:'), getTestBrowserInfo(worker)); query.tracebacks.forEach(function(traceback) { logger.info(traceback); }); } - var color = query.failed ? "red" : "green"; - logger.info("[%s] " + chalk[color](query.failed ? "Failed:" : "Passed:") + " %d tests, %d passed, %d failed; ran for %dms", getTestBrowserInfo(worker), query.total, query.passed, query.failed, query.runtime); + var color = query.failed ? 'red' : 'green'; + logger.info('[%s] ' + chalk[color](query.failed ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed; ran for %dms', getTestBrowserInfo(worker), query.total, query.passed, query.failed, query.runtime); config.status += query.failed; } @@ -248,8 +247,8 @@ exports.Server = function Server(bsClient, workers) { delete workers[uuid]; if (utils.objectSize(workers) === 0) { - var color = config.status > 0 ? "red" : "green"; - logger.info(chalk[color]("All tests done, failures: %d."), config.status); + var color = config.status > 0 ? 'red' : 'green'; + logger.info(chalk[color]('All tests done, failures: %d.'), config.status); if (config.status > 0) { config.status = 1; @@ -262,15 +261,15 @@ exports.Server = function Server(bsClient, workers) { response.end(); }, - "_log": function logHandler(uri, body, request, response) { - query = parseBody(body); + '_log': function logHandler(uri, body, request, response) { + var query = parseBody(body); logger.info('[' + request.headers['x-browser-string'] + '] ' + query); response.end(); }, - "_patch": function patchHandler(uri, body, request, response) { + '_patch': function patchHandler(uri, body, request, response) { handleFile(path.join(__dirname, uri), request, response); }, - "_default": function defaultHandler(uri, body, request, response) { + '_default': function defaultHandler(uri, body, request, response) { handleFile(path.join(process.cwd(), uri), request, response); } }; @@ -279,8 +278,6 @@ exports.Server = function Server(bsClient, workers) { return http.createServer(function(request, response) { var uri = url.parse(request.url).pathname; var method = uri.split('/')[1]; - var filename; - var body = ''; request.on('data', function(data) { diff --git a/lib/utils.js b/lib/utils.js index 390f62a..dfd7c1c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -6,9 +6,10 @@ var Log = require('./logger'), querystring = require('querystring'); String.prototype.escapeSpecialChars = function() { - return this.replace(/\n/g, "\\n") - .replace(/\\s/g, "\s") - .replace(/\\\'/, "\'"); + return this.replace(/\n/g, '\\n') + // TODO what is this supposed to do? JSHint considers this "Bad or unnecessary escaping." + .replace(/\\s/g, '\s') + .replace(/\\\'/, '\''); }; var titleCase = function toTitleCase(str) { @@ -20,7 +21,7 @@ var titleCase = function toTitleCase(str) { var uuid = function uuidGenerator() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, - v = c == 'x' ? r : (r & 0x3 | 0x8); + v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }; @@ -28,9 +29,9 @@ var uuid = function uuidGenerator() { var browserString = function browserString(config) { var os_details = config.os + ' ' + config.os_version; if (config.browser) { - return os_details + ', ' + (config.browser == 'ie' ? 'Internet Explorer' : titleCase(config.browser)) + ' ' + config.browser_version; + return os_details + ', ' + (config.browser === 'ie' ? 'Internet Explorer' : titleCase(config.browser)) + ' ' + config.browser_version; } else { - return os_details + (config.device ? (', ' + config.device) : ""); + return os_details + (config.device ? (', ' + config.device) : ''); } }; @@ -38,17 +39,19 @@ var objectSize = function objectSize(obj) { var size = 0, key; for (key in obj) { - if (obj.hasOwnProperty(key)) size++; + if (obj.hasOwnProperty(key)) { + size++; + } } return size; }; var alertBrowserStack = function alertBrowserStack(subject, content, params, fn) { - var endpoint = config.alert_endpoint || "http://www.browserstack.com/automate/alert"; + var endpoint = config.alert_endpoint || 'http://www.browserstack.com/automate/alert'; var urlObject = url.parse(endpoint); - var context = config.alert_context || "Runner alert"; - logger.info("[%s] [%s] %s", new Date(), context, subject); + var context = config.alert_context || 'Runner alert'; + logger.info('[%s] [%s] %s', new Date(), context, subject); if (typeof fn !== 'function') { if (typeof params === 'function') { @@ -73,22 +76,22 @@ var alertBrowserStack = function alertBrowserStack(subject, content, params, fn) port: urlObject.port, path: urlObject.path, method: 'POST', - auth: config.username + ":" + config.key, + auth: config.username + ':' + config.key, headers: { 'Content-Length': body.length } }; var callback = function(res) { - var response = ""; - res.setEncoding("utf8"); - res.on("data", function(chunk) { + var response = ''; + res.setEncoding('utf8'); + res.on('data', function(chunk) { response += chunk; }); - res.on("end", function() { + res.on('end', function() { if (res.statusCode !== 200) { var message; - if (res.headers["content-type"].indexOf("json") !== -1) { + if (res.headers['content-type'].indexOf('json') !== -1) { var resp = JSON.parse(response); message = resp.message; message += ' - ' + resp.errors.map(function(err) { @@ -98,7 +101,7 @@ var alertBrowserStack = function alertBrowserStack(subject, content, params, fn) message = response; } if (!message && res.statusCode === 403) { - message = "Forbidden"; + message = 'Forbidden'; } fn(new Error(message)); } else { diff --git a/package.json b/package.json index 40b9258..9bc7125 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "tunnel": "0.0.3" }, "devDependencies": { - "mocha": "~1.15.1" + "jshint": "2.5.6", + "mocha": "1.15.1" }, "licenses": [ { @@ -23,5 +24,8 @@ ], "bin": { "browserstack-runner": "bin/cli.js" + }, + "scripts": { + "test": "node_modules/.bin/mocha tests/unit && node_modules/.bin/jshint lib/*.js bin/" } } From bb6b3d3553abee14b35657151c8a07b80b9abe5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Mon, 20 Oct 2014 12:50:07 +0200 Subject: [PATCH 011/162] README: Overhaul everything, making usage more clear, add contributing Adds CONTRIBUTING.md specifically for contributors, not users. Makes installing from npm the primary usage example. Compress the default configuration example. Removes a bunch of newlines to make editing easier. Uses better formatting for parameter lists. --- CONTRIBUTING.md | 37 +++++++++ README.md | 216 ++++++++++++++++++------------------------------ 2 files changed, 118 insertions(+), 135 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b47ae28 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +## Contributing to browserstack-runner + +Your help improving this project is welcome! + +## Got a question or problem? Found an issue? + +If you have questions about how to use this tool, or you found a bug in the source code or a mistake in the documentation, [file an issue](https://github.com/browserstack/browserstack-runner/issues/new). + +## Want to contribute code? + +If you found an issue and want to contribute a fix or implement a new feature, send a pull request! + +To make changes: [Fork the repo, clone it locally](https://help.github.com/articles/fork-a-repo/), make a branch for your change, then implement it. + +Install some dependencies: + + npm install + +Then run tests with: + + npm test + +This runs some unit tests (consider adding more to `tests/unit`) and runs some other tools like jshint. Make sure to fix lint issues it finds. + +To test your change with another project where you use the tool, use `npm-link`: + + # in your browserstack-runner checkout + npm link + # in your project + npm link browserstack-runner + +Or do a local install: + + # in your project + npm install /path/to/checkout/browserstack-runner + +Once done, commit, push the branch to your repo and [create a pull request](https://help.github.com/articles/using-pull-requests/#initiating-the-pull-request). diff --git a/README.md b/README.md index 44771f1..a3f4c43 100644 --- a/README.md +++ b/README.md @@ -1,178 +1,124 @@ A command line interface to run browser tests over BrowserStack. -## Install -Go to the `browserstack-runner` directory. -Install browserstack-runner +## Usage - npm -g install +Install globally: -or + npm -g install browserstack-runner +Then, after setting up the configuration, run tests with: -For development, + browserstack-runner - npm link +You can also install locally and run the local binary: -## Configuration -To run browser tests on BrowserStack infrastructure, you need to -create a `browserstack.json` file in project's root directory (the -directory from which tests are run), by running this command: - - browserstack-runner init [preset] - -If nothing is provided as `preset` **default** is used. - -> Currently only one preset is present: **default** + npm install browserstack-runner + node_modules/.bin/browserstack-runner -### Parameters - - - *username*: BrowserStack username - (Alternatively: use `BROWSERSTACK_USERNAME` environment variable) - - - *key*: BrowserStack key - (Alternatively: use `BROWSERSTACK_KEY` environment variable) +## Configuration - - *test_path*: Path to the which will execute the tests when opened - in a browser. +To run browser tests on BrowserStack infrastructure, you need to create a `browserstack.json` file in project's root directory (the directory from which tests are run), by running this command: - - *test_framework*: Specify test framework which will execute the tests. - We support qunit, jasmine, jasmine 2.0 and mocha. + browserstack-runner init - - *timeout*: Specify worker timeout with BrowserStack. +### Parameters for `browserstack.json` - - *browsers*: A list of browsers on which tests are to be run. + * `username`: BrowserStack username (Or `BROWSERSTACK_USERNAME` environment variable) + * `key`: BrowserStack [access key](https://www.browserstack.com/accounts/local-testing) (Or `BROWSERSTACK_KEY` environment variable) + * `test_path`: Path to the test page which will run the tests when opened in a browser. + * `test_framework`: Specify test framework which will run the tests. Currently supporting qunit, jasmine, jasmine2 and mocha. + * `timeout`: Specify worker timeout with BrowserStack. + * `browsers`: A list of browsers on which tests are to be run. Find a [list of all supported browsers and platforms on browerstack.com](http://www.browserstack.com/list-of-browsers-and-platforms?product=live). + * `proxy`: Specify a proxy to use for the local tunnel. Object with `host`, `port`, `username` and `password` properties. -A sample configuration file (list: http://www.browserstack.com/list-of-browsers-and-platforms?product=live): +A sample configuration file: ```json { "username": "", - "key": "", - "test_framework": "qunit/jasmine/jasmine2/mocha", + "key": "", + "test_framework": "qunit|jasmine|jasmine2|mocha", "test_path": ["relative/path/to/test/page1", "relative/path/to/test/page2"], - "browsers": [{ - "browser": "firefox", - "browser_version": "15.0", - "device": null, - "os": "OS X", - "os_version": "Snow Leopard" - }, - { - "browser": "firefox", - "browser_version": "16.0", - "device": null, - "os": "Windows", - "os_version": "7" - }, - { - "browser": "firefox", - "browser_version": "17.0", - "device": null, - "os": "Windows", - "os_version": "8" - }, - { - "browser": "ie", - "browser_version": "8.0", - "device": null, - "os": "Windows", - "os_version": "7" - }, - { - "browser": "ie", - "browser_version": "9.0", - "device": null, - "os": "Windows", - "os_version": "7" - }, - { - "os": "android", - "os_version": "4.0", - "device": "Samsung Galaxy Nexus" - }, - { - "os": "ios", - "os_version": "7.0", - "device": "iPhone 5S" - }, - { - "browser": "ie", - "browser_version": "10.0", - "device": null, - "os": "Windows", - "os_version": "8" - }] -} -``` - -#### Compact `browsers` configuration - -Alternatively, if `os` and `os_version` granularity is not desired, following configuration can be used: -- *browser*_current or *browser*_latest: will assign the latest version of the *browser*. -- *browser*_previous: will assign the previous version of the *browser*. -- *browser*_*version*: will assign the *version* specificed of the *browser*. Minor versions can be concatinated with underscore. - -Example: -```json -"browsers": [ - "chrome_previous", - "chrome_latest", - "firefox_previous", - "firefox_latest", - "ie_6", - "ie_11", - "opera_12_1", - "safari_5_1", + "browsers": [ { "browser": "ie", "browser_version": "10.0", "device": null, "os": "Windows", "os_version": "8" + }, + { + "os": "android", + "os_version": "4.0", + "device": "Samsung Galaxy Nexus" + }, + { + "os": "ios", + "os_version": "7.0", + "device": "iPhone 5S" } -] + ] +} ``` -### Enviroment variables - -* `BROWSERSTACK_USERNAME`: -BrowserStack user name. +#### Compact `browsers` configuration -* `BROWSERSTACK_KEY`: -BrowserStack key. +When `os` and `os_version` granularity is not desired, following configuration can be used: + * `[browser]_current` or *browser*_latest: will assign the latest version of the *browser*. + * `[browser]_previous`: will assign the previous version of the *browser*. + * `[browser]_[version]`: will assign the *version* specificed of the *browser*. Minor versions can be concatinated with underscores. -* `TUNNEL_ID`: -Identifier for the current instance of the tunnel process. In `TRAVIS` setup `TRAVIS_JOB_ID` will be the default identifier. +This can also be mixed with fine-grained configuration. -* `BROWSERSTACK_JSON`: -Path to the browserstack.json file. If null, `browserstack.json` in the root directory will be used. +Example: +```json +{ + "browsers": [ + "chrome_previous", + "chrome_latest", + "firefox_previous", + "firefox_latest", + "ie_6", + "ie_11", + "opera_12_1", + "safari_5_1", + { + "browser": "ie", + "browser_version": "10.0", + "device": null, + "os": "Windows", + "os_version": "8" + } + ] +} +``` ### Proxy support for BrowserStack local + Add the following in `browserstack.json` ```json -... -"proxy": { - "host": "localhost", - "port": 3128, - "username": "foo", - "password": "bar" +{ + "proxy": { + "host": "localhost", + "port": 3128, + "username": "foo", + "password": "bar" + } } -... ``` -### Secure Information +### Supported environment variables + +To avoid duplication of system or user specific information across several configuration files, use these environment variables: -To prevent checking in the BrowserStack `username` and `key` in your -source control, the corresponding environment variables can be used. +* `BROWSERSTACK_USERNAME`: BrowserStack user name. +* `BROWSERSTACK_KEY`: BrowserStack key. +* `TUNNEL_ID`: Identifier for the current instance of the tunnel process. In `TRAVIS` setup `TRAVIS_JOB_ID` will be the default identifier. +* `BROWSERSTACK_JSON`: Path to the browserstack.json file. If null, `browserstack.json` in the root directory will be used. -The environment variables then can be safely provided in the build -configuration. For example, with travis-ci you can follow: -http://about.travis-ci.org/docs/user/build-configuration/#Secure-environment-variables +### Secure Information -## Running tests -Run `browserstack-runner` to run the tests on all the browsers mentioned -in the configuration. +To avoid checking in the BrowserStack `username` and `key` in your source control system, the corresponding environment variables can be used. -You can include this in your test script to automatically run cross -browser tests on every build. +These can also be provided by a build server, for example [using secure environment variables on Travis CI](http://about.travis-ci.org/docs/user/build-configuration/#Secure-environment-variables). From 7557eda8bac70b01c757899a42d691436eeae2e8 Mon Sep 17 00:00:00 2001 From: Rahul Nawani Date: Fri, 7 Nov 2014 17:29:15 +0530 Subject: [PATCH 012/162] bump version to 0.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9bc7125..855e349 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.2.0", + "version": "0.2.1", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 76c65ffde35b8d44abdddc281a4f7c4138f81324 Mon Sep 17 00:00:00 2001 From: Ash Ward Date: Wed, 9 Jul 2014 22:52:33 +0100 Subject: [PATCH 013/162] Removed dependency on Array.forEach from jasmine plugin for IE <= 8 --- lib/_patch/jasmine-plugin.js | 38 +++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/lib/_patch/jasmine-plugin.js b/lib/_patch/jasmine-plugin.js index c8abcfb..14ab9c2 100644 --- a/lib/_patch/jasmine-plugin.js +++ b/lib/_patch/jasmine-plugin.js @@ -1,33 +1,39 @@ (function(){ function countSpecs(suite, results){ - suite.specs.forEach(function(s){ - if(s.passed){ - results.passed++; - }else{ - results.tracebacks.push(s.description); - results.failed++; + for(var i = 0; i < suite.specs.length; ++i) { + if(suite.specs[i].passed){ + results.passed++; + } else { + results.tracebacks.push(suite.specs[i].description); + results.failed++; } - }); - suite.suites.forEach(function(s){ - results = countSpecs(s, results); - }); + } + + for(var i = 0; i < suite.suites.length; ++i) { + if(suite.suites[i]) { + results = countSpecs(suite.suites[i], results); + } + } + return(results); } var checker = setInterval(function(){ if(!jasmine.running){ - var results = {} - var report = jasmine.getJSReport() - var errors = []; + var results = {}; + var report = jasmine.getJSReport(); results.runtime = report.durationSec * 1000; results.total=0; results.passed=0; results.failed=0; results.tracebacks=[]; - jasmine.getJSReport().suites.forEach(function(suite){ - results = countSpecs(suite, results); - }); + for(var i = 0; i < report.suites.length; ++i) { + if(report.suites[i]) { + results = countSpecs(report.suites[i], results); + } + } + results.total = results.passed + results.failed; results.url = window.location.pathname; From a9151861b263fd3752f3e7951d9f8eae359de29d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Fri, 7 Nov 2014 13:01:42 +0100 Subject: [PATCH 014/162] Jasmine: Formats jasmine plugin and fixes the interval clearing It makes no sense to clear the interval while Jasmine is still running, potentially never reporting results when the suite didn't finish after the first interval ran. Closes #88 --- lib/_patch/jasmine-plugin.js | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/_patch/jasmine-plugin.js b/lib/_patch/jasmine-plugin.js index 14ab9c2..55687f0 100644 --- a/lib/_patch/jasmine-plugin.js +++ b/lib/_patch/jasmine-plugin.js @@ -1,45 +1,45 @@ (function(){ function countSpecs(suite, results){ - for(var i = 0; i < suite.specs.length; ++i) { - if(suite.specs[i].passed){ - results.passed++; + for (var i = 0; i < suite.specs.length; ++i) { + if (suite.specs[i].passed){ + results.passed++; } else { results.tracebacks.push(suite.specs[i].description); results.failed++; } } - for(var i = 0; i < suite.suites.length; ++i) { - if(suite.suites[i]) { + for (var i = 0; i < suite.suites.length; ++i) { + if (suite.suites[i]) { results = countSpecs(suite.suites[i], results); } } - - return(results); + + return results; } - var checker = setInterval(function(){ - if(!jasmine.running){ + var checker = setInterval(function() { + if (!jasmine.running) { var results = {}; var report = jasmine.getJSReport(); results.runtime = report.durationSec * 1000; - results.total=0; - results.passed=0; - results.failed=0; - results.tracebacks=[]; + results.total = 0; + results.passed = 0; + results.failed = 0; + results.tracebacks = []; - for(var i = 0; i < report.suites.length; ++i) { - if(report.suites[i]) { - results = countSpecs(report.suites[i], results); - } + for (var i = 0; i < report.suites.length; ++i) { + if (report.suites[i]) { + results = countSpecs(report.suites[i], results); + } } - + results.total = results.passed + results.failed; results.url = window.location.pathname; BrowserStack.post("/_report", results, function(){}); + clearInterval(checker); } - clearInterval(checker); }, 1000); })(); From b92ffafe61bb352dc4521af915489480e4605b68 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 9 Dec 2014 13:12:42 +0100 Subject: [PATCH 015/162] Changed to BrowserStackLocal.exe instead of the jar-file, the tunnel process is now killed when the tests are complete. Added path fix for Windows, it now works with "/" on Windows in the "test_path" config. Fixed the Process signals to work on Windows. --- bin/cli.js | 13 +++++------ lib/config.js | 4 ++++ lib/local.js | 62 ++++++++++++++++++++++++++++----------------------- lib/server.js | 2 +- lib/utils.js | 2 +- 5 files changed, 45 insertions(+), 38 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 1af389a..ff949e3 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -120,7 +120,7 @@ function launchServer() { } function launchBrowser(browser, path) { - var url = 'http://localhost:' + serverPort.toString() + '/' + path; + var url = 'http://localhost:' + serverPort.toString() + '/' + path.replace(/\\/gi, '/'); var browserString = utils.browserString(browser); logger.debug('[%s] Launching', getTestBrowserInfo(browserString, path)); @@ -224,7 +224,7 @@ var statusPoller = { config.status = 1; } - process.kill(process.pid, 'SIGTERM'); + process.exit('SIGTERM'); } } }, activityTimeout * 1000); @@ -245,7 +245,7 @@ var statusPoller = { config.status = 1; } - process.kill(process.pid, 'SIGTERM'); + process.exit('SIGTERM'); } } }, (activityTimeout * 1000)); @@ -308,11 +308,8 @@ try { runTests(); var pid_file = process.cwd() + '/browserstack-run.pid'; fs.writeFileSync(pid_file, process.pid, 'utf-8'); - process.on('SIGINT', function() { - cleanUpAndExit('SIGINT', 1); - }); - process.on('SIGTERM', function() { - cleanUpAndExit('SIGTERM', config.status); + process.on('exit', function(signal){ + cleanUpAndExit(signal, config.status); }); } catch (e) { console.log(e); diff --git a/lib/config.js b/lib/config.js index bd00cb4..f4d9bdd 100644 --- a/lib/config.js +++ b/lib/config.js @@ -61,6 +61,10 @@ if (commit_id) { }); var formatPath = function(path) { + if(/^win/.test(process.platform)){ + path = path.replace(/\//gi, '\\'); + } + if (path.indexOf(pwd) === 0) { path = path.slice(pwd.length + 1); } diff --git a/lib/local.js b/lib/local.js index 9142429..7eb801f 100644 --- a/lib/local.js +++ b/lib/local.js @@ -1,10 +1,10 @@ var Log = require('./logger'), logger = new Log(global.logLevel), - exec = require('child_process').exec, + exec = require('child_process').execFile, fs = require('fs'), http = require('http'), windows = ((process.platform.match(/win32/) || process.platform.match(/win64/)) !== null), - localBinary = process.cwd() + (windows ? '/BrowserStackTunnel.jar' : '/BrowserStackLocal'), + localBinary = process.cwd() + '/BrowserStackLocal' + (windows ? '.exe' : ''), utils = require('./utils'), config = require('./config'); @@ -12,29 +12,21 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { var that = {}; function tunnelLauncher() { - var tunnelCommand = (windows ? 'java -jar ' : '') + localBinary + ' '; - if (config.debug) { - tunnelCommand += ' -v '; - } - tunnelCommand += key + ' '; - tunnelCommand += 'localhost' + ','; - tunnelCommand += port.toString() + ','; - tunnelCommand += '0'; - tunnelCommand += (typeof uniqueIdentifier === 'undefined') ? ' -force -onlyAutomate' : ' -tunnelIdentifier ' + uniqueIdentifier; - tunnelCommand += checkAndAddProxy(); + var tunnelOptions = getTunnelOptions(key, uniqueIdentifier); if (typeof callback !== 'function') { callback = function(){}; } logger.debug('[%s] Launching tunnel', new Date()); - var subProcess = exec(tunnelCommand, function(error, stdout, stderr) { + + var subProcess = exec(localBinary, tunnelOptions, function(error, stdout, stderr) { logger.debug(stderr); logger.debug(error); if (stdout.indexOf('Error') >= 0 || error) { logger.debug('[%s] Tunnel launching failed', new Date()); logger.debug(stdout); - process.kill(process.pid, 'SIGINT'); + process.exit('SIGINT'); } }); @@ -67,19 +59,33 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { that.process = subProcess; } - function checkAndAddProxy() { - var proxy = config.proxy; - if (typeof proxy === 'undefined') { - return ''; + function getTunnelOptions(key, uniqueIdentifier){ + var options = [key]; + + if (config.debug) { + options.push('-v'); + } + + if(!uniqueIdentifier){ + options.push('-force'); + options.push('-onlyAutomate'); + } else { + options.push('-localIdentifier ' + uniqueIdentifier); } - var proxyCommand = ''; - proxyCommand += ' -proxyHost ' + proxy.host; - proxyCommand += ' -proxyPort ' + proxy.port; - if(typeof proxy.username !== 'undefined'){ - proxyCommand += ' -proxyUser ' + proxy.username; - proxyCommand += ' -proxyPass ' + proxy.password; + + var proxy = config.proxy; + + if(proxy){ + options.push('-proxyHost ' + proxy.host); + options.push('-proxyPort ' + proxy.port); + + if(proxy.username && proxy.password){ + options.push('-proxyUser ' + proxy.username); + options.push('-proxyPass ' + proxy.password); + } } - return proxyCommand; + + return options; } fs.exists(localBinary, function(exists) { @@ -87,10 +93,10 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { tunnelLauncher(); return; } - logger.debug('Downloading BrowserStack Local to `%s`', localBinary); + logger.debug('Downloading BrowserStack Local to "%s"', localBinary); var file = fs.createWriteStream(localBinary); - http.get(windows ? 'http://www.browserstack.com/BrowserStackTunnel.jar' : ('http://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal-' + process.platform + '-' + process.arch), + http.get((windows ? 'http://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal.exe' : ('http://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal-' + process.platform + '-' + process.arch)), function(response) { response.pipe(file); @@ -101,7 +107,7 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { }, 100); }).on('error', function(e) { logger.info('Got error while downloading binary: ' + e.message); - process.kill(process.pid, 'SIGINT'); + process.exit('SIGINT'); }); }); }); diff --git a/lib/server.js b/lib/server.js index 7a0da56..5e4d64f 100644 --- a/lib/server.js +++ b/lib/server.js @@ -254,7 +254,7 @@ exports.Server = function Server(bsClient, workers) { config.status = 1; } - process.kill(process.pid, 'SIGTERM'); + process.exit('SIGTERM'); } }); }); diff --git a/lib/utils.js b/lib/utils.js index dfd7c1c..3ede7e6 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -57,7 +57,7 @@ var alertBrowserStack = function alertBrowserStack(subject, content, params, fn) if (typeof params === 'function') { } else { fn = function() { - process.kill(process.pid, 'SIGINT'); + process.exit('SIGINT'); }; } } From 28bca6c1cecbbea9f86baf22dbff86f4b36e1001 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Dec 2014 13:21:46 +0100 Subject: [PATCH 016/162] Styling fixes and removed unnecessary regexp flag --- bin/cli.js | 2 +- lib/config.js | 4 ++-- lib/local.js | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index ff949e3..1e296ed 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -120,7 +120,7 @@ function launchServer() { } function launchBrowser(browser, path) { - var url = 'http://localhost:' + serverPort.toString() + '/' + path.replace(/\\/gi, '/'); + var url = 'http://localhost:' + serverPort.toString() + '/' + path.replace(/\\/g, '/'); var browserString = utils.browserString(browser); logger.debug('[%s] Launching', getTestBrowserInfo(browserString, path)); diff --git a/lib/config.js b/lib/config.js index f4d9bdd..52a7705 100644 --- a/lib/config.js +++ b/lib/config.js @@ -61,8 +61,8 @@ if (commit_id) { }); var formatPath = function(path) { - if(/^win/.test(process.platform)){ - path = path.replace(/\//gi, '\\'); + if (/^win/.test(process.platform)) { + path = path.replace(/\//g, '\\'); } if (path.indexOf(pwd) === 0) { diff --git a/lib/local.js b/lib/local.js index 7eb801f..eed801f 100644 --- a/lib/local.js +++ b/lib/local.js @@ -59,14 +59,14 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { that.process = subProcess; } - function getTunnelOptions(key, uniqueIdentifier){ + function getTunnelOptions(key, uniqueIdentifier) { var options = [key]; if (config.debug) { options.push('-v'); } - if(!uniqueIdentifier){ + if (!uniqueIdentifier) { options.push('-force'); options.push('-onlyAutomate'); } else { @@ -75,11 +75,11 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { var proxy = config.proxy; - if(proxy){ + if (proxy) { options.push('-proxyHost ' + proxy.host); options.push('-proxyPort ' + proxy.port); - if(proxy.username && proxy.password){ + if (proxy.username && proxy.password) { options.push('-proxyUser ' + proxy.username); options.push('-proxyPass ' + proxy.password); } From c24e31de40d968ca26c0a331204557677529a4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Fri, 9 Jan 2015 12:52:15 +0100 Subject: [PATCH 017/162] Ignore tests folder when publishing to npm Shouldn't include all the external repos when publishing. Also ignoring two dot files that aren't needed outside of the repo. --- .npmignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..ba98975 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +.editorconfig +.jshintrc +tests From ed1899e442f2c0e06666bb7ef35652d719c7451d Mon Sep 17 00:00:00 2001 From: Nakul Date: Wed, 14 Jan 2015 12:05:06 +0530 Subject: [PATCH 018/162] removing metro for now. --- lib/configParser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/configParser.js b/lib/configParser.js index 4ab8fb9..20e4895 100644 --- a/lib/configParser.js +++ b/lib/configParser.js @@ -56,7 +56,7 @@ var ConfigParser = { else { var windowsFiltered = ConfigParser.bsBrowsers.filter(function(currentValue, index, array) { - return currentValue.os == 'Windows' && currentValue.browser == browserObject.browser && parseFloat(currentValue.browser_version).toPrecision(4) == parseFloat(browserObject.browser_version).toPrecision(4); + return currentValue.os == 'Windows' && currentValue.browser_version.match(/metro/i) == null && currentValue.browser == browserObject.browser && parseFloat(currentValue.browser_version).toPrecision(4) == parseFloat(browserObject.browser_version).toPrecision(4); }); var osxFiltered = ConfigParser.bsBrowsers.filter(function(currentValue, index, array) { From fb7780db27d13cb464bf610c3449b5d4953e4a38 Mon Sep 17 00:00:00 2001 From: Nakul Date: Wed, 14 Jan 2015 12:52:23 +0530 Subject: [PATCH 019/162] new version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 855e349..a46a8c7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.2.1", + "version": "0.2.2", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From c6a104813f7abd9baa3316f76cf88d06e98de0c2 Mon Sep 17 00:00:00 2001 From: Pat O'Callaghan Date: Wed, 11 Feb 2015 19:18:07 +0000 Subject: [PATCH 020/162] Allow to add custom build string --- README.md | 1 + lib/config.js | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a3f4c43..ff50c1a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ To run browser tests on BrowserStack infrastructure, you need to create a `brows * `test_framework`: Specify test framework which will run the tests. Currently supporting qunit, jasmine, jasmine2 and mocha. * `timeout`: Specify worker timeout with BrowserStack. * `browsers`: A list of browsers on which tests are to be run. Find a [list of all supported browsers and platforms on browerstack.com](http://www.browserstack.com/list-of-browsers-and-platforms?product=live). + * `build`: A string to identify your test run in Browserstack. In `TRAVIS` setup `TRAVIS_COMMIT` will be the default identifier. * `proxy`: Specify a proxy to use for the local tunnel. Object with `host`, `port`, `username` and `password` properties. A sample configuration file: diff --git a/lib/config.js b/lib/config.js index bd00cb4..5471dd5 100644 --- a/lib/config.js +++ b/lib/config.js @@ -47,10 +47,8 @@ if (!config.project) { var commit_id = process.env.TRAVIS_COMMIT; -if (commit_id) { - config.build = 'Commit-' + commit_id.slice(0, commit_id.length / 2); -} { - config.build = 'Local run, ' + new Date().toISOString(); +if(!config.build) { + config.build = commit_id ? 'Commit-' + commit_id.slice(0, commit_id.length / 2) : 'Local run, ' + new Date().toISOString(); } ['username', 'key', 'test_path', 'browsers'].forEach(function(param) { From 74dc47a27311e6a1e1d9bb171c7530112c094b29 Mon Sep 17 00:00:00 2001 From: Patrick Kettner Date: Tue, 24 Feb 2015 12:33:50 -0500 Subject: [PATCH 021/162] use isArray rather than toString --- bin/cli.js | 4 ++-- lib/config.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 1af389a..c0e2e62 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -177,7 +177,7 @@ function launchBrowser(browser, path) { function launchBrowsers(config, browser) { setTimeout(function () { - if (Object.prototype.toString.call(config.test_path) === '[object Array]'){ + if (Array.isArray(config.test_path)){ config.multipleTest = config.test_path.length > 1? true : false; launchBrowser(browser, config.test_path[0]); } else { @@ -276,7 +276,7 @@ function runTests() { launchServer(); tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, function () { statusPoller.start(); - var total_runs = config.browsers.length * (Object.prototype.toString.call(config.test_path) === '[object Array]' ? config.test_path.length : 1); + var total_runs = config.browsers.length * (Array.isArray(config.test_path) ? config.test_path.length : 1); logger.info('Launching ' + config.browsers.length + ' worker(s) for ' + total_runs + ' run(s).'); browsers.forEach(function(browser) { if (browser.browser_version === 'latest') { diff --git a/lib/config.js b/lib/config.js index bd00cb4..8602408 100644 --- a/lib/config.js +++ b/lib/config.js @@ -73,7 +73,7 @@ var formatPath = function(path) { config.tunnelIdentifier = process.env.TUNNEL_ID || process.env.TRAVIS_JOB_ID || process.env.TRAVIS_BUILD_ID; -if (Object.prototype.toString.call(config.test_path) === '[object Array]') { +if (Array.isArray(config.test_path)) { config.test_path.forEach(function(path) { path = formatPath(path); }); From a96380effaf11701545d609051c2d13138f0cf92 Mon Sep 17 00:00:00 2001 From: Patrick Kettner Date: Wed, 25 Feb 2015 02:29:34 -0500 Subject: [PATCH 022/162] update live_server branch to latest master --- lib/config.js | 19 +++--- lib/proxy.js | 34 ++++++++++ lib/server.js | 174 +++++++++++++++++++++++++++++--------------------- 3 files changed, 146 insertions(+), 81 deletions(-) create mode 100644 lib/proxy.js diff --git a/lib/config.js b/lib/config.js index bd00cb4..0f110d7 100644 --- a/lib/config.js +++ b/lib/config.js @@ -53,7 +53,7 @@ if (commit_id) { config.build = 'Local run, ' + new Date().toISOString(); } -['username', 'key', 'test_path', 'browsers'].forEach(function(param) { +['username', 'key', 'browsers', 'test_path'].forEach(function(param) { if (typeof config[param] === 'undefined') { console.error('Configuration parameter `%s` is required.', param); process.exit(1); @@ -73,13 +73,16 @@ var formatPath = function(path) { config.tunnelIdentifier = process.env.TUNNEL_ID || process.env.TRAVIS_JOB_ID || process.env.TRAVIS_BUILD_ID; -if (Object.prototype.toString.call(config.test_path) === '[object Array]') { - config.test_path.forEach(function(path) { - path = formatPath(path); - }); -} else { - //Backward Compatibility, if test_path is not array of path - config.test_path = formatPath(config.test_path); +if (typeof(config['test_server']) === 'undefined') { + if (Object.prototype.toString.call(config.test_path) === '[object Array]') { + config.test_path.forEach(function(path) { + path = formatPath(path); + }); + + } else { + //Backward Compatibility, if test_path is not array of path + config.test_path = formatPath(config.test_path); + } } config.status = 0; diff --git a/lib/proxy.js b/lib/proxy.js new file mode 100644 index 0000000..f272b95 --- /dev/null +++ b/lib/proxy.js @@ -0,0 +1,34 @@ +var http = require('http'), + url = require('url'); + +var ProxyServer = { + onRequest: function(client_req, client_res, host, callback) { + var proxyUrl = url.parse(host); + var path = url.parse(host); + var options = { + path: client_req.url, + hostname: proxyUrl.hostname, + port: proxyUrl.port, + method: client_req.method, + headers: client_req.headers + }; + + var proxy = http.request(options, function (res) { + data = ""; + res.on('data', function(chunk) { + data += chunk; + }); + res.on('end', function() { + //Replace + callback(res, data); + }); + }).on('error', function(e) { + client_res.writeHead(500); + client_res.write('error: ' + e.toString()); + client_res.end(); + }); + proxy.end(); + } +}; + +exports.proxyServer = ProxyServer; diff --git a/lib/server.js b/lib/server.js index 7a0da56..13cd65c 100644 --- a/lib/server.js +++ b/lib/server.js @@ -7,6 +7,7 @@ var Log = require('./logger'), qs = require('querystring'), utils = require('./utils'), config = require('../lib/config'), + proxyServer = require('./proxy').proxyServer, chalk = require('chalk'); var mimeTypes = { @@ -30,7 +31,7 @@ function getTestBrowserInfo(worker) { exports.Server = function Server(bsClient, workers) { - function handleFile(filename, request, response) { + function handleFile(filename, request, response, doNotUseProxy) { var url_parts = url.parse(request.url, true); var query = url_parts.query; @@ -40,95 +41,122 @@ exports.Server = function Server(bsClient, workers) { logger.debug('[%s] Acknowledged', getTestBrowserInfo(worker)); } - fs.exists(filename, function(exists) { - if (!exists) { - response.writeHead(404, { - 'Content-Type': 'text/plain' - }); - response.write('404 Not Found\n'); - response.end(); - return; - } + var getReporterPatch = function (mimeType) { + var scripts = [ + 'json2.js', + 'browserstack.js', + ]; + + var framework_scripts = { + 'qunit': ['qunit-plugin.js'], + 'jasmine': ['jasmine-jsreporter.js', 'jasmine-plugin.js'], + 'jasmine2': ['jasmine2-plugin.js'], + 'mocha': ['mocha-plugin.js'] + }; - if (fs.lstatSync(filename).isDirectory()) { - filename = filename + (filename.lastIndexOf('/') === filename.length - 1 ? '' : '/') + 'index.html'; + var filePath = path.relative(process.cwd(), filename); + var pathMatches; + + if (typeof config.test_path === 'object') { + pathMatches = (config.test_path.indexOf(filePath) !== -1); + } else { + pathMatches = (filePath === config.test_path); } - fs.readFile(filename, {encoding: 'utf8'}, function(err, file) { + if (pathMatches && mimeType === 'text/html') { + var framework = config['test_framework']; + var tag_name = (framework === 'mocha') ? 'head' : 'body'; + var patch = '$1'; - if (err) { - response.writeHead(500, { - 'Content-Type': 'text/plain' + scripts.forEach(function(script) { + patch += '\n'; + }); + + // adding framework scripts + if (framework === 'jasmine') { + framework_scripts['jasmine'].forEach(function(script) { + patch += '\n'; + }); + patch += '\n'; + } else if (framework === 'jasmine2') { + framework_scripts['jasmine2'].forEach(function(script) { + patch += '\n'; + }); + } else if (framework === 'mocha') { + framework_scripts['mocha'].forEach(function(script) { + patch += '\n'; + }); + patch += '\n'; + } else { + framework_scripts['qunit'].forEach(function(script) { + patch += '\n'; }); - response.write(err + '\n'); - response.end(); - return; } + patch += ''; + return patch; + } + }; - var mimeType = mimeTypes[path.extname(filename).split('.')[1]]; - response.writeHead(200, { - 'Content-Type': mimeType + '; charset=utf-8', - }); + var writeResponse = function(err, data) { - var scripts = [ - 'json2.js', - 'browserstack.js', - ]; + if (err) { + response.writeHead(500, { + 'Content-Type': 'text/plain' + }); + response.write(err + '\n'); + response.end(); + return; + } - var framework_scripts = { - 'qunit': ['qunit-plugin.js'], - 'jasmine': ['jasmine-jsreporter.js', 'jasmine-plugin.js'], - 'jasmine2': ['jasmine2-plugin.js'], - 'mocha': ['mocha-plugin.js'] - }; + var mimeType = mimeTypes[path.extname(filename).split('.')[1]]; + response.writeHead(200, { + 'Content-Type': mimeType + '; charset=utf-8', + }); + var tag_name = (config['test_framework'] === 'mocha') ? 'head' : 'body'; + var matcher = new RegExp('(.*)<\/' + tag_name + '>'); ///(.*)<\/body>/; + var patch = getReporterPatch(mimeType); + data = data.replace(matcher, patch); - var filePath = path.relative(process.cwd(), filename); - var pathMatches; + response.write(data); + response.end(); + }; - if (typeof config.test_path === 'object') { - pathMatches = (config.test_path.indexOf(filePath) !== -1); - } else { - pathMatches = (filePath === config.test_path); + if (!doNotUseProxy && config.test_server) { + proxyServer.onRequest(request, response, config.test_server, function(remote_response, response_data) { + var mimeType = mimeTypes[path.extname(filename).split('.')[1]]; + var final_data = response_data; + var headers = remote_response.headers; + if (mimeType === 'text/html') { + var matcher = /(.*)<\/head>/; + var patch = getReporterPatch(mimeType); + final_data = response_data.replace(matcher, patch); + headers['content-length'] = final_data.length; } - if (pathMatches && mimeType === 'text/html') { - var framework = config['test_framework']; - var tag_name = (framework === 'mocha') ? 'head' : 'body'; - var matcher = new RegExp('(.*)<\/' + tag_name + '>'); ///(.*)<\/body>/; - var patch = '$1'; - scripts.forEach(function(script) { - patch += '\n'; - }); + response.writeHead(remote_response.statusCode, headers); + response.write(final_data); + response.end(); + return; + }); - // adding framework scripts - if (framework === 'jasmine') { - framework_scripts['jasmine'].forEach(function(script) { - patch += '\n'; - }); - patch += '\n'; - } else if (framework === 'jasmine2') { - framework_scripts['jasmine2'].forEach(function(script) { - patch += '\n'; - }); - } else if (framework === 'mocha') { - framework_scripts['mocha'].forEach(function(script) { - patch += '\n'; - }); - patch += '\n'; - } else { - framework_scripts['qunit'].forEach(function(script) { - patch += '\n'; - }); - } - patch += ''; + } else { - file = file.replace(matcher, patch); + fs.exists(filename, function(exists) { + if (!exists) { + response.writeHead(404, { + 'Content-Type': 'text/plain' + }); + response.write('404 Not Found\n'); + response.end(); + return; } + if (fs.lstatSync(filename).isDirectory()) { + filename = filename + (filename.lastIndexOf('/') === filename.length - 1 ? '' : '/') + 'index.html'; + } - response.write(file); - response.end(); + fs.readFile(filename, {encoding: 'utf8'}, writeResponse); }); - }); + } } function parseBody(body) { @@ -267,7 +295,7 @@ exports.Server = function Server(bsClient, workers) { response.end(); }, '_patch': function patchHandler(uri, body, request, response) { - handleFile(path.join(__dirname, uri), request, response); + handleFile(path.join(__dirname, uri), request, response, true); }, '_default': function defaultHandler(uri, body, request, response) { handleFile(path.join(process.cwd(), uri), request, response); From a4e2a0a29f43a2385d4f0f2cef0af11374996490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Tue, 31 Mar 2015 15:51:35 +0200 Subject: [PATCH 023/162] Utils: Compress timestamp, include content argument in log --- lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index dfd7c1c..ffc5b31 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -51,7 +51,7 @@ var alertBrowserStack = function alertBrowserStack(subject, content, params, fn) var urlObject = url.parse(endpoint); var context = config.alert_context || 'Runner alert'; - logger.info('[%s] [%s] %s', new Date(), context, subject); + logger.info('[%s] [%s] %s, %s', new Date().toISOString(), context, subject, content); if (typeof fn !== 'function') { if (typeof params === 'function') { From a63a3fa3ffc16333d4039eec124f9f7020f6210b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Tue, 31 Mar 2015 15:53:38 +0200 Subject: [PATCH 024/162] 0.2.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a46a8c7..0e1a33a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.2.2", + "version": "0.2.3", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 56134e22a20bee1fe26be10c27c5e3fe66e65d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Tue, 31 Mar 2015 16:28:03 +0200 Subject: [PATCH 025/162] Update worker config when changing worker URL so that timeouts log properly Without this, the log for timeouts would always show the initial URL, which can change when running multiple pages per worker. --- lib/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/server.js b/lib/server.js index 7a0da56..57ba77e 100644 --- a/lib/server.js +++ b/lib/server.js @@ -167,6 +167,7 @@ exports.Server = function Server(bsClient, workers) { } url += '_worker_key=' + worker._worker_key + '&_browser_string=' + getTestBrowserInfo(worker) ; worker.test_path = next_path; + worker.config.url = next_path; bsClient.changeUrl(worker.id, {url: url}, function() { callback(true); }); From 49334bf541855a24e42e7a29141f440a8a156469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Scott=20Gonz=C3=A1lez?= Date: Tue, 31 Mar 2015 11:49:41 -0400 Subject: [PATCH 026/162] Server: Add AMD support to QUnit patch file Closes #115 --- lib/_patch/qunit-plugin.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/_patch/qunit-plugin.js b/lib/_patch/qunit-plugin.js index 774ab1d..8ef3f10 100644 --- a/lib/_patch/qunit-plugin.js +++ b/lib/_patch/qunit-plugin.js @@ -1,7 +1,12 @@ // For logging assertions on the console, here's what grunt-contrib-qunit uses: // https://github.com/gruntjs/grunt-contrib-qunit/blob/784597023e7235337ca9c0651aa45124a2d72341/tasks/qunit.js#L45 -(function() { - +(function (factory) { + if (typeof define === 'function' && define.amd) { + require(['qunit'], factory); + } else { + factory(QUnit); + } +}(function(QUnit) { var failedAssertions = []; var options, currentModule, @@ -54,5 +59,4 @@ results.url = window.location.pathname; BrowserStack.post("/_report", results, function(){}); }); - -})(); +})); From 66ed8afa4c0c326f2465c4cdc80e2d9c61344244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Tue, 31 Mar 2015 17:58:09 +0200 Subject: [PATCH 027/162] 0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0e1a33a..4e190e3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.2.3", + "version": "0.3.0", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 4a121db4718765a93172be366b65dce00cf62d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20P=C3=B6yh=C3=B6nen?= Date: Wed, 8 Apr 2015 02:13:57 +0300 Subject: [PATCH 028/162] Support IE8 in Mocha tests Replaced Date.now() with (new Date).getTime() because Date.now() is not supported until IE9. --- lib/_patch/mocha-plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_patch/mocha-plugin.js b/lib/_patch/mocha-plugin.js index 1e84664..57d0648 100644 --- a/lib/_patch/mocha-plugin.js +++ b/lib/_patch/mocha-plugin.js @@ -54,7 +54,7 @@ runner.on('end', function() { results = {}; - results.runtime = Date.now() - start; + results.runtime = (new Date).getTime() - start; results.total = passes + failures; results.passed = passes; results.failed = failures; From 1a5bf9099ea60a13eff2deb3be31cf3551516a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Thu, 14 May 2015 19:29:43 +0200 Subject: [PATCH 029/162] 0.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4e190e3..6a2f87c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.3.0", + "version": "0.3.1", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From fce972585207d8b6937d42bd5ddcda2d4079d9e8 Mon Sep 17 00:00:00 2001 From: Imran Date: Fri, 3 Jul 2015 14:52:53 +0530 Subject: [PATCH 030/162] 0.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6a2f87c..8115a1f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.3.1", + "version": "0.3.2", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From d8ba2902ea4719ff5a29c62a6fecf8c96377e1e3 Mon Sep 17 00:00:00 2001 From: Imran Date: Sun, 12 Jul 2015 22:47:37 +0530 Subject: [PATCH 031/162] 0.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8115a1f..088ccb0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.3.2", + "version": "0.3.3", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From eaab15188f1e61e918015794cb0438814dbd1f7b Mon Sep 17 00:00:00 2001 From: Imran Date: Mon, 13 Jul 2015 21:13:08 +0530 Subject: [PATCH 032/162] 0.3.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 088ccb0..6d49b0b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.3.3", + "version": "0.3.4", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 5c3d9195bbd9ac504b332079ff7a955d3ad7dc8e Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Fri, 17 Jul 2015 13:10:01 -0400 Subject: [PATCH 033/162] Prevent fatal error when it gets a 403 Error This commit introduces a handler for `client.getWorkers` when it receives an error argument. This way it won't break JS from running, but instead will just report the error and continue. This is not the best way to fix this problem, but this will prevent false statements on CI runs, creating warnings rather than fatal errros. --- bin/cli.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/cli.js b/bin/cli.js index 0f40fb6..65fb6a4 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -193,6 +193,10 @@ var statusPoller = { start: function() { statusPoller.poller = setInterval(function () { client.getWorkers(function (err, _workers) { + if (!_workers) { + logger.info(chalk.red('Error found: ' + err)); + return; + } _workers.filter(function(currentValue) { return currentValue.status === 'running' && workerKeys[currentValue.id] && !workerKeys[currentValue.id].marked; }).forEach(function(_worker) { From 2e3f4a3c42f909cf3b957cd5bdfa5567a7e5f0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rn=20Zaefferer?= Date: Fri, 24 Apr 2015 14:14:27 +0200 Subject: [PATCH 034/162] Local: Revert writing tunnel file into working dir, document npm fix Revert "On linux, EACCES open ... BrowserStackLocal error. Fixes #91" This reverts commit c27a2adb88c02c72842dadb2b6fe4cce17137e28 Documents how to avoid the issue described in #91 by configuring npm properly. Ref #91 Fixes #105 --- README.md | 6 ++++++ lib/local.js | 14 +++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ff50c1a..386fb60 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,12 @@ You can also install locally and run the local binary: npm install browserstack-runner node_modules/.bin/browserstack-runner +If you're getting an error `EACCES open ... BrowserStackLocal`, configure npm to install modules using something other than the default "nobody" user: + + npm -g config set user [user] + +Where `[user]` is replaced with a local user with enough permissions. + ## Configuration To run browser tests on BrowserStack infrastructure, you need to create a `browserstack.json` file in project's root directory (the directory from which tests are run), by running this command: diff --git a/lib/local.js b/lib/local.js index eed801f..1f2b38d 100644 --- a/lib/local.js +++ b/lib/local.js @@ -4,7 +4,7 @@ var Log = require('./logger'), fs = require('fs'), http = require('http'), windows = ((process.platform.match(/win32/) || process.platform.match(/win64/)) !== null), - localBinary = process.cwd() + '/BrowserStackLocal' + (windows ? '.exe' : ''), + localBinary = __dirname + '/BrowserStackLocal' + (windows ? '.exe' : ''), utils = require('./utils'), config = require('./config'); @@ -61,30 +61,30 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { function getTunnelOptions(key, uniqueIdentifier) { var options = [key]; - + if (config.debug) { options.push('-v'); } - + if (!uniqueIdentifier) { options.push('-force'); options.push('-onlyAutomate'); } else { options.push('-localIdentifier ' + uniqueIdentifier); } - + var proxy = config.proxy; - + if (proxy) { options.push('-proxyHost ' + proxy.host); options.push('-proxyPort ' + proxy.port); - + if (proxy.username && proxy.password) { options.push('-proxyUser ' + proxy.username); options.push('-proxyPass ' + proxy.password); } } - + return options; } From b1a8b921a9d84bcdeaa269850c2b9d8160319887 Mon Sep 17 00:00:00 2001 From: Imran Date: Wed, 22 Jul 2015 19:13:53 +0530 Subject: [PATCH 035/162] 0.3.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d49b0b..2fda7dd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.3.4", + "version": "0.3.5", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From b73192ca2e01d68b4c2b524abf7aad1cbd12f071 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Thu, 16 Jul 2015 17:56:35 +0530 Subject: [PATCH 036/162] Retry opening URL if browser-worker fails to open page within ackTimeout. --- bin/cli.js | 102 +++++++++++++++++++++++++++++++++++++++++++------- lib/server.js | 38 +++++++------------ 2 files changed, 102 insertions(+), 38 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 65fb6a4..b1df810 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -20,6 +20,7 @@ var Log = require('../lib/logger'), logger = new Log(global.logLevel), BrowserStack = require('browserstack'), fs = require('fs'), + qs = require('querystring'), chalk = require('chalk'), config = require('../lib/config'), utils = require('../lib/utils'), @@ -32,6 +33,7 @@ var Log = require('../lib/logger'), server, timeout, activityTimeout, + ackTimeout, workers = {}, workerKeys = {}, tunnelingAgent, @@ -43,6 +45,7 @@ function terminateAllWorkers(callback) { var worker = workers[key]; if(worker) { logger.debug('[%s] Terminated', worker.string); + clearTimeout(worker.ackTimeout); clearTimeout(worker.activityTimeout); clearTimeout(worker.testActivityTimeout); delete workers[key]; @@ -109,9 +112,24 @@ function getTestBrowserInfo(browserString, path) { if (config.multipleTest) { info += ', ' + path; } + return info; } + +function buildTestUrl(test_path, worker_key, browser_string) { + var url = 'http://localhost:' + serverPort + '/' + test_path; + + var querystring = qs.stringify({ + _worker_key: worker_key, + _browser_string: browser_string + }); + + url += ((url.indexOf('?') > 0) ? '&' : '?') + querystring; + return url; +} + + function launchServer() { logger.debug('Launching server on port:', serverPort); @@ -120,20 +138,12 @@ function launchServer() { } function launchBrowser(browser, path) { - var url = 'http://localhost:' + serverPort.toString() + '/' + path.replace(/\\/g, '/'); - var browserString = utils.browserString(browser); - logger.debug('[%s] Launching', getTestBrowserInfo(browserString, path)); - var key = utils.uuid(); + var browserString = utils.browserString(browser); + var browserInfo = getTestBrowserInfo(browserString, path); + logger.debug('[%s] Launching', browserInfo); - if (url.indexOf('?') > 0) { - url += '&'; - } else { - url += '?'; - } - - url += '_worker_key=' + key + '&_browser_string=' + browserString; - browser['url'] = url; + browser.url = buildTestUrl(path.replace(/\\/g, '/'), key, browserString); if (config.project) { browser.project = config.project; @@ -153,6 +163,7 @@ function launchBrowser(browser, path) { timeout = 300; } activityTimeout = timeout - 10; + ackTimeout = parseInt(config.ackTimeout) || 60; client.createWorker(browser, function (err, worker) { if (err || typeof worker !== 'object') { @@ -169,10 +180,13 @@ function launchBrowser(browser, path) { worker.string = browserString; worker.test_path = path; worker.path_index = 0; + + // attach helper methods to manage worker state + attachWorkerHelpers(worker); + workers[key] = worker; workerKeys[worker.id] = {key: key, marked: false}; }); - } function launchBrowsers(config, browser) { @@ -187,6 +201,63 @@ function launchBrowsers(config, browser) { }, 100); } + +function attachWorkerHelpers(worker) { + // TODO: Consider creating instances of a proper 'Worker' class + + worker.buildUrl = function buildUrl(test_path) { + return buildTestUrl(test_path || this.test_path, this._worker_key, this.getTestBrowserInfo()); + }; + + worker.getTestBrowserInfo = function getTestBrowserInfo(test_path) { + var info = this.string; + if (config.multipleTest) { + info += ', ' + (test_path || this.test_path); + } + return info; + }; + + worker.awaitAck = function awaitAck() { + var self = this; + + if (this.ackTimeout) { + // Already awaiting ack, or awaited ack once and failed + return; + } + + this.ackTimeout = setTimeout(function () { + if (self.isAckd) { + // Already ack'd + return; + } + + // worker has not acknowledged itself in 60 sec, reopen url + client.changeUrl(self.id, { url: self.buildUrl() }, function () { + logger.debug("[%s] Sent Request to reload url", self.getTestBrowserInfo()); + }); + + }, ackTimeout * 1000); + + logger.debug('[%s] Awaiting ack', this.getTestBrowserInfo()); + }; + + worker.markAckd = function markAckd() { + this.resetAck(); + this.isAckd = true; + + logger.debug('[%s] Received ack', this.getTestBrowserInfo()); + }; + + worker.resetAck = function resetAck() { + clearTimeout(this.ackTimeout); + this.ackTimeout = null; + this.isAckd = false; + }; + + return worker; +} + + var statusPoller = { poller: null, @@ -208,10 +279,13 @@ var statusPoller = { if (_worker.status === 'running') { //clearInterval(statusPoller); - logger.debug('[%s] Launched', getTestBrowserInfo(worker.string, worker.test_path)); + logger.debug('[%s] Launched', worker.getTestBrowserInfo()); worker.launched = true; workerData.marked = true; + // Await ack from browser-worker + worker.awaitAck(); + worker.activityTimeout = setTimeout(function () { if (!worker.acknowledged) { var subject = 'Worker inactive for too long: ' + worker.string; diff --git a/lib/server.js b/lib/server.js index 09e17c5..fabc205 100644 --- a/lib/server.js +++ b/lib/server.js @@ -20,14 +20,6 @@ var mimeTypes = { 'css': 'text/css' }; -function getTestBrowserInfo(worker) { - var info = worker.string; - if(config.multipleTest) { - info += ', ' + worker.test_path; - } - return info; -} - exports.Server = function Server(bsClient, workers) { @@ -37,8 +29,7 @@ exports.Server = function Server(bsClient, workers) { if (query._worker_key && workers[query._worker_key]) { var worker = workers[query._worker_key]; - worker.acknowledged = true; - logger.debug('[%s] Acknowledged', getTestBrowserInfo(worker)); + worker.markAckd(); } var getReporterPatch = function (mimeType) { @@ -187,18 +178,14 @@ exports.Server = function Server(bsClient, workers) { function checkAndTerminateWorker(worker, callback) { var next_path = getNextTestPath(worker); if (next_path) { - var url = 'http://localhost:' + 8888 + '/' + next_path; - if (url.indexOf('?') > 0) { - url += '&'; - } else { - url += '?'; - } - url += '_worker_key=' + worker._worker_key + '&_browser_string=' + getTestBrowserInfo(worker) ; + var url = worker.buildUrl(next_path); worker.test_path = next_path; worker.config.url = next_path; - bsClient.changeUrl(worker.id, {url: url}, function() { + + bsClient.changeUrl(worker.id, { url: url }, function () { callback(true); }); + } else { bsClient.terminateWorker(worker.id, callback); } @@ -225,7 +212,7 @@ exports.Server = function Server(bsClient, workers) { if (query.tracebacks) { query.tracebacks.forEach(function(traceback) { - logger.info('[%s] ' + chalk.red('Error:'), getTestBrowserInfo(worker), formatTraceback(traceback)); + logger.info('[%s] ' + chalk.red('Error:'), worker.getTestBrowserInfo(), formatTraceback(traceback)); }); } response.end(); @@ -244,19 +231,19 @@ exports.Server = function Server(bsClient, workers) { logger.info('[%s] Null response from remote Browser', request.headers['x-browser-string']); } else { if (query.tracebacks && query.tracebacks.length > 0) { - logger.info('[%s] ' + chalk['red']('Tracebacks:'), getTestBrowserInfo(worker)); + logger.info('[%s] ' + chalk['red']('Tracebacks:'), worker.getTestBrowserInfo()); query.tracebacks.forEach(function(traceback) { logger.info(traceback); }); } var color = query.failed ? 'red' : 'green'; - logger.info('[%s] ' + chalk[color](query.failed ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed; ran for %dms', getTestBrowserInfo(worker), query.total, query.passed, query.failed, query.runtime); + logger.info('[%s] ' + chalk[color](query.failed ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed; ran for %dms', worker.getTestBrowserInfo(), query.total, query.passed, query.failed, query.runtime); config.status += query.failed; } bsClient.takeScreenshot(worker.id, function(error, screenshot) { if (!error && screenshot.url && query && query.failed) { - logger.info('[%s] ' + chalk.yellow('Screenshot:') + ' %s', getTestBrowserInfo(worker), screenshot.url); + logger.info('[%s] ' + chalk.yellow('Screenshot:') + ' %s', worker.getTestBrowserInfo(), screenshot.url); } checkAndTerminateWorker(worker, function(reusedWorker) { @@ -265,12 +252,15 @@ exports.Server = function Server(bsClient, workers) { } if (reusedWorker) { - logger.debug('[%s] Reused', getTestBrowserInfo(worker)); + logger.debug('[%s] Reused', worker.getTestBrowserInfo()); + worker.resetAck(); + worker.awaitAck(); return; } - logger.debug('[%s] Terminated', getTestBrowserInfo(worker)); + logger.debug('[%s] Terminated', worker.getTestBrowserInfo()); + clearTimeout(workers[uuid].ackTimeout); clearTimeout(workers[uuid].activityTimeout); clearTimeout(workers[uuid].testActivityTimeout); delete workers[uuid]; From 1f9698ae96da3e08a8e7a02cdc8ff080026e1149 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Fri, 31 Jul 2015 20:25:45 +0530 Subject: [PATCH 037/162] Bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2fda7dd..fdb8757 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.3.5", + "version": "0.3.6", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From b919bfd5732df66cb0d71e1792ca8358fe04b7dc Mon Sep 17 00:00:00 2001 From: Josh Duff Date: Sat, 8 Aug 2015 16:21:08 -0500 Subject: [PATCH 038/162] Don't assume qunit is the default test framework --- lib/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index fabc205..3094f47 100644 --- a/lib/server.js +++ b/lib/server.js @@ -78,7 +78,7 @@ exports.Server = function Server(bsClient, workers) { patch += '\n'; }); patch += '\n'; - } else { + } else if (framework === 'qunit') { framework_scripts['qunit'].forEach(function(script) { patch += '\n'; }); From fbfbc4bc77df1f1145237a3ac2e79e653e7be97f Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Sun, 9 Aug 2015 20:50:32 +0530 Subject: [PATCH 039/162] Minor jshint fixes --- .jshintrc | 6 +++++- bin/cli.js | 2 +- lib/proxy.js | 3 +-- lib/utils.js | 2 -- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.jshintrc b/.jshintrc index 036ffc4..a7fe4db 100644 --- a/.jshintrc +++ b/.jshintrc @@ -12,5 +12,9 @@ "sub": true, "trailing": true, "undef": true, - "unused": true + "unused": true, + "predef": [ + "require", + "global" + ] } diff --git a/bin/cli.js b/bin/cli.js index b1df810..bd6188b 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -233,7 +233,7 @@ function attachWorkerHelpers(worker) { // worker has not acknowledged itself in 60 sec, reopen url client.changeUrl(self.id, { url: self.buildUrl() }, function () { - logger.debug("[%s] Sent Request to reload url", self.getTestBrowserInfo()); + logger.debug('[%s] Sent Request to reload url', self.getTestBrowserInfo()); }); }, ackTimeout * 1000); diff --git a/lib/proxy.js b/lib/proxy.js index f272b95..1c352dc 100644 --- a/lib/proxy.js +++ b/lib/proxy.js @@ -4,7 +4,6 @@ var http = require('http'), var ProxyServer = { onRequest: function(client_req, client_res, host, callback) { var proxyUrl = url.parse(host); - var path = url.parse(host); var options = { path: client_req.url, hostname: proxyUrl.hostname, @@ -14,7 +13,7 @@ var ProxyServer = { }; var proxy = http.request(options, function (res) { - data = ""; + var data = ''; res.on('data', function(chunk) { data += chunk; }); diff --git a/lib/utils.js b/lib/utils.js index 296f47a..b89e26b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -7,8 +7,6 @@ var Log = require('./logger'), String.prototype.escapeSpecialChars = function() { return this.replace(/\n/g, '\\n') - // TODO what is this supposed to do? JSHint considers this "Bad or unnecessary escaping." - .replace(/\\s/g, '\s') .replace(/\\\'/, '\''); }; From 4b334b5879a56715036b7945d8f9fcb119df12c0 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Sun, 9 Aug 2015 20:51:08 +0530 Subject: [PATCH 040/162] Utils: escapeSpecialChars now handles escape sequences that may break JSON.parse() Data posted by the client may contain stacktraces and other strings that contain escape sequences such as \n. escapeSpecialChars now handles a wider set of whitespace escape sequences that would otherwise break JSON.parse. May require support for more escape chars, but this might suffice for most cases. Fixes #99 --- lib/utils.js | 8 +++++++- tests/unit/utils_spec.js | 20 ++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index b89e26b..2a1bcd2 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -7,7 +7,13 @@ var Log = require('./logger'), String.prototype.escapeSpecialChars = function() { return this.replace(/\n/g, '\\n') - .replace(/\\\'/, '\''); + .replace(/\r/g, '\\r') + .replace(/\t/g, '\\t') + .replace(/\f/g, '\\f') + .replace(/\u0008/g, '\\u0008') // \b + .replace(/\v/g, '\\u000b') // \v + .replace(/\0/g, '\\u0000') // \0 + .replace(/\\\'/, '\''); // TODO: check why this exists }; var titleCase = function toTitleCase(str) { diff --git a/tests/unit/utils_spec.js b/tests/unit/utils_spec.js index 73e6eee..62b75b9 100644 --- a/tests/unit/utils_spec.js +++ b/tests/unit/utils_spec.js @@ -6,7 +6,7 @@ describe('Utilities', function(){ assert.equal("Hello", utils.titleCase("hello")); assert.equal("Are You Serious?", utils.titleCase("are you serious?")); }); - + it('should generate 32 char uuid', function(){ assert.equal(5, utils.uuid().split("-").length); assert.equal(32 + 4, utils.uuid().length); @@ -16,7 +16,7 @@ describe('Utilities', function(){ assert.notEqual(utils.uuid(), utils.uuid()); assert.notEqual(utils.uuid(), utils.uuid()); }); - + it('should generate proper browser string for config', function(){ var chrome_mac = {os: "OS X", os_version: "Mavericks", "browser": "chrome", "browser_version": "latest"}; var chrome_windows = {os: "Windows", os_version: "XP", "browser": "chrome", "browser_version": "latest"}; @@ -24,16 +24,28 @@ describe('Utilities', function(){ var ie_windows = {os: "Windows", os_version: "7", "browser": "ie", "browser_version": "9.0"}; var iOS = {os: "iOS", os_version: "6.0", device: "iPhone 5"}; var androidConfig = {os: "android", os_version: "4.1"}; - + assert.equal("OS X Mavericks, Chrome latest", utils.browserString(chrome_mac)); assert.equal("Windows XP, Chrome latest", utils.browserString(chrome_windows)); assert.equal("Windows 7, Internet Explorer 9.0", utils.browserString(ie_windows)); assert.equal("iOS 6.0, iPhone 5", utils.browserString(iOS)); assert.equal("android 4.1", utils.browserString(androidConfig)); }); - + it('should return number of keys for this object', function(){ assert.equal(0, utils.objectSize({})); assert.equal(1, utils.objectSize({a: 2})); }); + + it('should escape special characters incompatible with JSON.parse', function () { + var testString = '{"tracebacks":[{"actual":null,"message":"Died on test #1 at http://localhost:8888/test/main/globals.js:43:7\n at http://localhost:8888/test/main/globals.js:67:2: Error","testName":"globals: Exported assertions"}]}'; + var expectString = '{"tracebacks":[{"actual":null,"message":"Died on test #1 at http://localhost:8888/test/main/globals.js:43:7\\n at http://localhost:8888/test/main/globals.js:67:2: Error","testName":"globals: Exported assertions"}]}'; + + var malformedJson = '{ "key" : "Bad\njson contains\rall\tsorts\bof\vhorrible\0 & nasty\fescape sequences" }'; + var expectJson = { "key" : "Bad\njson contains\rall\tsorts\bof\u000bhorrible\u0000 & nasty\fescape sequences" }; + + assert.throws(function () { JSON.parse(testString); }, SyntaxError, 'JSON.parse fails'); + assert.equal(testString.escapeSpecialChars(), expectString); + assert.equal(JSON.parse(malformedJson.escapeSpecialChars()).key, expectJson.key); + }); }); From 6cc9ca0c7758fc1d85e195ea87c4ed64f6e56740 Mon Sep 17 00:00:00 2001 From: Akhil Lb Date: Mon, 10 Aug 2015 13:03:56 +0530 Subject: [PATCH 041/162] Updated README --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/README.md b/README.md index 386fb60..fda134a 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,40 @@ A sample configuration file: } ``` +#### `browsers` parameter + +`browsers` parameter is a list of objects, where each object contains the details of the browsers on which you want to run your tests. This object differs for browsers on dekstop platforms and browsers on mobile platforms. Browsers on dekstop platoform should contain `browser`, `browser_version`, `os `, `os_version` parameters set as required and `device` parameter is set to `null`. + +Example: +```json +{ + "browser": "ie", + "browser_version": "10.0", + "device": null, + "os": "Windows", + "os_version": "8" +} +``` + +For mobile platforms, `os`, `os_version` and `device` parameters are required. + +Example: +```json +[{ + "os": "ios", + "os_version": "8.3", + "device_name": "iPhone 6 Plus" +}, +{ + "os": "android", + "os_version": "4.0", + "device_name": "Google Nexus" +} +] +``` + +For information regarding list of supported browsers, platforms and other details visit this [link](http://www.browserstack.com/list-of-browsers-and-platforms?product=live). + #### Compact `browsers` configuration When `os` and `os_version` granularity is not desired, following configuration can be used: @@ -100,6 +134,9 @@ Example: } ``` +**Note:** +These shortcuts work only for browsers on desktop platforms supported by Browserstack. + ### Proxy support for BrowserStack local Add the following in `browserstack.json` From aadc5a009fb58ea89da7c947a39a4094efd3ff4f Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Sat, 15 Aug 2015 02:46:13 +0530 Subject: [PATCH 042/162] Fixed worker key and ack flag --- bin/cli.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index bd6188b..46bbd7c 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -206,7 +206,8 @@ function attachWorkerHelpers(worker) { // TODO: Consider creating instances of a proper 'Worker' class worker.buildUrl = function buildUrl(test_path) { - return buildTestUrl(test_path || this.test_path, this._worker_key, this.getTestBrowserInfo()); + var workerKey = workerKeys[this.id] ? workerKeys[this.id].key : null; + return buildTestUrl(test_path || this.test_path, workerKey, this.getTestBrowserInfo()); }; worker.getTestBrowserInfo = function getTestBrowserInfo(test_path) { @@ -287,7 +288,7 @@ var statusPoller = { worker.awaitAck(); worker.activityTimeout = setTimeout(function () { - if (!worker.acknowledged) { + if (!worker.isAckd) { var subject = 'Worker inactive for too long: ' + worker.string; var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); utils.alertBrowserStack(subject, content, null, function(){}); @@ -308,7 +309,7 @@ var statusPoller = { }, activityTimeout * 1000); worker.testActivityTimeout = setTimeout(function () { - if (worker.acknowledged) { + if (worker.isAckd) { var subject = 'Tests timed out on: ' + worker.string; var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); utils.alertBrowserStack(subject, content, null, function(){}); From aa96f96cb1356e44d1015f9cc9cb5ad4c3f88f4c Mon Sep 17 00:00:00 2001 From: Akhil Lb Date: Mon, 17 Aug 2015 11:58:34 +0530 Subject: [PATCH 043/162] Added suggested changes --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fda134a..a41d860 100644 --- a/README.md +++ b/README.md @@ -90,17 +90,17 @@ Example: [{ "os": "ios", "os_version": "8.3", - "device_name": "iPhone 6 Plus" + "device": "iPhone 6 Plus" }, { "os": "android", "os_version": "4.0", - "device_name": "Google Nexus" + "device": "Google Nexus" } ] ``` -For information regarding list of supported browsers, platforms and other details visit this [link](http://www.browserstack.com/list-of-browsers-and-platforms?product=live). +For a full list of supported browsers, platforms and other details, [visit the BrowserStack site](https://www.browserstack.com/list-of-browsers-and-platforms?product=automate). #### Compact `browsers` configuration @@ -133,9 +133,8 @@ Example: ] } ``` - **Note:** -These shortcuts work only for browsers on desktop platforms supported by Browserstack. +These shortcuts work only for browsers on desktop platforms supported by BrowserStack. ### Proxy support for BrowserStack local From 001204cce230cc1fb88c9159d6a11e481ab9b26b Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Fri, 21 Aug 2015 02:24:27 +0530 Subject: [PATCH 044/162] Fixed args for execFile --- lib/local.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/local.js b/lib/local.js index 1f2b38d..a65c205 100644 --- a/lib/local.js +++ b/lib/local.js @@ -70,7 +70,8 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { options.push('-force'); options.push('-onlyAutomate'); } else { - options.push('-localIdentifier ' + uniqueIdentifier); + options.push('-localIdentifier'); + options.push(uniqueIdentifier); } var proxy = config.proxy; From ca4c34a7b1f96a97366578e4eeeace3272852013 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Fri, 21 Aug 2015 20:45:35 +0530 Subject: [PATCH 045/162] Bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fdb8757..473d861 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.3.6", + "version": "0.3.7", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 5d9bf6dca7e2522434f4d252673c5e7016b5e6db Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Thu, 17 Sep 2015 14:37:15 +0000 Subject: [PATCH 046/162] Support for query strings in test_paths Strips off iany query or anchor params for properly matching incoming URL path with declared test paths Fixes #138 --- lib/server.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/server.js b/lib/server.js index 3094f47..4c193aa 100644 --- a/lib/server.js +++ b/lib/server.js @@ -23,6 +23,11 @@ var mimeTypes = { exports.Server = function Server(bsClient, workers) { + var testFilePaths = (Array.isArray(config.test_path) ? config.test_path : [ config.test_path ]) + .map(function (path) { + return path.split(/[?#]/)[0]; + }); + function handleFile(filename, request, response, doNotUseProxy) { var url_parts = url.parse(request.url, true); var query = url_parts.query; @@ -46,13 +51,7 @@ exports.Server = function Server(bsClient, workers) { }; var filePath = path.relative(process.cwd(), filename); - var pathMatches; - - if (typeof config.test_path === 'object') { - pathMatches = (config.test_path.indexOf(filePath) !== -1); - } else { - pathMatches = (filePath === config.test_path); - } + var pathMatches = (testFilePaths.indexOf(filePath) !== -1); if (pathMatches && mimeType === 'text/html') { var framework = config['test_framework']; From a65607d318106a8902c515a9a200151bb99e00f8 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Sun, 4 Oct 2015 15:24:17 +0000 Subject: [PATCH 047/162] Bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 473d861..e54821a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.3.7", + "version": "0.3.8", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 110b9d0217110ef69c29e1d1e62611bde59b0a57 Mon Sep 17 00:00:00 2001 From: Nidhi Makhijani Date: Mon, 5 Oct 2015 16:01:17 +0530 Subject: [PATCH 048/162] add sample app to README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 386fb60..ae606c6 100644 --- a/README.md +++ b/README.md @@ -129,3 +129,9 @@ To avoid duplication of system or user specific information across several confi To avoid checking in the BrowserStack `username` and `key` in your source control system, the corresponding environment variables can be used. These can also be provided by a build server, for example [using secure environment variables on Travis CI](http://about.travis-ci.org/docs/user/build-configuration/#Secure-environment-variables). + + +### Code Sample + +Check out code sample [here]. +[here]:https://github.com/browserstack/browserstack-runner-sample From 9de24d1c0015579a59df7da4d66a621f6cbc7bf7 Mon Sep 17 00:00:00 2001 From: Nidhi Makhijani Date: Fri, 23 Oct 2015 16:55:57 +0530 Subject: [PATCH 049/162] Temporary Fix: Sanitie worker id Some android browsers send a request with a '\' character prefixed to every '&', which causes the uuid to end with a '\'. --- lib/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/server.js b/lib/server.js index 4c193aa..0177c04 100644 --- a/lib/server.js +++ b/lib/server.js @@ -223,6 +223,7 @@ exports.Server = function Server(bsClient, workers) { query = parseBody(body); } catch (e) {} var uuid = request.headers['x-worker-uuid']; + uuid = uuid && uuid.replace(/[^a-zA-Z0-9\-]/, ''); var worker = workers[uuid] || {}; worker._worker_key = uuid; From 7da77f869617f6f2ba482ecbfdb2f6b8184a1272 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Sun, 6 Dec 2015 16:41:40 +0000 Subject: [PATCH 050/162] Download BrowserStackLocal binary over HTTPS Closes #142 --- lib/local.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/local.js b/lib/local.js index a65c205..ffb7753 100644 --- a/lib/local.js +++ b/lib/local.js @@ -2,7 +2,7 @@ var Log = require('./logger'), logger = new Log(global.logLevel), exec = require('child_process').execFile, fs = require('fs'), - http = require('http'), + https = require('https'), windows = ((process.platform.match(/win32/) || process.platform.match(/win64/)) !== null), localBinary = __dirname + '/BrowserStackLocal' + (windows ? '.exe' : ''), utils = require('./utils'), @@ -97,7 +97,7 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { logger.debug('Downloading BrowserStack Local to "%s"', localBinary); var file = fs.createWriteStream(localBinary); - http.get((windows ? 'http://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal.exe' : ('http://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal-' + process.platform + '-' + process.arch)), + https.get('https://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal' + (windows ? '.exe' : '-' + process.platform + '-' + process.arch), function(response) { response.pipe(file); From 452e0d12880bfbf62f19f4a59e670caf8a0749db Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Sat, 19 Dec 2015 03:49:58 +0530 Subject: [PATCH 051/162] Support for static files with arbitrary mimetypes Includes static binary files, and audio/video files that require range support --- lib/server.js | 41 ++++++++++++++++++++++------------------- package.json | 2 ++ 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/server.js b/lib/server.js index 0177c04..f406f73 100644 --- a/lib/server.js +++ b/lib/server.js @@ -8,17 +8,9 @@ var Log = require('./logger'), utils = require('./utils'), config = require('../lib/config'), proxyServer = require('./proxy').proxyServer, - chalk = require('chalk'); - -var mimeTypes = { - 'html': 'text/html', - 'json': 'text/json', - 'jpeg': 'image/jpeg', - 'jpg': 'image/jpeg', - 'png': 'image/png', - 'js': 'text/javascript', - 'css': 'text/css' -}; + chalk = require('chalk'), + mime = require('mime'), + send = require('send'); exports.Server = function Server(bsClient, workers) { @@ -37,7 +29,7 @@ exports.Server = function Server(bsClient, workers) { worker.markAckd(); } - var getReporterPatch = function (mimeType) { + var getReporterPatch = function () { var scripts = [ 'json2.js', 'browserstack.js', @@ -53,7 +45,7 @@ exports.Server = function Server(bsClient, workers) { var filePath = path.relative(process.cwd(), filename); var pathMatches = (testFilePaths.indexOf(filePath) !== -1); - if (pathMatches && mimeType === 'text/html') { + if (pathMatches) { var framework = config['test_framework']; var tag_name = (framework === 'mocha') ? 'head' : 'body'; var patch = '$1'; @@ -98,13 +90,12 @@ exports.Server = function Server(bsClient, workers) { return; } - var mimeType = mimeTypes[path.extname(filename).split('.')[1]]; response.writeHead(200, { - 'Content-Type': mimeType + '; charset=utf-8', + 'Content-Type': 'text/html; charset=utf-8' }); var tag_name = (config['test_framework'] === 'mocha') ? 'head' : 'body'; var matcher = new RegExp('(.*)<\/' + tag_name + '>'); ///(.*)<\/body>/; - var patch = getReporterPatch(mimeType); + var patch = getReporterPatch(); data = data.replace(matcher, patch); response.write(data); @@ -113,12 +104,12 @@ exports.Server = function Server(bsClient, workers) { if (!doNotUseProxy && config.test_server) { proxyServer.onRequest(request, response, config.test_server, function(remote_response, response_data) { - var mimeType = mimeTypes[path.extname(filename).split('.')[1]]; + var mimeType = mime.lookup(filename); var final_data = response_data; var headers = remote_response.headers; if (mimeType === 'text/html') { var matcher = /(.*)<\/head>/; - var patch = getReporterPatch(mimeType); + var patch = getReporterPatch(); final_data = response_data.replace(matcher, patch); headers['content-length'] = final_data.length; } @@ -144,7 +135,19 @@ exports.Server = function Server(bsClient, workers) { filename = filename + (filename.lastIndexOf('/') === filename.length - 1 ? '' : '/') + 'index.html'; } - fs.readFile(filename, {encoding: 'utf8'}, writeResponse); + var mimeType = mime.lookup(filename); + if (mimeType === 'text/html') { + fs.readFile(filename, { encoding: 'utf8' }, function (err, data) { + writeResponse(err, data); + }); + } else { + send(request, filename) + .on('error', function onSendError(err) { + response.statusCode = err.status || 500; + response.end(err.message || 'Internal Server Error'); + }) + .pipe(response); + } }); } } diff --git a/package.json b/package.json index e54821a..e8a89a7 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "dependencies": { "browserstack": "1.1.0", "chalk": "0.4.0", + "mime": "1.3.4", + "send": "0.13.0", "tunnel": "0.0.3" }, "devDependencies": { From ce21352051ecddc112aa92dd81385079514ac6a2 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Sat, 19 Dec 2015 18:26:17 +0000 Subject: [PATCH 052/162] Updated browserstack module version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e8a89a7..ce657aa 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "url": "https://github.com/browserstack/browserstack-runner.git" }, "dependencies": { - "browserstack": "1.1.0", + "browserstack": "1.3.0", "chalk": "0.4.0", "mime": "1.3.4", "send": "0.13.0", From f1630448e6ae6ea3a6098033c26c348d31809fc4 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Fri, 25 Dec 2015 00:05:50 +0530 Subject: [PATCH 053/162] Bumped version to 0.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce657aa..a5bec94 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.3.8", + "version": "0.4.0", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 4edfe6af6cef44f72d297fec40964cc85a35167b Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Fri, 25 Dec 2015 00:34:04 +0530 Subject: [PATCH 054/162] Fix npm publish module Accidentally published npm module with mac binary bundled --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a5bec94..fe69392 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.4.0", + "version": "0.4.1", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 7e4d81395968b44f9161ab5d41129dbccafb8273 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Sat, 23 Jan 2016 19:29:09 +0000 Subject: [PATCH 055/162] Added checks for worker uuid --- lib/server.js | 61 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/lib/server.js b/lib/server.js index f406f73..752f682 100644 --- a/lib/server.js +++ b/lib/server.js @@ -82,11 +82,7 @@ exports.Server = function Server(bsClient, workers) { var writeResponse = function(err, data) { if (err) { - response.writeHead(500, { - 'Content-Type': 'text/plain' - }); - response.write(err + '\n'); - response.end(); + sendError(response, err, 500); return; } @@ -123,11 +119,7 @@ exports.Server = function Server(bsClient, workers) { fs.exists(filename, function(exists) { if (!exists) { - response.writeHead(404, { - 'Content-Type': 'text/plain' - }); - response.write('404 Not Found\n'); - response.end(); + sendError(response,'file not found', 404); return; } @@ -143,8 +135,7 @@ exports.Server = function Server(bsClient, workers) { } else { send(request, filename) .on('error', function onSendError(err) { - response.statusCode = err.status || 500; - response.end(err.message || 'Internal Server Error'); + sendError(response, (err.message || 'Internal Server Error'), err.status || 500); }) .pipe(response); } @@ -200,11 +191,38 @@ exports.Server = function Server(bsClient, workers) { return config.test_path[ ++worker.path_index ]; } + + function getWorkerUuid(request) { + var uuid = request.headers['x-worker-uuid']; + uuid = uuid && uuid.replace(/[^a-zA-Z0-9\-]/, ''); + return (uuid && workers[uuid]) ? uuid : null; + } + + + function sendError(response, errMessage, statusCode) { + response.writeHead(statusCode || 400, { + 'Content-Type': 'text/plain' + }); + + if (errMessage) { + response.write(errMessage + '\n'); + } + + response.end(); + } + + var handlers = { '_progress': function progressHandler(uri, body, request, response) { - var uuid = request.headers['x-worker-uuid']; - var worker = workers[uuid] || {}; + var uuid = getWorkerUuid(request); + if (!uuid) { + sendError(response, 'worker not found', 404); + return; + } + + var worker = workers[uuid]; var query = null; + try { query = parseBody(body); } catch(e) { @@ -212,7 +230,7 @@ exports.Server = function Server(bsClient, workers) { logger.info('[%s] Log: ' + qs.parse(body).data, worker.string); } - if (query.tracebacks) { + if (query && query.tracebacks) { query.tracebacks.forEach(function(traceback) { logger.info('[%s] ' + chalk.red('Error:'), worker.getTestBrowserInfo(), formatTraceback(traceback)); }); @@ -221,14 +239,19 @@ exports.Server = function Server(bsClient, workers) { }, '_report': function reportHandler(uri, body, request, response) { + var uuid = getWorkerUuid(request); + if (!uuid) { + sendError(response, 'worker not found', 404); + return; + } + + var worker = workers[uuid]; + worker._worker_key = uuid; + var query = null; try { query = parseBody(body); } catch (e) {} - var uuid = request.headers['x-worker-uuid']; - uuid = uuid && uuid.replace(/[^a-zA-Z0-9\-]/, ''); - var worker = workers[uuid] || {}; - worker._worker_key = uuid; if (query === null) { logger.info('[%s] Null response from remote Browser', request.headers['x-browser-string']); From 4ab29937d251edc617d44ff9220862e27a91c844 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Sat, 23 Jan 2016 22:05:08 +0000 Subject: [PATCH 056/162] Added finer log level: trace --- bin/cli.js | 67 ++++++++++++++++++++++++++++++++++++++++++++++----- lib/logger.js | 6 ++++- lib/server.js | 31 ++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 9 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 46bbd7c..03dc16b 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -3,7 +3,7 @@ var todo = process.argv[2]; if (todo === '--verbose') { - global.logLevel = 'debug'; + global.logLevel = process.env.LOG_LEVEL || 'debug'; } else { global.logLevel = 'info'; } @@ -40,7 +40,11 @@ var Log = require('../lib/logger'), tunnel; function terminateAllWorkers(callback) { + logger.trace('terminateAllWorkers'); + var cleanWorker = function(id, key) { + logger.trace('cleanWorker(%s, %s)', id, key); + client.terminateWorker(id, function() { var worker = workers[key]; if(worker) { @@ -52,12 +56,14 @@ function terminateAllWorkers(callback) { delete workerKeys[worker.id]; } if (utils.objectSize(workers) === 0) { + logger.trace('terminateAllWorkers: done'); callback(); } }); }; if (utils.objectSize(workers) === 0) { + logger.trace('terminateAllWorkers: done'); callback(); } else { for (var key in workers){ @@ -67,6 +73,7 @@ function terminateAllWorkers(callback) { } else { delete workers[key]; if (utils.objectSize(workers) === 0) { + logger.trace('terminateAllWorkers: done'); callback(); } } @@ -75,6 +82,8 @@ function terminateAllWorkers(callback) { } function cleanUpAndExit(signal, status) { + logger.trace('cleanUpAndExit: signal: %s', signal); + try { server.close(); } catch (e) { @@ -113,6 +122,7 @@ function getTestBrowserInfo(browserString, path) { info += ', ' + path; } + logger.trace('getTestBrowserInfo(%s, %s): %s', browserString, path, info); return info; } @@ -126,11 +136,13 @@ function buildTestUrl(test_path, worker_key, browser_string) { }); url += ((url.indexOf('?') > 0) ? '&' : '?') + querystring; + logger.trace('buildTestUrl:', url); return url; } function launchServer() { + logger.trace('launchServer:', serverPort); logger.debug('Launching server on port:', serverPort); server = new Server(client, workers); @@ -165,7 +177,11 @@ function launchBrowser(browser, path) { activityTimeout = timeout - 10; ackTimeout = parseInt(config.ackTimeout) || 60; + logger.trace('[%s] client.createWorker', browserInfo, browser); + client.createWorker(browser, function (err, worker) { + logger.trace('[%s] client.createWorker | response:', browserInfo, worker, err); + if (err || typeof worker !== 'object') { logger.info('Error from BrowserStack: ', err); utils.alertBrowserStack('Failed to launch worker', @@ -191,6 +207,8 @@ function launchBrowser(browser, path) { function launchBrowsers(config, browser) { setTimeout(function () { + logger.trace('launchBrowsers', browser); + if (Array.isArray(config.test_path)){ config.multipleTest = config.test_path.length > 1? true : false; launchBrowser(browser, config.test_path[0]); @@ -207,7 +225,9 @@ function attachWorkerHelpers(worker) { worker.buildUrl = function buildUrl(test_path) { var workerKey = workerKeys[this.id] ? workerKeys[this.id].key : null; - return buildTestUrl(test_path || this.test_path, workerKey, this.getTestBrowserInfo()); + var url = buildTestUrl(test_path || this.test_path, workerKey, this.getTestBrowserInfo()); + logger.trace('[%s] worker.buildUrl: %s', this.id, url); + return url; }; worker.getTestBrowserInfo = function getTestBrowserInfo(test_path) { @@ -222,18 +242,23 @@ function attachWorkerHelpers(worker) { var self = this; if (this.ackTimeout) { - // Already awaiting ack, or awaited ack once and failed + logger.trace('[%s] worker.awaitAck: already awaiting ack, or awaited ack once and failed', self.id); return; } + logger.trace('[%s] worker.awaitAck: timeout in %d secs', self.id, ackTimeout); + this.ackTimeout = setTimeout(function () { if (self.isAckd) { - // Already ack'd + logger.trace('[%s] worker.awaitAck: already ackd', self.id); return; } - // worker has not acknowledged itself in 60 sec, reopen url - client.changeUrl(self.id, { url: self.buildUrl() }, function () { + var url = self.buildUrl(); + logger.trace('[%s] worker.awaitAck: client.changeUrl: %s', self.id, url); + + client.changeUrl(self.id, { url: url }, function (err, data) { + logger.trace('[%s] worker.awaitAck: client.changeUrl: %s | response:', self.id, url, data, err); logger.debug('[%s] Sent Request to reload url', self.getTestBrowserInfo()); }); @@ -246,10 +271,13 @@ function attachWorkerHelpers(worker) { this.resetAck(); this.isAckd = true; + logger.trace('[%s] worker.markAckd', this.id); logger.debug('[%s] Received ack', this.getTestBrowserInfo()); }; worker.resetAck = function resetAck() { + logger.trace('[%s] worker.resetAck', this.id); + clearTimeout(this.ackTimeout); this.ackTimeout = null; this.isAckd = false; @@ -263,8 +291,12 @@ var statusPoller = { poller: null, start: function() { + logger.trace('statusPoller.start'); + statusPoller.poller = setInterval(function () { client.getWorkers(function (err, _workers) { + logger.trace('client.getWorkers | response: worker count: %d', (_workers || []).length, err); + if (!_workers) { logger.info(chalk.red('Error found: ' + err)); return; @@ -286,9 +318,12 @@ var statusPoller = { // Await ack from browser-worker worker.awaitAck(); + logger.trace('[%s] worker.activityTimeout: timeout in %d secs', worker.id, activityTimeout); worker.activityTimeout = setTimeout(function () { if (!worker.isAckd) { + logger.trace('[%s] worker.activityTimeout', worker.id); + var subject = 'Worker inactive for too long: ' + worker.string; var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); utils.alertBrowserStack(subject, content, null, function(){}); @@ -303,13 +338,21 @@ var statusPoller = { config.status = 1; } + logger.trace('[%s] worker.activityTimeout: all tests done', worker.id, config.status && 'with failures'); process.exit('SIGTERM'); } + } else { + logger.trace('[%s] worker.activityTimeout: already ackd', worker.id); } }, activityTimeout * 1000); + + logger.trace('[%s] worker.testActivityTimeout: timeout in %d secs', worker.id, activityTimeout); + worker.testActivityTimeout = setTimeout(function () { if (worker.isAckd) { + logger.trace('[%s] worker.testActivityTimeout', worker.id); + var subject = 'Tests timed out on: ' + worker.string; var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); utils.alertBrowserStack(subject, content, null, function(){}); @@ -324,8 +367,11 @@ var statusPoller = { config.status = 1; } + logger.trace('[%s] worker.testActivityTimeout: all tests done', worker.id, config.status && 'with failures'); process.exit('SIGTERM'); } + } else { + logger.trace('[%s] worker.testActivityTimeout: not ackd', worker.id); } }, (activityTimeout * 1000)); } @@ -335,12 +381,15 @@ var statusPoller = { }, stop: function() { + logger.trace('statusPoller.poller'); clearInterval(statusPoller.poller); } }; function runTests() { if (config.proxy) { + logger.trace('runTests: with proxy', config.proxy); + tunnelingAgent = tunnel.httpOverHttp({ proxy: config.proxy }); @@ -353,15 +402,21 @@ function runTests() { if (config.browsers && config.browsers.length > 0) { ConfigParser.parse(client, config.browsers, function(browsers){ launchServer(); + + logger.trace('runTests: creating tunnel'); tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, function () { + logger.trace('runTests: created tunnel'); + statusPoller.start(); var total_runs = config.browsers.length * (Array.isArray(config.test_path) ? config.test_path.length : 1); logger.info('Launching ' + config.browsers.length + ' worker(s) for ' + total_runs + ' run(s).'); browsers.forEach(function(browser) { if (browser.browser_version === 'latest') { logger.debug('[%s] Finding version.', utils.browserString(browser)); + logger.trace('runTests: client.getLatest'); client.getLatest(browser, function(err, version) { + logger.trace('runTests: client.getLatest | response:', version, err); logger.debug('[%s] Version is %s.', utils.browserString(browser), version); browser.browser_version = version; diff --git a/lib/logger.js b/lib/logger.js index 0f0c431..ca9c2e0 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,5 +1,5 @@ var fmt = require('util').format; -var logLevels = {ERROR: 3, INFO: 6, DEBUG: 7}; +var logLevels = {ERROR: 3, INFO: 6, DEBUG: 7, TRACE: 8}; function Log(level){ if ('string' === typeof level) { @@ -29,6 +29,10 @@ Log.prototype = { debug: function(){ this.log('DEBUG', arguments); + }, + + trace: function(){ + this.log('TRACE', arguments); } }; diff --git a/lib/server.js b/lib/server.js index 752f682..b937bbd 100644 --- a/lib/server.js +++ b/lib/server.js @@ -194,7 +194,13 @@ exports.Server = function Server(bsClient, workers) { function getWorkerUuid(request) { var uuid = request.headers['x-worker-uuid']; + uuid = uuid && uuid.replace(/[^a-zA-Z0-9\-]/, ''); + logger.trace('cleaning up worker uuid:', uuid); + + uuid = (uuid && typeof workers[uuid] === 'object') ? uuid : null; + logger.trace('worker uuid', uuid, (uuid ? 'valid' : 'invalid')); + return (uuid && workers[uuid]) ? uuid : null; } @@ -230,6 +236,8 @@ exports.Server = function Server(bsClient, workers) { logger.info('[%s] Log: ' + qs.parse(body).data, worker.string); } + logger.trace('[%s] _progress', worker.id, query); + if (query && query.tracebacks) { query.tracebacks.forEach(function(traceback) { logger.info('[%s] ' + chalk.red('Error:'), worker.getTestBrowserInfo(), formatTraceback(traceback)); @@ -253,6 +261,8 @@ exports.Server = function Server(bsClient, workers) { query = parseBody(body); } catch (e) {} + logger.trace('[%s] _report', worker.id, query); + if (query === null) { logger.info('[%s] Null response from remote Browser', request.headers['x-browser-string']); } else { @@ -267,23 +277,30 @@ exports.Server = function Server(bsClient, workers) { config.status += query.failed; } + logger.trace('[%s] _report: client.takeScreenshot', worker.id); + bsClient.takeScreenshot(worker.id, function(error, screenshot) { + logger.trace('[%s] _report: client.takeScreenshot | response:', worker.id, screenshot, error); + if (!error && screenshot.url && query && query.failed) { logger.info('[%s] ' + chalk.yellow('Screenshot:') + ' %s', worker.getTestBrowserInfo(), screenshot.url); } checkAndTerminateWorker(worker, function(reusedWorker) { if (!workers[uuid]) { + logger.trace('[%s] _report: checkAndTerminateWorker: worker not found', worker.id); return; } if (reusedWorker) { + logger.trace('[%s] _report: checkAndTerminateWorker: reused worker', worker.id); logger.debug('[%s] Reused', worker.getTestBrowserInfo()); worker.resetAck(); worker.awaitAck(); return; } + logger.trace('[%s] _report: checkAndTerminateWorker: terminated', worker.id); logger.debug('[%s] Terminated', worker.getTestBrowserInfo()); clearTimeout(workers[uuid].ackTimeout); @@ -299,6 +316,7 @@ exports.Server = function Server(bsClient, workers) { config.status = 1; } + logger.trace('[%s] _report: checkAndTerminateWorker: all tests done', worker.id, config.status && 'with failures'); process.exit('SIGTERM'); } }); @@ -307,15 +325,24 @@ exports.Server = function Server(bsClient, workers) { response.end(); }, '_log': function logHandler(uri, body, request, response) { + var uuid = getWorkerUuid(request); var query = parseBody(body); + + logger.trace('[%s] _log', ((uuid && workers[uuid]) || {}).id, query); logger.info('[' + request.headers['x-browser-string'] + '] ' + query); response.end(); }, '_patch': function patchHandler(uri, body, request, response) { - handleFile(path.join(__dirname, uri), request, response, true); + var filePath = path.join(__dirname, uri); + logger.trace('_patch', filePath); + + handleFile(filePath, request, response, true); }, '_default': function defaultHandler(uri, body, request, response) { - handleFile(path.join(process.cwd(), uri), request, response); + var filePath = path.join(process.cwd(), uri); + logger.trace('_default', filePath); + + handleFile(filePath, request, response); } }; From e73722e96c6fd6547a67fe0720170ce8ff0faecf Mon Sep 17 00:00:00 2001 From: Akhil Lb Date: Wed, 27 Jan 2016 12:04:58 +0530 Subject: [PATCH 057/162] Correct spelling mistakes in docs --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index a41d860..6975dde 100644 --- a/README.md +++ b/README.md @@ -70,14 +70,13 @@ A sample configuration file: #### `browsers` parameter -`browsers` parameter is a list of objects, where each object contains the details of the browsers on which you want to run your tests. This object differs for browsers on dekstop platforms and browsers on mobile platforms. Browsers on dekstop platoform should contain `browser`, `browser_version`, `os `, `os_version` parameters set as required and `device` parameter is set to `null`. +`browsers` parameter is a list of objects, where each object contains the details of the browsers on which you want to run your tests. This object differs for browsers on desktop platforms and browsers on mobile platforms. Browsers on desktop platform should contain `browser`, `browser_version`, `os `, `os_version` parameters set as required. Example: ```json { "browser": "ie", "browser_version": "10.0", - "device": null, "os": "Windows", "os_version": "8" } From fb309e41b6fe1595570053eb7c772232f44218a1 Mon Sep 17 00:00:00 2001 From: Prayag Verma Date: Wed, 3 Feb 2016 09:01:41 +0530 Subject: [PATCH 058/162] Fix typo in Readme.md Found a spelling mistake - `specificed` > `specified` `concatinated` -> `concatenated` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a56c987..bedd979 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ For a full list of supported browsers, platforms and other details, [visit the B When `os` and `os_version` granularity is not desired, following configuration can be used: * `[browser]_current` or *browser*_latest: will assign the latest version of the *browser*. * `[browser]_previous`: will assign the previous version of the *browser*. - * `[browser]_[version]`: will assign the *version* specificed of the *browser*. Minor versions can be concatinated with underscores. + * `[browser]_[version]`: will assign the *version* specified of the *browser*. Minor versions can be concatenated with underscores. This can also be mixed with fine-grained configuration. From 0ede6e1f95f65cd40ee8c9535b30731e5950cae3 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Thu, 4 Feb 2016 16:23:31 +0530 Subject: [PATCH 059/162] Verify downloaded binary before attempting to start tunnel Performs a simple check with '-version' switch, and on failure causes the binary to be downloaded again. Fixes #143 --- lib/local.js | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/lib/local.js b/lib/local.js index ffb7753..ea46d46 100644 --- a/lib/local.js +++ b/lib/local.js @@ -89,7 +89,81 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { return options; } - fs.exists(localBinary, function(exists) { + function runTunnelCmd(tunnelOptions, subProcessTimeout, processOutputHook, callback) { + var isRunning, subProcess, timeoutHandle; + + var callbackOnce = function (err, result) { + clearTimeout(timeoutHandle); + if (subProcess && isRunning) { + try { + process.kill(subProcess.pid, 'SIGKILL'); + subProcess = null; + } catch (e) { + logger.debug('[%s] failed to kill process:', new Date(), e); + } + } + + callback && callback(err, result); + callback = null; + }; + + isRunning = true; + + try { + subProcess = exec(localBinary, tunnelOptions, function (error, stdout) { + isRunning = false; + + if (error) { + callbackOnce(new Error('failed to get process output: ' + error)); + } else if (stdout) { + processOutputHook(stdout, callbackOnce); + } + }); + + subProcess.stdout.on('data', function (data) { + processOutputHook(data, callbackOnce); + }); + } catch (e) { + // Handles EACCESS and other errors when binary file exists, + // but doesn't have necessary permissions (among other issues) + callbackOnce(new Error('failed to get process output: ' + e)); + } + + if (subProcessTimeout > 0) { + timeoutHandle = setTimeout(function () { + callbackOnce(new Error('failed to get process output: command timeout')); + }, subProcessTimeout); + } + } + + function getTunnelBinaryVersion(callback) { + var subProcessTimeout = 3000; + + runTunnelCmd([ '-version' ], subProcessTimeout, function (data, done) { + var matches = /version\s+(\d+(\.\d+)*)/.exec(data); + var version = (matches && matches.length > 2) && matches[1]; + logger.debug('[%s] Tunnel binary: found version', new Date(), version); + + done(isFinite(version) ? null : new Error('failed to get binary version'), parseFloat(version)); + }, callback); + } + + function verifyTunnelBinary(callback) { + logger.debug('[%s] Verifying tunnel binary', new Date()); + + fs.exists(localBinary, function (exists) { + if (!exists) { + logger.debug('[%s] Verifying tunnel binary: file does not exist', new Date()); + callback(false); + } else { + getTunnelBinaryVersion(function (err, version) { + callback(!err && isFinite(version)); + }); + } + }); + } + + verifyTunnelBinary(function (exists) { if (exists) { tunnelLauncher(); return; From 361e83496ee2e37ed28a7763c110eb41a1d56506 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Mon, 8 Feb 2016 00:29:41 +0530 Subject: [PATCH 060/162] Fix empty mocha tracebacks --- lib/_patch/mocha-plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_patch/mocha-plugin.js b/lib/_patch/mocha-plugin.js index 57d0648..6a5a771 100644 --- a/lib/_patch/mocha-plugin.js +++ b/lib/_patch/mocha-plugin.js @@ -48,7 +48,7 @@ failures += 1; if (err) { - tracebacks.push(err); + tracebacks.push(stack(err)); } }); From 3d7a7404a71b82cc27c52e4f3efdbefaeb53eab6 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Mon, 8 Feb 2016 00:30:24 +0530 Subject: [PATCH 061/162] Delay posting results to capture "multiple-done" errors --- lib/_patch/mocha-plugin.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/_patch/mocha-plugin.js b/lib/_patch/mocha-plugin.js index 6a5a771..a102661 100644 --- a/lib/_patch/mocha-plugin.js +++ b/lib/_patch/mocha-plugin.js @@ -53,14 +53,17 @@ }); runner.on('end', function() { - results = {}; - results.runtime = (new Date).getTime() - start; - results.total = passes + failures; - results.passed = passes; - results.failed = failures; - results.tracebacks = tracebacks; - results.url = window.location.pathname; - BrowserStack.post("/_report", results, function(){}); + // delay posting results a little to capture "multiple-done" errors + setTimeout(function () { + results = {}; + results.runtime = (new Date).getTime() - start; + results.total = passes + failures; + results.passed = passes; + results.failed = failures; + results.tracebacks = tracebacks; + results.url = window.location.pathname; + BrowserStack.post("/_report", results, function(){}); + }, 1000); }); }; From 1cb8deb6a75560191885b5d5afc6419c654ced34 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Mon, 8 Feb 2016 14:43:52 +0000 Subject: [PATCH 062/162] Fix for error when logging circular refs, support for multi-arg console.log Fixes #133 --- .jshintrc | 3 ++- lib/_patch/browserstack-util.js | 1 + lib/_patch/browserstack.js | 10 ++++++---- lib/server.js | 34 ++++++++++++++++++++++++++++++--- 4 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 lib/_patch/browserstack-util.js diff --git a/.jshintrc b/.jshintrc index a7fe4db..ff347da 100644 --- a/.jshintrc +++ b/.jshintrc @@ -15,6 +15,7 @@ "unused": true, "predef": [ "require", - "global" + "global", + "window" ] } diff --git a/lib/_patch/browserstack-util.js b/lib/_patch/browserstack-util.js new file mode 100644 index 0000000..fddda62 --- /dev/null +++ b/lib/_patch/browserstack-util.js @@ -0,0 +1 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o=3;if(hasAcc&&xs.reduce)return xs.reduce(f,acc);if(xs.reduce)return xs.reduce(f);for(var i=0;i1)))/4)-floor((year-1901+month)/100)+floor((year-1601+month)/400)}}if(!(isProperty=objectProto.hasOwnProperty)){isProperty=function(property){var members={},constructor;if((members.__proto__=null,members.__proto__={toString:1},members).toString!=getClass){isProperty=function(property){var original=this.__proto__,result=property in(this.__proto__=null,this);this.__proto__=original;return result}}else{constructor=members.constructor;isProperty=function(property){var parent=(this.constructor||constructor).prototype;return property in this&&!(property in parent&&this[property]===parent[property])}}members=null;return isProperty.call(this,property)}}var PrimitiveTypes={"boolean":1,number:1,string:1,undefined:1};var isHostType=function(object,property){var type=typeof object[property];return type=="object"?!!object[property]:!PrimitiveTypes[type]};forEach=function(object,callback){var size=0,Properties,members,property;(Properties=function(){this.valueOf=0}).prototype.valueOf=0;members=new Properties;for(property in members){if(isProperty.call(members,property)){size++}}Properties=members=null;if(!size){members=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,length;var hasProperty=!isFunction&&typeof object.constructor!="function"&&isHostType(object,"hasOwnProperty")?object.hasOwnProperty:isProperty;for(property in object){if(!(isFunction&&property=="prototype")&&hasProperty.call(object,property)){callback(property)}}for(length=members.length;property=members[--length];hasProperty.call(object,property)&&callback(property));}}else if(size==2){forEach=function(object,callback){var members={},isFunction=getClass.call(object)==functionClass,property;for(property in object){if(!(isFunction&&property=="prototype")&&!isProperty.call(members,property)&&(members[property]=1)&&isProperty.call(object,property)){callback(property)}}}}else{forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,isConstructor;for(property in object){if(!(isFunction&&property=="prototype")&&isProperty.call(object,property)&&!(isConstructor=property==="constructor")){callback(property)}}if(isConstructor||isProperty.call(object,property="constructor")){callback(property)}}}return forEach(object,callback)};if(!has("json-stringify")){var Escapes={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};var leadingZeroes="000000";var toPaddedString=function(width,value){return(leadingZeroes+(value||0)).slice(-width)};var unicodePrefix="\\u00";var quote=function(value){var result='"',index=0,length=value.length,useCharIndex=!charIndexBuggy||length>10;var symbols=useCharIndex&&(charIndexBuggy?value.split(""):value);for(;index-1/0&&value<1/0){if(getDay){date=floor(value/864e5);for(year=floor(date/365.2425)+1970-1;getDay(year+1,0)<=date;year++);for(month=floor((date-getDay(year,0))/30.42);getDay(year,month+1)<=date;month++);date=1+date-getDay(year,month);time=(value%864e5+864e5)%864e5;hours=floor(time/36e5)%24;minutes=floor(time/6e4)%60;seconds=floor(time/1e3)%60;milliseconds=time%1e3}else{year=value.getUTCFullYear();month=value.getUTCMonth();date=value.getUTCDate();hours=value.getUTCHours();minutes=value.getUTCMinutes();seconds=value.getUTCSeconds();milliseconds=value.getUTCMilliseconds()}value=(year<=0||year>=1e4?(year<0?"-":"+")+toPaddedString(6,year<0?-year:year):toPaddedString(4,year))+"-"+toPaddedString(2,month+1)+"-"+toPaddedString(2,date)+"T"+toPaddedString(2,hours)+":"+toPaddedString(2,minutes)+":"+toPaddedString(2,seconds)+"."+toPaddedString(3,milliseconds)+"Z"}else{value=null}}else if(typeof value.toJSON=="function"&&(className!=numberClass&&className!=stringClass&&className!=arrayClass||isProperty.call(value,"toJSON"))){value=value.toJSON(property)}}if(callback){value=callback.call(object,property,value)}if(value===null){return"null"}className=getClass.call(value);if(className==booleanClass){return""+value}else if(className==numberClass){return value>-1/0&&value<1/0?""+value:"null"}else if(className==stringClass){return quote(""+value)}if(typeof value=="object"){for(length=stack.length;length--;){if(stack[length]===value){throw TypeError()}}stack.push(value);results=[];prefix=indentation;indentation+=whitespace;if(className==arrayClass){for(index=0,length=value.length;index0){for(whitespace="",width>10&&(width=10);whitespace.length=48&&charCode<=57||charCode>=97&&charCode<=102||charCode>=65&&charCode<=70)){abort()}}value+=fromCharCode("0x"+source.slice(begin,Index));break;default:abort()}}else{if(charCode==34){break}charCode=source.charCodeAt(Index);begin=Index;while(charCode>=32&&charCode!=92&&charCode!=34){charCode=source.charCodeAt(++Index)}value+=source.slice(begin,Index)}}if(source.charCodeAt(Index)==34){Index++;return value}abort();default:begin=Index;if(charCode==45){isSigned=true;charCode=source.charCodeAt(++Index)}if(charCode>=48&&charCode<=57){if(charCode==48&&(charCode=source.charCodeAt(Index+1),charCode>=48&&charCode<=57)){abort()}isSigned=false;for(;Index=48&&charCode<=57);Index++);if(source.charCodeAt(Index)==46){position=++Index;for(;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}charCode=source.charCodeAt(Index);if(charCode==101||charCode==69){charCode=source.charCodeAt(++Index);if(charCode==43||charCode==45){Index++}for(position=Index;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}return+source.slice(begin,Index)}if(isSigned){abort()}if(source.slice(Index,Index+4)=="true"){Index+=4;return true}else if(source.slice(Index,Index+5)=="false"){Index+=5;return false}else if(source.slice(Index,Index+4)=="null"){Index+=4;return null}abort()}}return"$"};var get=function(value){var results,hasMembers;if(value=="$"){abort()}if(typeof value=="string"){if((charIndexBuggy?value.charAt(0):value[0])=="@"){return value.slice(1)}if(value=="["){results=[];for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="]"){break}if(hasMembers){if(value==","){value=lex();if(value=="]"){abort()}}else{abort()}}if(value==","){abort()}results.push(get(value))}return results}else if(value=="{"){results={};for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="}"){break}if(hasMembers){if(value==","){value=lex();if(value=="}"){abort()}}else{abort()}}if(value==","||typeof value!="string"||(charIndexBuggy?value.charAt(0):value[0])!="@"||lex()!=":"){abort()}results[value.slice(1)]=get(lex())}return results}abort()}return value};var update=function(source,property,callback){var element=walk(source,property,callback);if(element===undef){delete source[property]}else{source[property]=element}};var walk=function(source,property,callback){var value=source[property],length;if(typeof value=="object"&&value){if(getClass.call(value)==arrayClass){for(length=value.length;length--;){update(value,length,callback)}}else{forEach(value,function(property){update(value,property,callback)})}}return callback.call(source,property,value)};exports.parse=function(source,callback){var result,value;Index=0;Source=""+source;result=get(lex());if(lex()!="$"){abort()}Index=Source=null;return callback&&getClass.call(callback)==functionClass?walk((value={},value[""]=result,value),"",callback):result}}}exports["runInContext"]=runInContext;return exports}if(typeof exports=="object"&&exports&&!exports.nodeType&&!isLoader){runInContext(root,exports)}else{var nativeJSON=root.JSON;var JSON3=runInContext(root,root["JSON3"]={noConflict:function(){root.JSON=nativeJSON;return JSON3}});root.JSON={parse:JSON3.parse,stringify:JSON3.stringify}}if(isLoader){define(function(){return JSON3})}})(this)}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],8:[function(require,module,exports){"use strict";var hasOwn=Object.prototype.hasOwnProperty;var toString=Object.prototype.toString;var isFunction=function(fn){return typeof fn==="function"&&!(fn instanceof RegExp)||toString.call(fn)==="[object Function]"};module.exports=function forEach(obj,fn){if(!isFunction(fn)){throw new TypeError("iterator must be a function")}var i,k,isString=typeof obj==="string",l=obj.length,context=arguments.length>2?arguments[2]:null;if(l===+l){for(i=0;i=0&&toString.call(value.callee)==="[object Function]"}return isArguments}},{}],11:[function(require,module,exports){var map=require("array-map");var indexOf=require("indexof");var isArray=require("isarray");var forEach=require("foreach");var reduce=require("array-reduce");var getObjectKeys=require("object-keys");var JSON=require("json3");function objectKeys(val){if(Object.keys)return Object.keys(val);return getObjectKeys(val)}module.exports=inspect;function inspect(obj,opts){var ctx={seen:[],stylize:stylizeNoColor};if(arguments.length>=3)ctx.depth=arguments[2];if(arguments.length>=4)ctx.colors=arguments[3];if(isBoolean(opts)){ctx.showHidden=opts}else if(opts){_extend(ctx,opts)}if(isUndefined(ctx.showHidden))ctx.showHidden=false;if(isUndefined(ctx.depth))ctx.depth=2;if(isUndefined(ctx.colors))ctx.colors=false;if(isUndefined(ctx.customInspect))ctx.customInspect=true;if(ctx.colors)ctx.stylize=stylizeWithColor;return formatValue(ctx,obj,ctx.depth)}inspect.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]};inspect.styles={special:"cyan",number:"yellow","boolean":"yellow",undefined:"grey","null":"bold",string:"green",date:"magenta",regexp:"red"};function stylizeNoColor(str,styleType){return str}function isBoolean(arg){return typeof arg==="boolean"}function isUndefined(arg){return arg===void 0}function stylizeWithColor(str,styleType){var style=inspect.styles[styleType];if(style){return"["+inspect.colors[style][0]+"m"+str+"["+inspect.colors[style][1]+"m"}else{return str}}function isFunction(arg){return typeof arg==="function"}function isString(arg){return typeof arg==="string"}function isNumber(arg){return typeof arg==="number"}function isNull(arg){return arg===null}function hasOwn(obj,prop){return Object.prototype.hasOwnProperty.call(obj,prop)}function isRegExp(re){return isObject(re)&&objectToString(re)==="[object RegExp]"}function isObject(arg){return typeof arg==="object"&&arg!==null}function isError(e){return isObject(e)&&(objectToString(e)==="[object Error]"||e instanceof Error)}function isDate(d){return isObject(d)&&objectToString(d)==="[object Date]"}function objectToString(o){return Object.prototype.toString.call(o)}function arrayToHash(array){var hash={};forEach(array,function(val,idx){hash[val]=true});return hash}function formatArray(ctx,value,recurseTimes,visibleKeys,keys){var output=[];for(var i=0,l=value.length;i=0||indexOf(keys,"description")>=0)){return formatError(value)}if(keys.length===0){if(isFunction(value)){var name=value.name?": "+value.name:"";return ctx.stylize("[Function"+name+"]","special")}if(isRegExp(value)){return ctx.stylize(RegExp.prototype.toString.call(value),"regexp")}if(isDate(value)){return ctx.stylize(Date.prototype.toString.call(value),"date")}if(isError(value)){return formatError(value)}}var base="",array=false,braces=["{","}"];if(isArray(value)){array=true;braces=["[","]"]}if(isFunction(value)){var n=value.name?": "+value.name:"";base=" [Function"+n+"]"}if(isRegExp(value)){base=" "+RegExp.prototype.toString.call(value)}if(isDate(value)){base=" "+Date.prototype.toUTCString.call(value)}if(isError(value)){base=" "+formatError(value)}if(keys.length===0&&(!array||value.length==0)){return braces[0]+base+braces[1]}if(recurseTimes<0){if(isRegExp(value)){return ctx.stylize(RegExp.prototype.toString.call(value),"regexp")}else{return ctx.stylize("[Object]","special")}}ctx.seen.push(value);var output;if(array){output=formatArray(ctx,value,recurseTimes,visibleKeys,keys)}else{output=map(keys,function(key){return formatProperty(ctx,value,recurseTimes,visibleKeys,key,array)})}ctx.seen.pop();return reduceToSingleString(output,base,braces)}function formatProperty(ctx,value,recurseTimes,visibleKeys,key,array){var name,str,desc;desc={value:value[key]};if(Object.getOwnPropertyDescriptor){desc=Object.getOwnPropertyDescriptor(value,key)||desc}if(desc.get){if(desc.set){str=ctx.stylize("[Getter/Setter]","special")}else{str=ctx.stylize("[Getter]","special")}}else{if(desc.set){str=ctx.stylize("[Setter]","special")}}if(!hasOwn(visibleKeys,key)){name="["+key+"]"}if(!str){if(indexOf(ctx.seen,desc.value)<0){if(isNull(recurseTimes)){str=formatValue(ctx,desc.value,null)}else{str=formatValue(ctx,desc.value,recurseTimes-1)}if(str.indexOf("\n")>-1){if(array){str=map(str.split("\n"),function(line){return" "+line}).join("\n").substr(2)}else{str="\n"+map(str.split("\n"),function(line){return" "+line}).join("\n")}}}else{str=ctx.stylize("[Circular]","special")}}if(isUndefined(name)){if(array&&key.match(/^\d+$/)){return str}name=JSON.stringify(""+key);if(name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)){name=name.substr(1,name.length-2);name=ctx.stylize(name,"name")}else{name=name.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'");name=ctx.stylize(name,"string")}}return name+": "+str}function formatPrimitive(ctx,value){if(isUndefined(value))return ctx.stylize("undefined","undefined");if(isString(value)){var simple="'"+JSON.stringify(value).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return ctx.stylize(simple,"string")}if(isNumber(value))return ctx.stylize(""+value,"number");if(isBoolean(value))return ctx.stylize(""+value,"boolean");if(isNull(value))return ctx.stylize("null","null")}function reduceToSingleString(output,base,braces){var numLinesEst=0;var length=reduce(output,function(prev,cur){numLinesEst++;if(cur.indexOf("\n")>=0)numLinesEst++;return prev+cur.replace(/\u001b\[\d\d?m/g,"").length+1},0);if(length>60){return braces[0]+(base===""?"":base+"\n ")+" "+output.join(",\n ")+" "+braces[1]}return braces[0]+base+" "+output.join(", ")+" "+braces[1]}function _extend(origin,add){if(!add||!isObject(add))return origin;var keys=objectKeys(add);var i=keys.length;while(i--){origin[keys[i]]=add[keys[i]]}return origin}},{"array-map":2,"array-reduce":3,foreach:4,indexof:5,isarray:6,json3:7,"object-keys":9}]},{},[1]); diff --git a/lib/_patch/browserstack.js b/lib/_patch/browserstack.js index 4080b60..4ea1f07 100644 --- a/lib/_patch/browserstack.js +++ b/lib/_patch/browserstack.js @@ -39,11 +39,13 @@ _console_log = console.log; - console.log = function (arguments) { - post('/_log/', arguments, function () {}); + console.log = function () { + var args = BrowserStack.util.toArray(arguments).map(BrowserStack.util.inspect); + post('/_log/', { arguments: args }, function () {}); }; - console.warn = function (arguments) { - post('/_log/', arguments, function () {}); + console.warn = function () { + var args = BrowserStack.util.toArray(arguments).map(BrowserStack.util.inspect); + post('/_log/', { arguments: args }, function () {}); }; BrowserStack.post = post; diff --git a/lib/server.js b/lib/server.js index b937bbd..b166d55 100644 --- a/lib/server.js +++ b/lib/server.js @@ -3,6 +3,7 @@ var Log = require('./logger'), http = require('http'), url = require('url'), path = require('path'), + util = require('util'), fs = require('fs'), qs = require('querystring'), utils = require('./utils'), @@ -10,7 +11,8 @@ var Log = require('./logger'), proxyServer = require('./proxy').proxyServer, chalk = require('chalk'), mime = require('mime'), - send = require('send'); + send = require('send'), + vm = require('vm'); exports.Server = function Server(bsClient, workers) { @@ -33,6 +35,7 @@ exports.Server = function Server(bsClient, workers) { var scripts = [ 'json2.js', 'browserstack.js', + 'browserstack-util.js' ]; var framework_scripts = { @@ -327,9 +330,34 @@ exports.Server = function Server(bsClient, workers) { '_log': function logHandler(uri, body, request, response) { var uuid = getWorkerUuid(request); var query = parseBody(body); - logger.trace('[%s] _log', ((uuid && workers[uuid]) || {}).id, query); - logger.info('[' + request.headers['x-browser-string'] + '] ' + query); + + var logged = false; + + if (query && Array.isArray(query.arguments)) { + var context = { input: query.arguments, format: util.format, output: '' }; + var tryEvalOrString = function (arg) { + try { + return eval('o = ' + arg); + } catch (e) { + return arg; + } + }; + + try { + // eval each element of query.arguments safely in an isolated context + vm.runInNewContext('output = format.apply(null, input.map(' + tryEvalOrString.toString() + '));', context); + logger.info('[' + request.headers['x-browser-string'] + '] ' + context.output); + logged = true; + } catch (e) { + logger.debug('_log: failed to format console log data', query); + } + } + + if (!logged) { + logger.info('[' + request.headers['x-browser-string'] + '] ' + query); + } + response.end(); }, '_patch': function patchHandler(uri, body, request, response) { From ad86ae8126184ac8cd0502f660e9a7411c32c423 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Mon, 8 Feb 2016 15:51:13 +0000 Subject: [PATCH 063/162] Minor change in eval fn string --- lib/server.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/server.js b/lib/server.js index b166d55..e2ff8d9 100644 --- a/lib/server.js +++ b/lib/server.js @@ -336,17 +336,11 @@ exports.Server = function Server(bsClient, workers) { if (query && Array.isArray(query.arguments)) { var context = { input: query.arguments, format: util.format, output: '' }; - var tryEvalOrString = function (arg) { - try { - return eval('o = ' + arg); - } catch (e) { - return arg; - } - }; + var tryEvalOrString = 'function (arg) { try { return eval(\'o = \' + arg); } catch (e) { return arg; } }'; try { // eval each element of query.arguments safely in an isolated context - vm.runInNewContext('output = format.apply(null, input.map(' + tryEvalOrString.toString() + '));', context); + vm.runInNewContext('output = format.apply(null, input.map(' + tryEvalOrString + '));', context); logger.info('[' + request.headers['x-browser-string'] + '] ' + context.output); logged = true; } catch (e) { From c31790a29fed0ee1047648a4c36084c89cf1cab2 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Mon, 8 Feb 2016 15:51:47 +0000 Subject: [PATCH 064/162] Bumped version to v0.4.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fe69392..2d4194b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.4.1", + "version": "0.4.2", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From b8f043ab153897d412f6009ec011db84544add17 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Tue, 9 Feb 2016 13:44:04 +0530 Subject: [PATCH 065/162] Load config only when required Enables unit testing of functions without having browserstack.json --- lib/utils.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 2a1bcd2..779eae1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,9 +1,9 @@ var Log = require('./logger'), logger = new Log(global.logLevel), - config = require('./config'), http = require('http'), url = require('url'), - querystring = require('querystring'); + querystring = require('querystring'), + config; String.prototype.escapeSpecialChars = function() { return this.replace(/\n/g, '\\n') @@ -51,6 +51,10 @@ var objectSize = function objectSize(obj) { }; var alertBrowserStack = function alertBrowserStack(subject, content, params, fn) { + if (!config) { + config = require('./config'); + } + var endpoint = config.alert_endpoint || 'http://www.browserstack.com/automate/alert'; var urlObject = url.parse(endpoint); From b61158f1882bf17519cbe4e51ff829fb7a22fa85 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Tue, 9 Feb 2016 13:58:36 +0530 Subject: [PATCH 066/162] Scripts for testing external repositories --- package.json | 6 +- tests/external-tests.js | 187 +++++++++++++++++++++++++++++ tests/helper.js | 257 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 449 insertions(+), 1 deletion(-) create mode 100755 tests/external-tests.js create mode 100644 tests/helper.js diff --git a/package.json b/package.json index 2d4194b..7b92bde 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,10 @@ "browserstack-runner": "bin/cli.js" }, "scripts": { - "test": "node_modules/.bin/mocha tests/unit && node_modules/.bin/jshint lib/*.js bin/" + "lint": "node_modules/.bin/jshint lib/*.js bin/ tests/*.js", + "test-unit": "node_modules/.bin/mocha tests/unit", + "test-ci": "npm run lint && npm run test-unit && TEST_MODE=all tests/external-tests.js", + "test": "npm run lint && npm run test-unit && TEST_MODE=required tests/external-tests.js", + "update-util": "node_modules/.bin/browserify lib/client-browserstack-util.js | node_modules/.bin/uglifyjs > lib/_patch/browserstack-util.js" } } diff --git a/tests/external-tests.js b/tests/external-tests.js new file mode 100755 index 0000000..053803f --- /dev/null +++ b/tests/external-tests.js @@ -0,0 +1,187 @@ +#! /usr/bin/env node + +var path = require('path'); +var Helper = require('./helper'); + +var browserstackConfig = { + username: 'BROWSERSTACK_USERNAME', + key: 'BROWSERSTACK_KEY' +}; + +var mode = (process.env.TEST_MODE || 'all').toLowerCase(); +var runnerPath = path.resolve(path.join(__dirname, '..', 'bin', 'cli.js')); +var testHome = path.resolve(__dirname); +process.chdir(testHome); + +var repositories = [ + { + name: 'qunit', + tag: '1.21.0', + url: 'https://github.com/jquery/qunit.git', + test_framework: 'qunit', + browsers: [ + { + 'browser': 'firefox', + 'browser_version': '44.0', + 'os': 'OS X', + 'os_version': 'El Capitan' + } + ], + test_path: [ + 'test/index.html' + ], + expected_results: { + tests: 534, + passed: 534, + failed: 0 + } + }, + { + name: 'mocha', + tag: 'v2.4.5', + url: 'https://github.com/mochajs/mocha.git', + test_framework: 'mocha', + browsers: [ + { + 'browser': 'ie', + 'browser_version': '11.0', + 'os': 'Windows', + 'os_version': '10' + } + ], + test_path: [ + 'test/browser/index.html', + 'test/browser/large.html', + 'test/browser/opts.html' + ], + expected_results: { + tests: 89, + passed: 80, + failed: 9 + } + }, + { + name: 'spine', + tag: 'v.1.6.2', + url: 'https://github.com/spine/spine.git', + test_framework: 'jasmine2', + browsers: [ + { + 'browser': 'safari', + 'browser_version': '9.0', + 'os': 'OS X', + 'os_version': 'El Capitan' + } + ], + test_path: [ + 'test/index.html' + ], + expected_results: { + tests: 161, + passed: 161, + failed: 0 + } + }, + { + name: 'spine', + tag: 'v1.0.0', + url: 'https://github.com/spine/spine.git', + test_framework: 'jasmine', + browsers: [ + { + 'browser': 'safari', + 'browser_version': '5.1', + 'os': 'OS X', + 'os_version': 'Snow Leopard' + } + ], + test_path: [ + 'test/index.html' + ], + patches: [ + { + find: 'jasmine.getEnv().execute();', + replace: 'window.onload = function () { jasmine.getEnv().execute(); };' + } + ], + expected_results: { + tests: 63, + passed: 63, + failed: 0 + } + } +]; + +var repositoriesOptional = [ + { + name: 'qunit', + tag: 'v1.0.0', + url: 'https://github.com/jquery/qunit.git', + test_framework: 'qunit', + browsers: [ + { + 'browser': 'firefox', + 'browser_version': '44.0', + 'os': 'OS X', + 'os_version': 'Snow Leopard' + } + ], + test_path: [ + 'test/index.html', + 'test/logs.html' + ], + expected_results: { + tests: 323, + passed: 323, + failed: 0 + } + }, + { + name: 'mocha', + tag: '1.21.5', + url: 'https://github.com/mochajs/mocha.git', + test_framework: 'mocha', + browsers: [ + { + 'browser': 'ie', + 'browser_version': '10.0', + 'os': 'Windows', + 'os_version': '7' + } + ], + test_path: [ + 'test/browser/index.html', + 'test/browser/large.html', + 'test/browser/opts.html' + ], + expected_results: { + tests: 84, + passed: 77, + failed: 7 + } + } +]; + +function run(repositories) { + Helper.runRepositories(browserstackConfig, repositories, testHome, runnerPath, function (err) { + if (err) { + console.log(err.stack); + throw err; + } + + console.log('Done.'); + }); +} + +switch (mode) { + case 'required': + run(repositories); + break; + + case 'optional': + run(repositoriesOptional); + break; + + default: + run([].concat(repositories).concat(repositoriesOptional)); +} diff --git a/tests/helper.js b/tests/helper.js new file mode 100644 index 0000000..99ea17a --- /dev/null +++ b/tests/helper.js @@ -0,0 +1,257 @@ + +var exec = require('child_process').execFile; +var execSync = require('child_process').execFileSync; +var fs = require('fs'); +var path = require('path'); +var util = require('util'); + +module.exports = { + runRepositories: runRepositories, + runRepository: runRepository +}; + +function runRepositories(browserstackConfig, repositories, testHome, runnerPath, callback) { + var repository = repositories.shift(); + if (!repository) { + return callback(null); + } + + runRepository(testHome, runnerPath, repository, browserstackConfig, function (err) { + if (err) { + return callback(err); + } + + process.nextTick(function () { + runRepositories(browserstackConfig, repositories, testHome, runnerPath, callback); + }); + }); +} + + +function runRepository(testHome, runnerPath, repository, config, callback) { + var done = function () { + try { + process.chdir(testHome); + } catch(e) { + return callback('Error switching to test directory: ' + e); + } + + callback.apply(null, Array.prototype.slice.call(arguments, 0)); + }; + + try { + fs.mkdirSync(repository.test_framework); + } catch (e) { + // ignore + } + + process.chdir(repository.test_framework); + repository.branch = repository.branch || repository.tag; + + var dirName = repository.name + '-' + repository.branch; + var conf = {}; + for (var k in config) { + conf[k] = config[k]; + } + + gitCloneByBranch(repository, dirName, function (err) { + if (err && !err.message.match(/already exists/)) { + return done(err); + } + + try { + console.log('Switching to repository:', dirName); + process.chdir(dirName); + } catch (e) { + return callback('Error switching to project directory: ' + e); + } + + if (repository.patches && repository.patches.length) { + patchFiles(repository.test_path, repository.patches); + } + + conf.test_framework = repository.test_framework; + conf.browsers = repository.browsers; + conf.project = repository.name; + conf.build = repository.branch; + conf.test_path = repository.test_path; + + runTests(runnerPath, process.cwd(), conf, repository.expected_results, done); + }); +} + +function gitCloneByBranch(repository, dirName, callback) { + fs.lstat(dirName, function (err, stat) { + var dirExistsError = new Error(dirName + ' already exists'); + if (err && (err.code !== 'ENOENT' || err.errno !== -2)) { + return callback(err); + } + + if (stat && stat.isDirectory()) { + return callback(dirExistsError); + } + + var cmd = util.format('git clone -b %s --single-branch --depth 1 %s %s', repository.branch, repository.url, dirName); + console.log('Executing:', cmd); + var cmdParts = cmd.split(' '); + + runCommand(cmdParts.shift(), cmdParts, false, null, callback); + }); +} + +function patchFiles(files, patches) { + if (files && files.length && patches && patches.length) { + files.forEach(function (f) { + try { + var content = fs.readFileSync(f, 'utf8'); + patches.forEach(function (p) { + if (content.indexOf(p.replace) === -1) { + content = content.replace(p.find, p.replace); + } + }); + + fs.writeFileSync(f, content, 'utf8'); + } catch (e) { + console.warn(e); + } + }); + } +} + +function initRepository() { + try { + execSync('npm', [ 'install' ]); + } catch (e) { + console.error(e.message || e.toString()); + } + + try { + var stat = fs.lstatSync('bower.json'); + if (stat && stat.isFile()) { + execSync('bower', [ 'install' ]); + } + } catch (e) { + if (e.code !== 'ENOENT' || e.errno !== -2) { + console.warn(e.message || e.toString()); + } + } +} + +function runTests(runnerPath, projectDir, conf, expectedResults, callback) { + var results = { + tests: 0, + passed: 0, + failed: 0 + }; + + initRepository(); + + var confPath = path.join(process.cwd(), 'browserstack.json'); + var confString = JSON.stringify(conf, null, 4); + console.log('Creating config (%s):\n%s', confPath, confString); + + fs.writeFile(confPath, confString, 'utf8', function (err) { + if (err) { + return callback(err); + } + + console.log('Running tests:', projectDir); + runCommand(runnerPath, [], true, function (data, done) { + if (data && data.length) { + var matches = data.match(/\[(.*)\] (passed|failed): (\d+) tests, (\d+) passed, (\d+) failed.*[^\n]/i); + if (matches && matches.length > 5) { + // results.pages.push(matches[1].split(', ').slice(2).join('')); + + [ 'failed', 'passed', 'tests' ].forEach(function (k) { + results[k] += parseInt(matches.pop()); + }); + + console.log('>', data.trim()); + } + } + + // continue until end + done(false); + }, function (err) { + if (err) { + return callback(err); + } + + var diff = Object.keys(results).reduce(function (o, k) { + if (isFinite(expectedResults[k]) && expectedResults[k] !== results[k]) { + o.push(util.format('Mismatch in %s: %d !== %d', k, results[k], expectedResults[k])); + } + + return o; + }, []); + + callback(diff.length ? new Error(diff.join('\r\n')) : null, results); + }); + }); +} + +function runCommand(cmd, args, ignoreErr, processOutputHook, callback) { + var isRunning = true, + output = '', + subProcess, + timeoutHandle; + + if (!processOutputHook) { + processOutputHook = function (data, done) { + output += data; + done(); + }; + } + + var callbackOnce = function (err, result) { + clearTimeout(timeoutHandle); + if (subProcess && isRunning) { + try { + process.kill(subProcess.pid, 'SIGKILL'); + subProcess = null; + } catch (e) { + } + } + + callback && callback(err, result); + callback = null; + }; + + var processOutput = function (isError) { + return function (data) { + processOutputHook(data, function (isDone) { + if (isDone) { + isError ? callbackOnce(new Error(data)) : callbackOnce(null, data); + } + }); + }; + }; + + try { + subProcess = exec(cmd, args, function (error, stdout, stderr) { + isRunning = false; + + if (error) { + if (ignoreErr) { + if (stdout && !stdout.match(/tests done, failures/)) { + console.warn(stdout || stderr); + console.log(error.stack); + } + + callbackOnce(null); + } else { + callbackOnce(new Error('failed to get process output: ' + error)); + } + } else { + callbackOnce(null, stdout || stderr || output || error); + } + }); + + subProcess.stdout.on('data', processOutput(false)); + subProcess.stderr.on('data', processOutput(true)); + } catch (e) { + // Handles EACCESS and other errors when binary file exists, + // but doesn't have necessary permissions (among other issues) + callbackOnce(new Error('failed to get process output: ' + e)); + } +} From c07cbc8d59101779e40f84517c6eb119d850a0f3 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Tue, 9 Feb 2016 14:08:45 +0530 Subject: [PATCH 067/162] Added .travis.yml --- .travis.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4f488f2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: node_js + +node_js: + - 'stable' + +before_install: + - npm install -g grunt-cli jshint gulp + - true && `base64 --decode <<< ZXhwb3J0IEJST1dTRVJTVEFDS19VU0VSTkFNRT1icm93c2Vyc3RhY2tydW5uMQo=` + - true && `base64 --decode <<< ZXhwb3J0IEJST1dTRVJTVEFDS19LRVk9NW5qc0d5emdqNkpWMmh3eUh1dHEK` + +script: + - npm run-script test-ci + +cache: + directories: + - node_modules From 6f49745692f20eecd6db4665a6c6b78f31c17392 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Tue, 9 Feb 2016 15:22:13 +0530 Subject: [PATCH 068/162] Prefix build with Travis build no. --- tests/helper.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/helper.js b/tests/helper.js index 99ea17a..feac305 100644 --- a/tests/helper.js +++ b/tests/helper.js @@ -73,7 +73,9 @@ function runRepository(testHome, runnerPath, repository, config, callback) { conf.test_framework = repository.test_framework; conf.browsers = repository.browsers; conf.project = repository.name; - conf.build = repository.branch; + + var ciPrefix = process.env.TRAVIS_BUILD_NUMBER; + conf.build = (ciPrefix ? ciPrefix + '-' : '') + repository.branch; conf.test_path = repository.test_path; runTests(runnerPath, process.cwd(), conf, repository.expected_results, done); From f5d3c7993cc288d148126385f3b0d5ac275a0e50 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Mon, 29 Feb 2016 14:15:30 +0530 Subject: [PATCH 069/162] Fixed indentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bedd979..9b66a83 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ A sample configuration file: Example: ```json { - "browser": "ie", + "browser": "ie", "browser_version": "10.0", "os": "Windows", "os_version": "8" From c2efa492973aeeb2d88c5922a39ed61e301dc719 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Wed, 16 Mar 2016 20:10:57 +0000 Subject: [PATCH 070/162] Using webpack in place of browserify for requirejs compat --- lib/_patch/browserstack-util.js | 2 +- lib/client-browserstack-util.js | 22 ++++++++++++++++++++++ package.json | 2 +- webpack.config.js | 17 +++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 lib/client-browserstack-util.js create mode 100644 webpack.config.js diff --git a/lib/_patch/browserstack-util.js b/lib/_patch/browserstack-util.js index fddda62..6821bf9 100644 --- a/lib/_patch/browserstack-util.js +++ b/lib/_patch/browserstack-util.js @@ -1 +1 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o=3;if(hasAcc&&xs.reduce)return xs.reduce(f,acc);if(xs.reduce)return xs.reduce(f);for(var i=0;i1)))/4)-floor((year-1901+month)/100)+floor((year-1601+month)/400)}}if(!(isProperty=objectProto.hasOwnProperty)){isProperty=function(property){var members={},constructor;if((members.__proto__=null,members.__proto__={toString:1},members).toString!=getClass){isProperty=function(property){var original=this.__proto__,result=property in(this.__proto__=null,this);this.__proto__=original;return result}}else{constructor=members.constructor;isProperty=function(property){var parent=(this.constructor||constructor).prototype;return property in this&&!(property in parent&&this[property]===parent[property])}}members=null;return isProperty.call(this,property)}}var PrimitiveTypes={"boolean":1,number:1,string:1,undefined:1};var isHostType=function(object,property){var type=typeof object[property];return type=="object"?!!object[property]:!PrimitiveTypes[type]};forEach=function(object,callback){var size=0,Properties,members,property;(Properties=function(){this.valueOf=0}).prototype.valueOf=0;members=new Properties;for(property in members){if(isProperty.call(members,property)){size++}}Properties=members=null;if(!size){members=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,length;var hasProperty=!isFunction&&typeof object.constructor!="function"&&isHostType(object,"hasOwnProperty")?object.hasOwnProperty:isProperty;for(property in object){if(!(isFunction&&property=="prototype")&&hasProperty.call(object,property)){callback(property)}}for(length=members.length;property=members[--length];hasProperty.call(object,property)&&callback(property));}}else if(size==2){forEach=function(object,callback){var members={},isFunction=getClass.call(object)==functionClass,property;for(property in object){if(!(isFunction&&property=="prototype")&&!isProperty.call(members,property)&&(members[property]=1)&&isProperty.call(object,property)){callback(property)}}}}else{forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,isConstructor;for(property in object){if(!(isFunction&&property=="prototype")&&isProperty.call(object,property)&&!(isConstructor=property==="constructor")){callback(property)}}if(isConstructor||isProperty.call(object,property="constructor")){callback(property)}}}return forEach(object,callback)};if(!has("json-stringify")){var Escapes={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};var leadingZeroes="000000";var toPaddedString=function(width,value){return(leadingZeroes+(value||0)).slice(-width)};var unicodePrefix="\\u00";var quote=function(value){var result='"',index=0,length=value.length,useCharIndex=!charIndexBuggy||length>10;var symbols=useCharIndex&&(charIndexBuggy?value.split(""):value);for(;index-1/0&&value<1/0){if(getDay){date=floor(value/864e5);for(year=floor(date/365.2425)+1970-1;getDay(year+1,0)<=date;year++);for(month=floor((date-getDay(year,0))/30.42);getDay(year,month+1)<=date;month++);date=1+date-getDay(year,month);time=(value%864e5+864e5)%864e5;hours=floor(time/36e5)%24;minutes=floor(time/6e4)%60;seconds=floor(time/1e3)%60;milliseconds=time%1e3}else{year=value.getUTCFullYear();month=value.getUTCMonth();date=value.getUTCDate();hours=value.getUTCHours();minutes=value.getUTCMinutes();seconds=value.getUTCSeconds();milliseconds=value.getUTCMilliseconds()}value=(year<=0||year>=1e4?(year<0?"-":"+")+toPaddedString(6,year<0?-year:year):toPaddedString(4,year))+"-"+toPaddedString(2,month+1)+"-"+toPaddedString(2,date)+"T"+toPaddedString(2,hours)+":"+toPaddedString(2,minutes)+":"+toPaddedString(2,seconds)+"."+toPaddedString(3,milliseconds)+"Z"}else{value=null}}else if(typeof value.toJSON=="function"&&(className!=numberClass&&className!=stringClass&&className!=arrayClass||isProperty.call(value,"toJSON"))){value=value.toJSON(property)}}if(callback){value=callback.call(object,property,value)}if(value===null){return"null"}className=getClass.call(value);if(className==booleanClass){return""+value}else if(className==numberClass){return value>-1/0&&value<1/0?""+value:"null"}else if(className==stringClass){return quote(""+value)}if(typeof value=="object"){for(length=stack.length;length--;){if(stack[length]===value){throw TypeError()}}stack.push(value);results=[];prefix=indentation;indentation+=whitespace;if(className==arrayClass){for(index=0,length=value.length;index0){for(whitespace="",width>10&&(width=10);whitespace.length=48&&charCode<=57||charCode>=97&&charCode<=102||charCode>=65&&charCode<=70)){abort()}}value+=fromCharCode("0x"+source.slice(begin,Index));break;default:abort()}}else{if(charCode==34){break}charCode=source.charCodeAt(Index);begin=Index;while(charCode>=32&&charCode!=92&&charCode!=34){charCode=source.charCodeAt(++Index)}value+=source.slice(begin,Index)}}if(source.charCodeAt(Index)==34){Index++;return value}abort();default:begin=Index;if(charCode==45){isSigned=true;charCode=source.charCodeAt(++Index)}if(charCode>=48&&charCode<=57){if(charCode==48&&(charCode=source.charCodeAt(Index+1),charCode>=48&&charCode<=57)){abort()}isSigned=false;for(;Index=48&&charCode<=57);Index++);if(source.charCodeAt(Index)==46){position=++Index;for(;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}charCode=source.charCodeAt(Index);if(charCode==101||charCode==69){charCode=source.charCodeAt(++Index);if(charCode==43||charCode==45){Index++}for(position=Index;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}return+source.slice(begin,Index)}if(isSigned){abort()}if(source.slice(Index,Index+4)=="true"){Index+=4;return true}else if(source.slice(Index,Index+5)=="false"){Index+=5;return false}else if(source.slice(Index,Index+4)=="null"){Index+=4;return null}abort()}}return"$"};var get=function(value){var results,hasMembers;if(value=="$"){abort()}if(typeof value=="string"){if((charIndexBuggy?value.charAt(0):value[0])=="@"){return value.slice(1)}if(value=="["){results=[];for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="]"){break}if(hasMembers){if(value==","){value=lex();if(value=="]"){abort()}}else{abort()}}if(value==","){abort()}results.push(get(value))}return results}else if(value=="{"){results={};for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="}"){break}if(hasMembers){if(value==","){value=lex();if(value=="}"){abort()}}else{abort()}}if(value==","||typeof value!="string"||(charIndexBuggy?value.charAt(0):value[0])!="@"||lex()!=":"){abort()}results[value.slice(1)]=get(lex())}return results}abort()}return value};var update=function(source,property,callback){var element=walk(source,property,callback);if(element===undef){delete source[property]}else{source[property]=element}};var walk=function(source,property,callback){var value=source[property],length;if(typeof value=="object"&&value){if(getClass.call(value)==arrayClass){for(length=value.length;length--;){update(value,length,callback)}}else{forEach(value,function(property){update(value,property,callback)})}}return callback.call(source,property,value)};exports.parse=function(source,callback){var result,value;Index=0;Source=""+source;result=get(lex());if(lex()!="$"){abort()}Index=Source=null;return callback&&getClass.call(callback)==functionClass?walk((value={},value[""]=result,value),"",callback):result}}}exports["runInContext"]=runInContext;return exports}if(typeof exports=="object"&&exports&&!exports.nodeType&&!isLoader){runInContext(root,exports)}else{var nativeJSON=root.JSON;var JSON3=runInContext(root,root["JSON3"]={noConflict:function(){root.JSON=nativeJSON;return JSON3}});root.JSON={parse:JSON3.parse,stringify:JSON3.stringify}}if(isLoader){define(function(){return JSON3})}})(this)}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],8:[function(require,module,exports){"use strict";var hasOwn=Object.prototype.hasOwnProperty;var toString=Object.prototype.toString;var isFunction=function(fn){return typeof fn==="function"&&!(fn instanceof RegExp)||toString.call(fn)==="[object Function]"};module.exports=function forEach(obj,fn){if(!isFunction(fn)){throw new TypeError("iterator must be a function")}var i,k,isString=typeof obj==="string",l=obj.length,context=arguments.length>2?arguments[2]:null;if(l===+l){for(i=0;i=0&&toString.call(value.callee)==="[object Function]"}return isArguments}},{}],11:[function(require,module,exports){var map=require("array-map");var indexOf=require("indexof");var isArray=require("isarray");var forEach=require("foreach");var reduce=require("array-reduce");var getObjectKeys=require("object-keys");var JSON=require("json3");function objectKeys(val){if(Object.keys)return Object.keys(val);return getObjectKeys(val)}module.exports=inspect;function inspect(obj,opts){var ctx={seen:[],stylize:stylizeNoColor};if(arguments.length>=3)ctx.depth=arguments[2];if(arguments.length>=4)ctx.colors=arguments[3];if(isBoolean(opts)){ctx.showHidden=opts}else if(opts){_extend(ctx,opts)}if(isUndefined(ctx.showHidden))ctx.showHidden=false;if(isUndefined(ctx.depth))ctx.depth=2;if(isUndefined(ctx.colors))ctx.colors=false;if(isUndefined(ctx.customInspect))ctx.customInspect=true;if(ctx.colors)ctx.stylize=stylizeWithColor;return formatValue(ctx,obj,ctx.depth)}inspect.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]};inspect.styles={special:"cyan",number:"yellow","boolean":"yellow",undefined:"grey","null":"bold",string:"green",date:"magenta",regexp:"red"};function stylizeNoColor(str,styleType){return str}function isBoolean(arg){return typeof arg==="boolean"}function isUndefined(arg){return arg===void 0}function stylizeWithColor(str,styleType){var style=inspect.styles[styleType];if(style){return"["+inspect.colors[style][0]+"m"+str+"["+inspect.colors[style][1]+"m"}else{return str}}function isFunction(arg){return typeof arg==="function"}function isString(arg){return typeof arg==="string"}function isNumber(arg){return typeof arg==="number"}function isNull(arg){return arg===null}function hasOwn(obj,prop){return Object.prototype.hasOwnProperty.call(obj,prop)}function isRegExp(re){return isObject(re)&&objectToString(re)==="[object RegExp]"}function isObject(arg){return typeof arg==="object"&&arg!==null}function isError(e){return isObject(e)&&(objectToString(e)==="[object Error]"||e instanceof Error)}function isDate(d){return isObject(d)&&objectToString(d)==="[object Date]"}function objectToString(o){return Object.prototype.toString.call(o)}function arrayToHash(array){var hash={};forEach(array,function(val,idx){hash[val]=true});return hash}function formatArray(ctx,value,recurseTimes,visibleKeys,keys){var output=[];for(var i=0,l=value.length;i=0||indexOf(keys,"description")>=0)){return formatError(value)}if(keys.length===0){if(isFunction(value)){var name=value.name?": "+value.name:"";return ctx.stylize("[Function"+name+"]","special")}if(isRegExp(value)){return ctx.stylize(RegExp.prototype.toString.call(value),"regexp")}if(isDate(value)){return ctx.stylize(Date.prototype.toString.call(value),"date")}if(isError(value)){return formatError(value)}}var base="",array=false,braces=["{","}"];if(isArray(value)){array=true;braces=["[","]"]}if(isFunction(value)){var n=value.name?": "+value.name:"";base=" [Function"+n+"]"}if(isRegExp(value)){base=" "+RegExp.prototype.toString.call(value)}if(isDate(value)){base=" "+Date.prototype.toUTCString.call(value)}if(isError(value)){base=" "+formatError(value)}if(keys.length===0&&(!array||value.length==0)){return braces[0]+base+braces[1]}if(recurseTimes<0){if(isRegExp(value)){return ctx.stylize(RegExp.prototype.toString.call(value),"regexp")}else{return ctx.stylize("[Object]","special")}}ctx.seen.push(value);var output;if(array){output=formatArray(ctx,value,recurseTimes,visibleKeys,keys)}else{output=map(keys,function(key){return formatProperty(ctx,value,recurseTimes,visibleKeys,key,array)})}ctx.seen.pop();return reduceToSingleString(output,base,braces)}function formatProperty(ctx,value,recurseTimes,visibleKeys,key,array){var name,str,desc;desc={value:value[key]};if(Object.getOwnPropertyDescriptor){desc=Object.getOwnPropertyDescriptor(value,key)||desc}if(desc.get){if(desc.set){str=ctx.stylize("[Getter/Setter]","special")}else{str=ctx.stylize("[Getter]","special")}}else{if(desc.set){str=ctx.stylize("[Setter]","special")}}if(!hasOwn(visibleKeys,key)){name="["+key+"]"}if(!str){if(indexOf(ctx.seen,desc.value)<0){if(isNull(recurseTimes)){str=formatValue(ctx,desc.value,null)}else{str=formatValue(ctx,desc.value,recurseTimes-1)}if(str.indexOf("\n")>-1){if(array){str=map(str.split("\n"),function(line){return" "+line}).join("\n").substr(2)}else{str="\n"+map(str.split("\n"),function(line){return" "+line}).join("\n")}}}else{str=ctx.stylize("[Circular]","special")}}if(isUndefined(name)){if(array&&key.match(/^\d+$/)){return str}name=JSON.stringify(""+key);if(name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)){name=name.substr(1,name.length-2);name=ctx.stylize(name,"name")}else{name=name.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'");name=ctx.stylize(name,"string")}}return name+": "+str}function formatPrimitive(ctx,value){if(isUndefined(value))return ctx.stylize("undefined","undefined");if(isString(value)){var simple="'"+JSON.stringify(value).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return ctx.stylize(simple,"string")}if(isNumber(value))return ctx.stylize(""+value,"number");if(isBoolean(value))return ctx.stylize(""+value,"boolean");if(isNull(value))return ctx.stylize("null","null")}function reduceToSingleString(output,base,braces){var numLinesEst=0;var length=reduce(output,function(prev,cur){numLinesEst++;if(cur.indexOf("\n")>=0)numLinesEst++;return prev+cur.replace(/\u001b\[\d\d?m/g,"").length+1},0);if(length>60){return braces[0]+(base===""?"":base+"\n ")+" "+output.join(",\n ")+" "+braces[1]}return braces[0]+base+" "+output.join(", ")+" "+braces[1]}function _extend(origin,add){if(!add||!isObject(add))return origin;var keys=objectKeys(add);var i=keys.length;while(i--){origin[keys[i]]=add[keys[i]]}return origin}},{"array-map":2,"array-reduce":3,foreach:4,indexof:5,isarray:6,json3:7,"object-keys":9}]},{},[1]); +!function(t){function e(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var r={};return e.m=t,e.c=r,e.p="",e(0)}([function(t,e,r){!function(t){"use strict";t.BrowserStack=t.BrowserStack||{},t.BrowserStack.util={inspect:r(1),toArray:function(t,e){var r=[];e=e||0;for(var n=e||0;n=3&&(r.depth=arguments[2]),arguments.length>=4&&(r.colors=arguments[3]),i(e)?r.showHidden=e:e&&A(r,e),l(r.showHidden)&&(r.showHidden=!1),l(r.depth)&&(r.depth=2),l(r.colors)&&(r.colors=!1),l(r.customInspect)&&(r.customInspect=!0),r.colors&&(r.stylize=u),w(r,t,r.depth)}function c(t,e){return t}function i(t){return"boolean"==typeof t}function l(t){return void 0===t}function u(t,e){var r=o.styles[e];return r?"["+o.colors[r][0]+"m"+t+"["+o.colors[r][1]+"m":t}function a(t){return"function"==typeof t}function f(t){return"string"==typeof t}function s(t){return"number"==typeof t}function p(t){return null===t}function y(t,e){return Object.prototype.hasOwnProperty.call(t,e)}function g(t){return h(t)&&"[object RegExp]"===d(t)}function h(t){return"object"==typeof t&&null!==t}function b(t){return h(t)&&("[object Error]"===d(t)||t instanceof Error)}function v(t){return h(t)&&"[object Date]"===d(t)}function d(t){return Object.prototype.toString.call(t)}function j(t){var e={};return E(t,function(t,r){e[t]=!0}),e}function O(t,e,r,n,o){for(var c=[],i=0,l=e.length;l>i;++i)y(e,String(i))?c.push(m(t,e,r,n,String(i),!0)):c.push("");return E(o,function(o){o.match(/^\d+$/)||c.push(m(t,e,r,n,o,!0))}),c}function S(t){return"["+Error.prototype.toString.call(t)+"]"}function w(t,e,r){if(t.customInspect&&e&&a(e.inspect)&&e.inspect!==o&&(!e.constructor||e.constructor.prototype!==e)){var c=e.inspect(r,t);return f(c)||(c=w(t,c,r)),c}var i=C(t,e);if(i)return i;var l=n(e),u=j(l);if(t.showHidden&&Object.getOwnPropertyNames&&(l=Object.getOwnPropertyNames(e)),b(e)&&(_(l,"message")>=0||_(l,"description")>=0))return S(e);if(0===l.length){if(a(e)){var s=e.name?": "+e.name:"";return t.stylize("[Function"+s+"]","special")}if(g(e))return t.stylize(RegExp.prototype.toString.call(e),"regexp");if(v(e))return t.stylize(Date.prototype.toString.call(e),"date");if(b(e))return S(e)}var p="",y=!1,h=["{","}"];if(z(e)&&(y=!0,h=["[","]"]),a(e)){var d=e.name?": "+e.name:"";p=" [Function"+d+"]"}if(g(e)&&(p=" "+RegExp.prototype.toString.call(e)),v(e)&&(p=" "+Date.prototype.toUTCString.call(e)),b(e)&&(p=" "+S(e)),0===l.length&&(!y||0==e.length))return h[0]+p+h[1];if(0>r)return g(e)?t.stylize(RegExp.prototype.toString.call(e),"regexp"):t.stylize("[Object]","special");t.seen.push(e);var A;return A=y?O(t,e,r,u,l):T(l,function(n){return m(t,e,r,u,n,y)}),t.seen.pop(),x(A,p,h)}function m(t,e,r,n,o,c){var i,u,a;if(a={value:e[o]},Object.getOwnPropertyDescriptor&&(a=Object.getOwnPropertyDescriptor(e,o)||a),a.get?u=a.set?t.stylize("[Getter/Setter]","special"):t.stylize("[Getter]","special"):a.set&&(u=t.stylize("[Setter]","special")),y(n,o)||(i="["+o+"]"),u||(_(t.seen,a.value)<0?(u=p(r)?w(t,a.value,null):w(t,a.value,r-1),u.indexOf("\n")>-1&&(u=c?T(u.split("\n"),function(t){return" "+t}).join("\n").substr(2):"\n"+T(u.split("\n"),function(t){return" "+t}).join("\n"))):u=t.stylize("[Circular]","special")),l(i)){if(c&&o.match(/^\d+$/))return u;i=k.stringify(""+o),i.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(i=i.substr(1,i.length-2),i=t.stylize(i,"name")):(i=i.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),i=t.stylize(i,"string"))}return i+": "+u}function C(t,e){if(l(e))return t.stylize("undefined","undefined");if(f(e)){var r="'"+k.stringify(e).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return t.stylize(r,"string")}return s(e)?t.stylize(""+e,"number"):i(e)?t.stylize(""+e,"boolean"):p(e)?t.stylize("null","null"):void 0}function x(t,e,r){var n=0,o=N(t,function(t,e){return n++,e.indexOf("\n")>=0&&n++,t+e.replace(/\u001b\[\d\d?m/g,"").length+1},0);return o>60?r[0]+(""===e?"":e+"\n ")+" "+t.join(",\n ")+" "+r[1]:r[0]+e+" "+t.join(", ")+" "+r[1]}function A(t,e){if(!e||!h(e))return t;for(var r=n(e),o=r.length;o--;)t[r[o]]=e[r[o]];return t}var T=r(2),_=r(3),z=r(4),E=r(5),N=r(6),P=r(7),k=r(10);t.exports=o,o.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},o.styles={special:"cyan",number:"yellow","boolean":"yellow",undefined:"grey","null":"bold",string:"green",date:"magenta",regexp:"red"}},function(t,e){t.exports=function(t,e){if(t.map)return t.map(e);for(var n=[],o=0;oi;i++)e.call(o,t[i],i,t);else for(var l in t)r.call(t,l)&&e.call(o,t[l],l,t)}},function(t,e){var r=Object.prototype.hasOwnProperty;t.exports=function(t,e,n){var o=arguments.length>=3;if(o&&t.reduce)return t.reduce(e,n);if(t.reduce)return t.reduce(e);for(var c=0;c2?arguments[2]:null;if(l===+l)for(n=0;l>n;n++)null===u?e(i?t.charAt(n):t[n],n,t):e.call(u,i?t.charAt(n):t[n],n,t);else for(c in t)r.call(t,c)&&(null===u?e(t[c],c,t):e.call(u,t[c],c,t))}},function(t,e){"use strict";var r=Object.prototype.toString;t.exports=function n(t){var e=r.call(t),n="[object Arguments]"===e;return n||(n="[object Array]"!==e&&null!==t&&"object"==typeof t&&"number"==typeof t.length&&t.length>=0&&"[object Function]"===r.call(t.callee)),n}},function(t,e,r){var n;(function(o){!function(c){function i(t,e){function r(t){if(r[t]!==h)return r[t];var c;if("bug-string-char-index"==t)c="a"!="a"[0];else if("json"==t)c=r("json-stringify")&&r("json-parse");else{var i,l='{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';if("json-stringify"==t){var a=e.stringify,f="function"==typeof a&&d;if(f){(i=function(){return 1}).toJSON=i;try{f="0"===a(0)&&"0"===a(new n)&&'""'==a(new o)&&a(v)===h&&a(h)===h&&a()===h&&"1"===a(i)&&"[1]"==a([i])&&"[null]"==a([h])&&"null"==a(null)&&"[null,null,null]"==a([h,v,null])&&a({a:[i,!0,!1,null,"\x00\b\n\f\r "]})==l&&"1"===a(null,i)&&"[\n 1,\n 2\n]"==a([1,2],null,1)&&'"-271821-04-20T00:00:00.000Z"'==a(new u(-864e13))&&'"+275760-09-13T00:00:00.000Z"'==a(new u(864e13))&&'"-000001-01-01T00:00:00.000Z"'==a(new u(-621987552e5))&&'"1969-12-31T23:59:59.999Z"'==a(new u(-1))}catch(s){f=!1}}c=f}if("json-parse"==t){var p=e.parse;if("function"==typeof p)try{if(0===p("0")&&!p(!1)){i=p(l);var y=5==i.a.length&&1===i.a[0];if(y){try{y=!p('" "')}catch(s){}if(y)try{y=1!==p("01")}catch(s){}if(y)try{y=1!==p("1.")}catch(s){}}}}catch(s){y=!1}c=y}}return r[t]=!!c}t||(t=c.Object()),e||(e=c.Object());var n=t.Number||c.Number,o=t.String||c.String,l=t.Object||c.Object,u=t.Date||c.Date,a=t.SyntaxError||c.SyntaxError,f=t.TypeError||c.TypeError,s=t.Math||c.Math,p=t.JSON||c.JSON;"object"==typeof p&&p&&(e.stringify=p.stringify,e.parse=p.parse);var y,g,h,b=l.prototype,v=b.toString,d=new u(-0xc782b5b800cec);try{d=-109252==d.getUTCFullYear()&&0===d.getUTCMonth()&&1===d.getUTCDate()&&10==d.getUTCHours()&&37==d.getUTCMinutes()&&6==d.getUTCSeconds()&&708==d.getUTCMilliseconds()}catch(j){}if(!r("json")){var O="[object Function]",S="[object Date]",w="[object Number]",m="[object String]",C="[object Array]",x="[object Boolean]",A=r("bug-string-char-index");if(!d)var T=s.floor,_=[0,31,59,90,120,151,181,212,243,273,304,334],z=function(t,e){return _[e]+365*(t-1970)+T((t-1969+(e=+(e>1)))/4)-T((t-1901+e)/100)+T((t-1601+e)/400)};(y=b.hasOwnProperty)||(y=function(t){var e,r={};return(r.__proto__=null,r.__proto__={toString:1},r).toString!=v?y=function(t){var e=this.__proto__,r=t in(this.__proto__=null,this);return this.__proto__=e,r}:(e=r.constructor,y=function(t){var r=(this.constructor||e).prototype;return t in this&&!(t in r&&this[t]===r[t])}),r=null,y.call(this,t)});var E={"boolean":1,number:1,string:1,undefined:1},N=function(t,e){var r=typeof t[e];return"object"==r?!!t[e]:!E[r]};if(g=function(t,e){var r,n,o,c=0;(r=function(){this.valueOf=0}).prototype.valueOf=0,n=new r;for(o in n)y.call(n,o)&&c++;return r=n=null,c?g=2==c?function(t,e){var r,n={},o=v.call(t)==O;for(r in t)o&&"prototype"==r||y.call(n,r)||!(n[r]=1)||!y.call(t,r)||e(r)}:function(t,e){var r,n,o=v.call(t)==O;for(r in t)o&&"prototype"==r||!y.call(t,r)||(n="constructor"===r)||e(r);(n||y.call(t,r="constructor"))&&e(r)}:(n=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"],g=function(t,e){var r,o,c=v.call(t)==O,i=!c&&"function"!=typeof t.constructor&&N(t,"hasOwnProperty")?t.hasOwnProperty:y;for(r in t)c&&"prototype"==r||!i.call(t,r)||e(r);for(o=n.length;r=n[--o];i.call(t,r)&&e(r));}),g(t,e)},!r("json-stringify")){var P={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"},k="000000",U=function(t,e){return(k+(e||0)).slice(-t)},J="\\u00",D=function(t){for(var e='"',r=0,n=t.length,o=!A||n>10,c=o&&(A?t.split(""):t);n>r;r++){var i=t.charCodeAt(r);switch(i){case 8:case 9:case 10:case 12:case 13:case 34:case 92:e+=P[i];break;default:if(32>i){e+=J+U(2,i.toString(16));break}e+=o?c[r]:t.charAt(r)}}return e+'"'},F=function(t,e,r,n,o,c,i){var l,u,a,s,p,b,d,j,O,A,_,E,N,P,k,J;try{l=e[t]}catch(I){}if("object"==typeof l&&l)if(u=v.call(l),u!=S||y.call(l,"toJSON"))"function"==typeof l.toJSON&&(u!=w&&u!=m&&u!=C||y.call(l,"toJSON"))&&(l=l.toJSON(t));else if(l>-1/0&&1/0>l){if(z){for(p=T(l/864e5),a=T(p/365.2425)+1970-1;z(a+1,0)<=p;a++);for(s=T((p-z(a,0))/30.42);z(a,s+1)<=p;s++);p=1+p-z(a,s),b=(l%864e5+864e5)%864e5,d=T(b/36e5)%24,j=T(b/6e4)%60,O=T(b/1e3)%60,A=b%1e3}else a=l.getUTCFullYear(),s=l.getUTCMonth(),p=l.getUTCDate(),d=l.getUTCHours(),j=l.getUTCMinutes(),O=l.getUTCSeconds(),A=l.getUTCMilliseconds();l=(0>=a||a>=1e4?(0>a?"-":"+")+U(6,0>a?-a:a):U(4,a))+"-"+U(2,s+1)+"-"+U(2,p)+"T"+U(2,d)+":"+U(2,j)+":"+U(2,O)+"."+U(3,A)+"Z"}else l=null;if(r&&(l=r.call(e,t,l)),null===l)return"null";if(u=v.call(l),u==x)return""+l;if(u==w)return l>-1/0&&1/0>l?""+l:"null";if(u==m)return D(""+l);if("object"==typeof l){for(P=i.length;P--;)if(i[P]===l)throw f();if(i.push(l),_=[],k=c,c+=o,u==C){for(N=0,P=l.length;P>N;N++)E=F(N,l,r,n,o,c,i),_.push(E===h?"null":E);J=_.length?o?"[\n"+c+_.join(",\n"+c)+"\n"+k+"]":"["+_.join(",")+"]":"[]"}else g(n||l,function(t){var e=F(t,l,r,n,o,c,i);e!==h&&_.push(D(t)+":"+(o?" ":"")+e)}),J=_.length?o?"{\n"+c+_.join(",\n"+c)+"\n"+k+"}":"{"+_.join(",")+"}":"{}";return i.pop(),J}};e.stringify=function(t,e,r){var n,o,c,i;if("function"==typeof e||"object"==typeof e&&e)if((i=v.call(e))==O)o=e;else if(i==C){c={};for(var l,u=0,a=e.length;a>u;l=e[u++],i=v.call(l),(i==m||i==w)&&(c[l]=1));}if(r)if((i=v.call(r))==w){if((r-=r%1)>0)for(n="",r>10&&(r=10);n.lengthI;)switch(o=c.charCodeAt(I)){case 9:case 10:case 13:case 32:I++;break;case 123:case 125:case 91:case 93:case 58:case 44:return t=A?c.charAt(I):c[I],I++,t;case 34:for(t="@",I++;i>I;)if(o=c.charCodeAt(I),32>o)H();else if(92==o)switch(o=c.charCodeAt(++I)){case 92:case 34:case 47:case 98:case 116:case 110:case 102:case 114:t+=Z[o],I++;break;case 117:for(e=++I,r=I+4;r>I;I++)o=c.charCodeAt(I),o>=48&&57>=o||o>=97&&102>=o||o>=65&&70>=o||H();t+=$("0x"+c.slice(e,I));break;default:H()}else{if(34==o)break;for(o=c.charCodeAt(I),e=I;o>=32&&92!=o&&34!=o;)o=c.charCodeAt(++I);t+=c.slice(e,I)}if(34==c.charCodeAt(I))return I++,t;H();default:if(e=I,45==o&&(n=!0,o=c.charCodeAt(++I)),o>=48&&57>=o){for(48==o&&(o=c.charCodeAt(I+1),o>=48&&57>=o)&&H(),n=!1;i>I&&(o=c.charCodeAt(I),o>=48&&57>=o);I++);if(46==c.charCodeAt(I)){for(r=++I;i>r&&(o=c.charCodeAt(r),o>=48&&57>=o);r++);r==I&&H(),I=r}if(o=c.charCodeAt(I),101==o||69==o){for(o=c.charCodeAt(++I),43!=o&&45!=o||I++,r=I;i>r&&(o=c.charCodeAt(r),o>=48&&57>=o);r++);r==I&&H(),I=r}return+c.slice(e,I)}if(n&&H(),"true"==c.slice(I,I+4))return I+=4,!0;if("false"==c.slice(I,I+5))return I+=5,!1;if("null"==c.slice(I,I+4))return I+=4,null;H()}return"$"},B=function(t){var e,r;if("$"==t&&H(),"string"==typeof t){if("@"==(A?t.charAt(0):t[0]))return t.slice(1);if("["==t){for(e=[];t=R(),"]"!=t;r||(r=!0))r&&(","==t?(t=R(),"]"==t&&H()):H()),","==t&&H(),e.push(B(t));return e}if("{"==t){for(e={};t=R(),"}"!=t;r||(r=!0))r&&(","==t?(t=R(),"}"==t&&H()):H()),","!=t&&"string"==typeof t&&"@"==(A?t.charAt(0):t[0])&&":"==R()||H(),e[t.slice(1)]=B(R());return e}H()}return t},G=function(t,e,r){var n=L(t,e,r);n===h?delete t[e]:t[e]=n},L=function(t,e,r){var n,o=t[e];if("object"==typeof o&&o)if(v.call(o)==C)for(n=o.length;n--;)G(o,n,r);else g(o,function(t){G(o,t,r)});return r.call(t,e,o)};e.parse=function(t,e){var r,n;return I=0,M=""+t,r=B(R()),"$"!=R()&&H(),I=M=null,e&&v.call(e)==O?L((n={},n[""]=r,n),"",e):r}}}return e.runInContext=i,e}var l=r(11),u="object"==typeof o&&o;if(!u||u.global!==u&&u.window!==u||(c=u),"object"!=typeof e||!e||e.nodeType||l){var a=c.JSON,f=i(c,c.JSON3={noConflict:function(){return c.JSON=a,f}});c.JSON={parse:f.parse,stringify:f.stringify}}else i(c,e);l&&(n=function(){return f}.call(e,r,e,t),!(void 0!==n&&(t.exports=n)))}(this)}).call(e,function(){return this}())},function(t,e){(function(e){t.exports=e}).call(e,{})}]); \ No newline at end of file diff --git a/lib/client-browserstack-util.js b/lib/client-browserstack-util.js new file mode 100644 index 0000000..3b8873d --- /dev/null +++ b/lib/client-browserstack-util.js @@ -0,0 +1,22 @@ +(function (global) { + 'use strict'; + + global.BrowserStack = global.BrowserStack || {}; + global.BrowserStack.util = { + inspect: require('util-inspect'), + toArray: function toArray(list, index) { + var array = []; + index = index || 0; + + for (var i = index || 0; i < list.length; i++) { + array[i - index] = list[i]; + } + + return array; + } + }; + + if (global.JSON3 && typeof global.JSON3.noConflict === 'function') { + global.JSON3.noConflict(); + } +})(window || {}); diff --git a/package.json b/package.json index 7b92bde..df4353e 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,6 @@ "test-unit": "node_modules/.bin/mocha tests/unit", "test-ci": "npm run lint && npm run test-unit && TEST_MODE=all tests/external-tests.js", "test": "npm run lint && npm run test-unit && TEST_MODE=required tests/external-tests.js", - "update-util": "node_modules/.bin/browserify lib/client-browserstack-util.js | node_modules/.bin/uglifyjs > lib/_patch/browserstack-util.js" + "update-util": "webpack" } } diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..18df6ec --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,17 @@ +var webpack = require("webpack"); + +module.exports = { + entry: "./lib/client-browserstack-util.js", + output: { + path: "./lib/_patch", + filename: "browserstack-util.js" + }, + plugins: [ + new webpack.optimize.UglifyJsPlugin({ + minimize: true, + comments: false, + compress: { warnings: false }, + sourceMap: false + }) + ] +}; From 51618fc87a777777a54c71880c866aed334c5315 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Thu, 17 Mar 2016 12:07:01 +0530 Subject: [PATCH 071/162] Bumped version to 0.4.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index df4353e..1c4571f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.4.2", + "version": "0.4.3", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 75c896bec330402d613610f35c75fcd0315f63ce Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Wed, 11 May 2016 21:31:54 +0530 Subject: [PATCH 072/162] Ignore terminate worker minimum life error --- lib/server.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index e2ff8d9..180b442 100644 --- a/lib/server.js +++ b/lib/server.js @@ -183,7 +183,9 @@ exports.Server = function Server(bsClient, workers) { }); } else { - bsClient.terminateWorker(worker.id, callback); + bsClient.terminateWorker(worker.id, function () { + callback(false); + }); } } From d14d04801fabb34bbd6a518fb6638b73356cdac5 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Thu, 12 May 2016 13:38:03 +0530 Subject: [PATCH 073/162] Bumped version to 0.4.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c4571f..abc76b1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.4.3", + "version": "0.4.4", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 01fe42769f358a92d980223578755e096e151a90 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 19:06:19 +0100 Subject: [PATCH 074/162] Scripts: add js-reporters distribution. --- lib/_patch/js-reporters.js | 1369 ++++++++++++++++++++++++++++++++++++ lib/server.js | 3 +- 2 files changed, 1371 insertions(+), 1 deletion(-) create mode 100644 lib/_patch/js-reporters.js diff --git a/lib/_patch/js-reporters.js b/lib/_patch/js-reporters.js new file mode 100644 index 0000000..9860b75 --- /dev/null +++ b/lib/_patch/js-reporters.js @@ -0,0 +1,1369 @@ +/** + * JsReporters 1.0.0 + * https://github.com/js-reporters + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: Wed Jul 06 2016 + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.JsReporters = factory()); +}(this, function () { 'use strict'; + + function __commonjs(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } + + + var babelHelpers = {}; + babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; + }; + + babelHelpers.classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + }; + + babelHelpers.createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + + babelHelpers.inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + }; + + babelHelpers.possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; + }; + + babelHelpers.slicedToArray = function () { + function sliceIterator(arr, i) { + var _arr = []; + var _n = true; + var _d = false; + var _e = undefined; + + try { + for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value); + + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"]) _i["return"](); + } finally { + if (_d) throw _e; + } + } + + return _arr; + } + + return function (arr, i) { + if (Array.isArray(arr)) { + return arr; + } else if (Symbol.iterator in Object(arr)) { + return sliceIterator(arr, i); + } else { + throw new TypeError("Invalid attempt to destructure non-iterable instance"); + } + }; + }(); + + babelHelpers; + + var events = __commonjs(function (module) { + // Copyright Joyent, Inc. and other Node contributors. + // + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to permit + // persons to whom the Software is furnished to do so, subject to the + // following conditions: + // + // The above copyright notice and this permission notice shall be included + // in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. + + function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; + } + module.exports = EventEmitter; + + // Backwards-compat with node 0.10.x + EventEmitter.EventEmitter = EventEmitter; + + EventEmitter.prototype._events = undefined; + EventEmitter.prototype._maxListeners = undefined; + + // By default EventEmitters will print a warning if more than 10 listeners are + // added to it. This is a useful default which helps finding memory leaks. + EventEmitter.defaultMaxListeners = 10; + + // Obviously not all Emitters should be limited to 10. This function allows + // that to be increased. Set to zero for unlimited. + EventEmitter.prototype.setMaxListeners = function (n) { + if (!isNumber(n) || n < 0 || isNaN(n)) throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; + }; + + EventEmitter.prototype.emit = function (type) { + var er, handler, len, args, i, listeners; + + if (!this._events) this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || isObject(this._events.error) && !this._events.error.length) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } + throw TypeError('Uncaught, unspecified "error" event.'); + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + args = Array.prototype.slice.call(arguments, 1); + handler.apply(this, args); + } + } else if (isObject(handler)) { + args = Array.prototype.slice.call(arguments, 1); + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) { + listeners[i].apply(this, args); + } + } + + return true; + }; + + EventEmitter.prototype.addListener = function (type, listener) { + var m; + + if (!isFunction(listener)) throw TypeError('listener must be a function'); + + if (!this._events) this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) this.emit('newListener', type, isFunction(listener.listener) ? listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener;else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener);else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; + }; + + EventEmitter.prototype.on = EventEmitter.prototype.addListener; + + EventEmitter.prototype.once = function (type, listener) { + if (!isFunction(listener)) throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; + }; + + // emits a 'removeListener' event iff the listener was removed + EventEmitter.prototype.removeListener = function (type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || isFunction(list.listener) && list.listener === listener) { + delete this._events[type]; + if (this._events.removeListener) this.emit('removeListener', type, listener); + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || list[i].listener && list[i].listener === listener) { + position = i; + break; + } + } + + if (position < 0) return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) this.emit('removeListener', type, listener); + } + + return this; + }; + + EventEmitter.prototype.removeAllListeners = function (type) { + var key, listeners; + + if (!this._events) return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) this._events = {};else if (this._events[type]) delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + while (listeners.length) { + this.removeListener(type, listeners[listeners.length - 1]); + } + } + delete this._events[type]; + + return this; + }; + + EventEmitter.prototype.listeners = function (type) { + var ret; + if (!this._events || !this._events[type]) ret = [];else if (isFunction(this._events[type])) ret = [this._events[type]];else ret = this._events[type].slice(); + return ret; + }; + + EventEmitter.prototype.listenerCount = function (type) { + if (this._events) { + var evlistener = this._events[type]; + + if (isFunction(evlistener)) return 1;else if (evlistener) return evlistener.length; + } + return 0; + }; + + EventEmitter.listenerCount = function (emitter, type) { + return emitter.listenerCount(type); + }; + + function isFunction(arg) { + return typeof arg === 'function'; + } + + function isNumber(arg) { + return typeof arg === 'number'; + } + + function isObject(arg) { + return (typeof arg === 'undefined' ? 'undefined' : babelHelpers.typeof(arg)) === 'object' && arg !== null; + } + + function isUndefined(arg) { + return arg === void 0; + } + }); + + var EventEmitter = events && (typeof events === 'undefined' ? 'undefined' : babelHelpers.typeof(events)) === 'object' && 'default' in events ? events['default'] : events; + + var Test = function Test(testName, suiteName, status, runtime, errors) { + babelHelpers.classCallCheck(this, Test); + + this.testName = testName; + this.suiteName = suiteName; + this.status = status; + this.runtime = runtime; + this.errors = errors; + }; + + var Suite = function () { + + /** + * + * @param name + * @param childSuites + * @param tests: array containing tests belonging to the suite but not to a child suite + */ + + function Suite(name, childSuites, tests) { + babelHelpers.classCallCheck(this, Suite); + + this.name = name; + this.childSuites = childSuites; + this.tests = tests; + } + + babelHelpers.createClass(Suite, [{ + key: 'getAllTests', + value: function getAllTests() { + var childSuiteTests = this.childSuites.map(function (suite) { + return suite.getAllTests(); + }).reduce(function (allTests, a) { + return allTests.concat(a); + }, []); + + return this.tests.concat(childSuiteTests); + } + }, { + key: 'runtime', + get: function get() { + var status = this.status; + + if (status === 'skipped' || status === undefined) { + return undefined; + } + + var runtime = this.getAllTests().map(function (test) { + return test.status === 'skipped' ? 0 : test.runtime; + }).reduce(function (sum, testRuntime) { + return sum + testRuntime; + }, 0); + + return runtime; + } + }, { + key: 'status', + get: function get() { + var passed = 0; + var failed = 0; + var skipped = 0; + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = this.getAllTests()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var test = _step.value; + + // If a suite contains a test whose status is still undefined, + // there is no final status for the suite as well. + if (test.status === undefined) { + return undefined; + } else if (test.status === 'passed') { + passed++; + } else if (test.status === 'skipped') { + skipped++; + } else { + failed++; + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + if (failed > 0) { + return 'failed'; + } else if (skipped > 0 && passed === 0) { + return 'skipped'; + } else { + return 'passed'; + } + } + }]); + return Suite; + }(); + + Object.defineProperties(Suite.prototype, { + toJSON: { + value: function value() { + var ret = {}; + for (var x in this) { + ret[x] = this[x]; + } + return ret; + } + }, + runtime: { + enumerable: true + }, + status: { + enumerable: true + } + }); + + var QUnitAdapter = function (_EventEmitter) { + babelHelpers.inherits(QUnitAdapter, _EventEmitter); + + function QUnitAdapter(QUnit) { + babelHelpers.classCallCheck(this, QUnitAdapter); + + var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(QUnitAdapter).call(this)); + + _this.QUnit = QUnit; + _this.tests = {}; + + QUnit.begin(_this.onBegin.bind(_this)); + QUnit.testStart(_this.onTestStart.bind(_this)); + QUnit.log(_this.onLog.bind(_this)); + QUnit.testDone(_this.onTestDone.bind(_this)); + QUnit.done(_this.onDone.bind(_this)); + return _this; + } + + babelHelpers.createClass(QUnitAdapter, [{ + key: 'convertModule', + value: function convertModule(qunitModule) { + var _this2 = this; + + return new Suite(qunitModule.name, [], qunitModule.tests.map(function (qunitTest) { + var test = new Test(qunitTest.name, qunitModule.name.replace(/> /g, '')); + + _this2.tests[qunitTest.testId] = test; + + return test; + })); + } + }, { + key: 'saveTestDetails', + value: function saveTestDetails(qunitTest) { + var test = this.tests[qunitTest.testId]; + + test.errors = this.errors; + + if (qunitTest.failed > 0) { + test.status = 'failed'; + } else if (qunitTest.skipped) { + test.status = 'skipped'; + } else { + test.status = 'passed'; + } + + // Workaround for QUnit skipped tests runtime which is a Number. + if (test.status !== 'skipped') { + test.runtime = qunitTest.runtime; + } else { + test.runtime = undefined; + } + } + }, { + key: 'createGlobalSuite', + value: function createGlobalSuite() { + var topLevelSuites = []; + var globalSuite; + var modules; + + // Access QUnit internals to get all suites and tests, working around + // missing event data. + + // Create the global suite first. + if (this.QUnit.config.modules.length > 0 && this.QUnit.config.modules[0].name === '') { + globalSuite = this.convertModule(this.QUnit.config.modules[0]); + globalSuite.name = undefined; + + // The suiteName of global tests must be undefined. + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = globalSuite.tests[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var test = _step.value; + + test.suiteName = undefined; + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + modules = this.QUnit.config.modules.slice(1); + } else { + globalSuite = new Suite(undefined, [], []); + modules = this.QUnit.config.modules; + } + + // Build a list with all suites. + var suites = modules.map(this.convertModule.bind(this)); + + // Iterate through the whole suites and check if they have composed names, + // like "suiteName1 > suiteName2 > ... > suiteNameN". + // + // If a suite has a composed name, its name will be the last in the sequence + // and its parent name will be the one right before it. Search the parent + // suite after its name and then add the suite with the composed name to the + // childSuites. + // + // If a suite does not have a composed name, add it to the topLevelSuites, + // this means that this suite is the direct child of the global suite. + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = suites[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var suite = _step2.value; + + var indexEnd = suite.name.lastIndexOf(' > '); + + if (indexEnd !== -1) { + // Find the ' > ' characters that appears before the parent name. + var indexStart = suite.name.substring(0, indexEnd).lastIndexOf(' > '); + // If it is -1, the parent suite name starts at 0, else escape + // this characters ' > '. + indexStart = indexStart === -1 ? 0 : indexStart + 3; + + var parentSuiteName = suite.name.substring(indexStart, indexEnd); + + // Keep only the name of the suite itself. + suite.name = suite.name.substring(indexEnd + 3); + + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = suites[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var parentSuite = _step3.value; + + if (parentSuite.name === parentSuiteName) { + parentSuite.childSuites.push(suite); + } + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + } else { + topLevelSuites.push(suite); + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + globalSuite.childSuites = topLevelSuites; + + return globalSuite; + } + }, { + key: 'createSuiteStart', + value: function createSuiteStart(suite) { + return new Suite(suite.name, suite.childSuites.map(this.createSuiteStart.bind(this)), suite.tests.map(this.createTestStart.bind(this))); + } + }, { + key: 'createSuiteEnd', + value: function createSuiteEnd(suite) { + return new Suite(suite.name, suite.childSuites.map(this.createSuiteEnd.bind(this)), suite.tests.map(this.createTestEnd.bind(this))); + } + }, { + key: 'createTestStart', + value: function createTestStart(test) { + return new Test(test.testName, test.suiteName); + } + }, { + key: 'createTestEnd', + value: function createTestEnd(test) { + return new Test(test.testName, test.suiteName, test.status, test.runtime, test.errors); + } + }, { + key: 'emitData', + value: function emitData(suite) { + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + for (var _iterator4 = suite.tests[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var test = _step4.value; + + this.emit('testStart', this.createTestStart(test)); + this.emit('testEnd', this.createTestEnd(test)); + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + + var _iteratorNormalCompletion5 = true; + var _didIteratorError5 = false; + var _iteratorError5 = undefined; + + try { + for (var _iterator5 = suite.childSuites[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { + var _suite = _step5.value; + + this.emit('suiteStart', this.createSuiteStart(_suite)); + this.emitData(_suite); + this.emit('suiteEnd', this.createSuiteEnd(_suite)); + } + } catch (err) { + _didIteratorError5 = true; + _iteratorError5 = err; + } finally { + try { + if (!_iteratorNormalCompletion5 && _iterator5.return) { + _iterator5.return(); + } + } finally { + if (_didIteratorError5) { + throw _iteratorError5; + } + } + } + } + }, { + key: 'onBegin', + value: function onBegin() { + this.globalSuite = this.createGlobalSuite(); + } + }, { + key: 'onTestStart', + value: function onTestStart(details) { + this.errors = []; + } + }, { + key: 'onLog', + value: function onLog(details) { + if (!details.result) { + this.errors.push(details); + } + } + }, { + key: 'onTestDone', + value: function onTestDone(details) { + this.saveTestDetails(details); + } + }, { + key: 'onDone', + value: function onDone() { + this.emit('runStart', this.createSuiteStart(this.globalSuite)); + this.emitData(this.globalSuite); + this.emit('runEnd', this.createSuiteEnd(this.globalSuite)); + } + }]); + return QUnitAdapter; + }(EventEmitter); + + /** + * Limitations: + * - Errors in afterAll are ignored. + */ + + var JasmineAdapter = function (_EventEmitter) { + babelHelpers.inherits(JasmineAdapter, _EventEmitter); + + function JasmineAdapter(jasmine) { + babelHelpers.classCallCheck(this, JasmineAdapter); + + var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(JasmineAdapter).call(this)); + + _this.jasmine = jasmine; + jasmine.addReporter({ + jasmineStarted: _this.onJasmineStarted.bind(_this), + specDone: _this.onSpecDone.bind(_this), + specStarted: _this.onSpecStarted.bind(_this), + suiteStarted: _this.onSuiteStarted.bind(_this), + suiteDone: _this.onSuiteDone.bind(_this), + jasmineDone: _this.onJasmineDone.bind(_this) + }); + + _this.suites = {}; + _this.tests = {}; + return _this; + } + + babelHelpers.createClass(JasmineAdapter, [{ + key: 'createSuiteStart', + value: function createSuiteStart(suite) { + return new Suite(suite.name, suite.childSuites.map(this.createSuiteStart.bind(this)), suite.tests.map(this.createTestStart.bind(this))); + } + }, { + key: 'createSuiteEnd', + value: function createSuiteEnd(suite) { + return new Suite(suite.name, suite.childSuites.map(this.createSuiteEnd.bind(this)), suite.tests.map(this.createTestEnd.bind(this))); + } + }, { + key: 'createTestStart', + value: function createTestStart(test) { + return new Test(test.testName, test.suiteName); + } + }, { + key: 'createTestEnd', + value: function createTestEnd(test) { + return new Test(test.testName, test.suiteName, test.status, test.runtime, test.errors); + } + }, { + key: 'saveTestDetails', + value: function saveTestDetails(jasmineSpec) { + var test = this.tests[jasmineSpec.id]; + + test.errors = jasmineSpec.failedExpectations; + + if (jasmineSpec.status === 'pending') { + test.status = 'skipped'; + } else { + test.status = jasmineSpec.status; + test.runtime = new Date() - this.startTime; + } + } + }, { + key: 'isJasmineGlobalSuite', + value: function isJasmineGlobalSuite(suite) { + return suite.description === 'Jasmine__TopLevel__Suite'; + } + + /** + * Jasmine provides details about childSuites and tests only in the structure + * returned by "this.jasmine.topSuite()". + * + * This function creates the global suite for the runStart event, as also + * saves the created suites and tests compliant with the CRI standard in an + * object using as key their unique ids provided by Jasmine. + */ + + }, { + key: 'createGlobalSuite', + value: function createGlobalSuite(jasmineSuite) { + var childSuites = []; + var tests = []; + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = jasmineSuite.children[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var child = _step.value; + + if (child.id.indexOf('suite') === 0) { + childSuites.push(this.createGlobalSuite(child)); + } else { + var suiteName = void 0; + var test = void 0; + + // Jasmine full description is of form "suite1 suite2 ... suiteN test", + // for the "suiteName" property we need to remove test name. + if (!this.isJasmineGlobalSuite(jasmineSuite)) { + suiteName = child.result.fullName.substring(0, child.result.fullName.indexOf(child.description) - 1); + } + + test = new Test(child.description, suiteName); + + tests.push(test); + this.tests[child.id] = test; + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + var name = this.isJasmineGlobalSuite(jasmineSuite) ? undefined : jasmineSuite.description; + var suite = new Suite(name, childSuites, tests); + + this.suites[jasmineSuite.id] = suite; + + return suite; + } + }, { + key: 'onJasmineStarted', + value: function onJasmineStarted() { + this.globalSuite = this.createGlobalSuite(this.jasmine.topSuite()); + this.emit('runStart', this.createSuiteStart(this.globalSuite)); + } + }, { + key: 'onSpecStarted', + value: function onSpecStarted(details) { + this.startTime = new Date(); + this.emit('testStart', this.createTestStart(this.tests[details.id])); + } + }, { + key: 'onSpecDone', + value: function onSpecDone(details) { + this.saveTestDetails(details); + this.emit('testEnd', this.createTestEnd(this.tests[details.id])); + } + }, { + key: 'onSuiteStarted', + value: function onSuiteStarted(details) { + this.emit('suiteStart', this.createSuiteStart(this.suites[details.id])); + } + }, { + key: 'onSuiteDone', + value: function onSuiteDone(details) { + this.emit('suiteEnd', this.createSuiteEnd(this.suites[details.id])); + } + }, { + key: 'onJasmineDone', + value: function onJasmineDone() { + this.emit('runEnd', this.createSuiteEnd(this.globalSuite)); + } + }]); + return JasmineAdapter; + }(EventEmitter); + + var MochaAdapter = function (_EventEmitter) { + babelHelpers.inherits(MochaAdapter, _EventEmitter); + + function MochaAdapter(mocha) { + babelHelpers.classCallCheck(this, MochaAdapter); + + var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(MochaAdapter).call(this)); + + _this.mocha = mocha; + + mocha.reporter(function (runner) { + _this.runner = runner; + + runner.on('start', _this.onStart.bind(_this)); + runner.on('suite', _this.onSuite.bind(_this)); + runner.on('test', _this.onTest.bind(_this)); + runner.on('pending', _this.onPending.bind(_this)); + runner.on('fail', _this.onFail.bind(_this)); + runner.on('test end', _this.onTestEnd.bind(_this)); + runner.on('suite end', _this.onSuiteEnd.bind(_this)); + runner.on('end', _this.onEnd.bind(_this)); + }); + return _this; + } + + babelHelpers.createClass(MochaAdapter, [{ + key: 'convertSuite', + value: function convertSuite(mochaSuite) { + return new Suite(mochaSuite.title, mochaSuite.suites.map(this.convertSuite.bind(this)), mochaSuite.tests.map(this.convertTest.bind(this))); + } + }, { + key: 'convertTest', + value: function convertTest(mochaTest) { + var suiteName; + + if (!mochaTest.parent.root) { + suiteName = this.buildSuiteName(mochaTest.parent); + } + + // If the test has the errors attached a "test end" must be emitted, else + // a "test start". + if (mochaTest.errors !== undefined) { + var status = mochaTest.state === undefined ? 'skipped' : mochaTest.state; + + // Test end. + return new Test(mochaTest.title, suiteName, status, mochaTest.duration, mochaTest.errors); + } + + // Test start. + return new Test(mochaTest.title, suiteName); + } + + /** + * Builds a concatenated name from nested suites. + */ + + }, { + key: 'buildSuiteName', + value: function buildSuiteName(mochaSuite) { + var suiteName = mochaSuite.title; + var parent = mochaSuite.parent; + + while (!parent.root) { + suiteName = parent.title + ' ' + suiteName; + parent = parent.parent; + } + + return suiteName; + } + }, { + key: 'onStart', + value: function onStart() { + var globalSuiteStart = this.convertSuite(this.runner.suite); + globalSuiteStart.name = undefined; + + this.emit('runStart', globalSuiteStart); + } + }, { + key: 'onSuite', + value: function onSuite(mochaSuite) { + if (!mochaSuite.root) { + this.emit('suiteStart', this.convertSuite(mochaSuite)); + } + } + }, { + key: 'onTest', + value: function onTest(mochaTest) { + this.errors = []; + + this.emit('testStart', this.convertTest(mochaTest)); + } + + /** + * Emits the start of pending tests, because Mocha does not emit skipped tests + * on its "test" event. + */ + + }, { + key: 'onPending', + value: function onPending(mochaTest) { + this.emit('testStart', this.convertTest(mochaTest)); + } + }, { + key: 'onFail', + value: function onFail(test, error) { + this.errors.push(error); + } + }, { + key: 'onTestEnd', + value: function onTestEnd(mochaTest) { + // Save the errors on Mocha's test object, because when the suite that + // contains this test is emitted on the "suiteEnd" event, it should contain + // also this test with all its details (errors, status, runtime). Runtime + // and status are already attached to the test, but the errors don't. + mochaTest.errors = this.errors; + + this.emit('testEnd', this.convertTest(mochaTest)); + } + }, { + key: 'onSuiteEnd', + value: function onSuiteEnd(mochaSuite) { + if (!mochaSuite.root) { + this.emit('suiteEnd', this.convertSuite(mochaSuite)); + } + } + }, { + key: 'onEnd', + value: function onEnd() { + var globalSuiteEnd = this.convertSuite(this.runner.suite); + globalSuiteEnd.name = undefined; + + this.emit('runEnd', globalSuiteEnd); + } + }]); + return MochaAdapter; + }(EventEmitter); + + var TapReporter = function () { + function TapReporter(runner) { + babelHelpers.classCallCheck(this, TapReporter); + + this.testCount = 0; + + runner.on('runStart', this.onRunStart.bind(this)); + runner.on('testEnd', this.onTestEnd.bind(this)); + runner.on('runEnd', this.onRunEnd.bind(this)); + } + + babelHelpers.createClass(TapReporter, [{ + key: 'onRunStart', + value: function onRunStart(globalSuite) { + console.log('TAP version 13'); + } + }, { + key: 'onTestEnd', + value: function onTestEnd(test) { + this.testCount = this.testCount + 1; + + // TODO maybe switch to test.fullName + // @see https://github.com/js-reporters/js-reporters/issues/65 + if (test.status === 'passed') { + console.log('ok ' + this.testCount + ' ' + test.testName); + } else if (test.status === 'skipped') { + console.log('ok ' + this.testCount + ' ' + test.testName + ' # SKIP'); + } else { + console.log('not ok ' + this.testCount + ' ' + test.testName); + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = test.errors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var error = _step.value; + + console.log(' ---'); + console.log(' message: "' + error.toString() + '"'); + console.log(' severity: failed'); + console.log(' ...'); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + } + } + }, { + key: 'onRunEnd', + value: function onRunEnd(globalSuite) { + console.log('1..' + this.testCount); + } + }], [{ + key: 'init', + value: function init(runner) { + return new TapReporter(runner); + } + }]); + return TapReporter; + }(); + + // TODO: finish grouping once suiteStart is implemented + var hasGrouping = 'group' in console && 'groupEnd' in console; + + var ConsoleReporter = function () { + function ConsoleReporter(runner) { + babelHelpers.classCallCheck(this, ConsoleReporter); + + runner.on('runStart', this.onRunStart); + runner.on('suiteStart', this.onSuiteStart); + runner.on('testStart', this.onTestStart); + runner.on('testEnd', this.onTestEnd); + runner.on('suiteEnd', this.onSuiteEnd); + runner.on('runEnd', this.onRunEnd); + } + + babelHelpers.createClass(ConsoleReporter, [{ + key: 'onRunStart', + value: function onRunStart(suite) { + console.log('runStart', suite); + } + }, { + key: 'onSuiteStart', + value: function onSuiteStart(suite) { + if (hasGrouping) { + console.group(suite.name); + } + console.log('suiteStart', suite); + } + }, { + key: 'onTestStart', + value: function onTestStart(test) { + console.log('testStart', test); + } + }, { + key: 'onTestEnd', + value: function onTestEnd(test) { + console.log('testEnd', test); + } + }, { + key: 'onSuiteEnd', + value: function onSuiteEnd(suite) { + console.log('suiteEnd', suite); + if (hasGrouping) { + console.groupEnd(); + } + } + }, { + key: 'onRunEnd', + value: function onRunEnd(globalSuite) { + console.log('runEnd', globalSuite); + } + }], [{ + key: 'init', + value: function init(runner) { + return new ConsoleReporter(runner); + } + }]); + return ConsoleReporter; + }(); + + /* + The TestReporter verifies that a test runner outputs the right data in the right order. + To do so, it compares the actual output with the provided reference data. + The result is given in the ok attribute. + */ + + var TestReporter = function () { + + /** + * @param runner: standardized test runner (or adapter) + * @param referenceData: An array of all expected (eventName, eventData) tuples in the right order + */ + + function TestReporter(runner, referenceData) { + babelHelpers.classCallCheck(this, TestReporter); + + this.referenceData = referenceData.slice(); + this.error = false; + runner.on('runStart', this.onEvent.bind(this, 'runStart')); + runner.on('suiteStart', this.onEvent.bind(this, 'suiteStart')); + runner.on('testStart', this.onEvent.bind(this, 'testStart')); + runner.on('testEnd', this.onEvent.bind(this, 'testEnd')); + runner.on('suiteEnd', this.onEvent.bind(this, 'suiteEnd')); + runner.on('runEnd', this.onEvent.bind(this, 'runEnd')); + } + + /** + * Gets called on each event emitted by the runner. Checks if the actual event matches the expected event. + */ + + + babelHelpers.createClass(TestReporter, [{ + key: 'onEvent', + value: function onEvent(eventName, eventData) { + var _referenceData$shift = this.referenceData.shift(); + + var _referenceData$shift2 = babelHelpers.slicedToArray(_referenceData$shift, 2); + + var expectedEventName = _referenceData$shift2[0]; + var expectedEventData = _referenceData$shift2[1]; + + + if (eventName !== expectedEventName || !this.equal(eventData, expectedEventData)) { + this.error = true; + console.error('expected:', expectedEventName, expectedEventData, '\r\n', 'actual:', eventName, eventData); + } + } + }, { + key: 'equal', + + + /** + * Helper function to compare + * - two Test objects + * - two Suite objects + * - two arrays of Test or Suite objects + * The equality check is not completely strict, e.g. the runtime of a Test does not have to be equal. + * @returns {boolean}: true if both objects are equal, false otherwise + */ + value: function equal(actual, expected) { + if (expected instanceof Suite) { + if (actual.name !== expected.name) { + return false; + } + if (!this.equal(actual.childSuites, expected.childSuites)) { + return false; + } + + if (!this.equal(actual.tests, expected.tests)) { + return false; + } + } else if (expected instanceof Test) { + var _arr = ['testName', 'suiteName', 'status']; + + for (var _i = 0; _i < _arr.length; _i++) { + var property = _arr[_i]; + if (actual[property] !== expected[property]) { + return false; + } + } + if (typeof actual.runtime !== 'number' && actual.runtime !== undefined) { + return false; + } + + if (!(actual.errors === undefined && expected.errors === undefined) && actual.errors.length !== expected.errors.length) { + return false; + } + } else if (Array.isArray(expected)) { + if (actual.length !== expected.length) { + return false; + } + + for (var i = 0; i < expected.length; i++) { + if (!this.equal(actual[i], expected[i])) { + return false; + } + } + } else { + return false; + } + return true; + } + }, { + key: 'ok', + get: function get() { + return !this.error && this.referenceData.length === 0; + } + }]); + return TestReporter; + }(); + + var index = { + QUnitAdapter: QUnitAdapter, + JasmineAdapter: JasmineAdapter, + MochaAdapter: MochaAdapter, + TapReporter: TapReporter, + ConsoleReporter: ConsoleReporter, + TestReporter: TestReporter, + Test: Test, + Suite: Suite + }; + + return index; + +})); \ No newline at end of file diff --git a/lib/server.js b/lib/server.js index 180b442..53a7e63 100644 --- a/lib/server.js +++ b/lib/server.js @@ -35,7 +35,8 @@ exports.Server = function Server(bsClient, workers) { var scripts = [ 'json2.js', 'browserstack.js', - 'browserstack-util.js' + 'browserstack-util.js', + 'js-reporters.js' ]; var framework_scripts = { From f9326220a13f50fecfb6dddde5182775ea459bf8 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 19:09:34 +0100 Subject: [PATCH 075/162] QUnitReporter: update QUnit reporter based on the js-reporter adapter. --- lib/_patch/my-mocha.js | 34 ++++++++++++++++++++ lib/_patch/qunit-plugin.js | 63 ++++++++++++-------------------------- 2 files changed, 54 insertions(+), 43 deletions(-) create mode 100644 lib/_patch/my-mocha.js diff --git a/lib/_patch/my-mocha.js b/lib/_patch/my-mocha.js new file mode 100644 index 0000000..51e6ce3 --- /dev/null +++ b/lib/_patch/my-mocha.js @@ -0,0 +1,34 @@ +(function() { + var runner = new JsReporters.MochaAdapter(mocha); + var errors = [] + var passed = 0, failed = 0, total = 0; + var startTime; + + runner.on('runStart', function() { + startTime = new Date() + }); + + runner.on('testEnd', function(test) { + total = total + 1; + + passed = passed + (test.status === 'passed' ? 1 : 0); + failed = failed + (test.status === 'failed' ? 1 : 0); + + test.errors.forEach(function(error) { + errors.push(error) + }); + }); + + runner.on('runEnd', function() { + var results = {} + + results.runtime = new Date() - startTime; + results.total = total; + results.passed = passed; + results.failed = failed; + results.tracebacks = errors; + results.url = window.location.pathname; + + BrowserStack.post("/_report", results, function() {}); + }); +})(); diff --git a/lib/_patch/qunit-plugin.js b/lib/_patch/qunit-plugin.js index 8ef3f10..663a139 100644 --- a/lib/_patch/qunit-plugin.js +++ b/lib/_patch/qunit-plugin.js @@ -7,56 +7,33 @@ factory(QUnit); } }(function(QUnit) { - var failedAssertions = []; - var options, - currentModule, - currentTest, - setTimeoutVariable; - var pendingTest = {}; + var runner = new JsReporters.QUnitAdapter(QUnit); + var tracebacks = []; + var total = 0, + passed = 0, + failed = 0; - var testTimeout = function() { - var error = { - testName: currentTest, - message: "Stuck on this test for 60 sec." - }; + runner.on('testEnd', function(test) { + total = total + 1 - BrowserStack.post('/_progress', { - tracebacks: [error] - }, function(){}); - }; + passed = passed + (test.status === 'passed' ? 1 : 0); + failed = failed + (test.status === 'failed' ? 1 : 0); - QUnit.testDone(function(details) { - var ct = details.module + " - " + details.name; - clearTimeout(pendingTest[ct]); + test.errors.forEach(function(error) { + tracebacks.push(error) + }); }); - QUnit.testStart(function(details) { - currentTest = details.module + " - " + details.name; - pendingTest[currentTest] = setTimeout(function() { - testTimeout(currentTest); - }, 60000); - }); - - QUnit.log(function(details) { - if (details.result) { - return; - } - - var error = { - actual: details.actual, - expected: details.expected, - message: details.message, - source: details.source, - testName:( details.module + ": " + details.name) - }; + runner.on('runEnd', function(globalSuite) { + var results = {}; - BrowserStack.post('/_progress', { - tracebacks: [error] - }, function(){}); - }); - - QUnit.done(function(results) { + results.runtime = globalSuite.runtime; + results.total = total; + results.passed = passed; + results.failed = failed; + results.tracebacks = tracebacks; results.url = window.location.pathname; + BrowserStack.post("/_report", results, function(){}); }); })); From 8c88f26b7a996ebccb20d26b0ffb1a873ea1e8c4 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 19:13:02 +0100 Subject: [PATCH 076/162] Testing: update QUnit expected results from assertions to tests. --- tests/external-tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/external-tests.js b/tests/external-tests.js index 053803f..cb2fc72 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -31,8 +31,8 @@ var repositories = [ 'test/index.html' ], expected_results: { - tests: 534, - passed: 534, + tests: 133, + passed: 130, failed: 0 } }, From 4644eed2b7353850ff8164cbf1080c7207eee615 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 19:21:04 +0100 Subject: [PATCH 077/162] JasmineReporter: update Jasmine reporter based on the js-reporters adapter. --- lib/_patch/jasmine2-plugin.js | 50 +++++++++++++++++++---------------- lib/server.js | 1 - 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/lib/_patch/jasmine2-plugin.js b/lib/_patch/jasmine2-plugin.js index a716cdc..05581e7 100644 --- a/lib/_patch/jasmine2-plugin.js +++ b/lib/_patch/jasmine2-plugin.js @@ -1,28 +1,32 @@ (function() { - var checker = setInterval(function() { - if (!jasmine.running) { - var results = {}; - var specs = jsApiReporter.specs(); - results.runtime = jsApiReporter.executionTime(); - results.total = 0; - results.passed = 0; - results.failed = 0; - results.tracebacks = []; + var runner = new JsReporters.JasmineAdapter(jasmine.getEnv()); + var tracebacks = []; + var total = 0, + passed = 0, + failed = 0; - for (var spec in specs) { - if (specs[spec].status === 'passed') { - results.passed++; - } else { - results.tracebacks.push(specs[spec].description); - results.failed++; - } - } + runner.on('testEnd', function(test) { + total = total + 1 - results.total = results.passed + results.failed; - results.url = window.location.pathname; - BrowserStack.post('/_report', results, function(){}); - } - clearInterval(checker); - }, 1000); + passed = passed + (test.status === 'passed' ? 1 : 0); + failed = failed + (test.status === 'failed' ? 1 : 0); + + test.errors.forEach(function(error) { + tracebacks.push(error) + }); + }); + + runner.on('runEnd', function(globalSuite) { + var results = {}; + + results.runtime = globalSuite.runtime; + results.total = total; + results.passed = passed; + results.failed = failed; + results.tracebacks = tracebacks; + results.url = window.location.pathname; + + BrowserStack.post("/_report", results, function() {}); + }); })(); diff --git a/lib/server.js b/lib/server.js index 53a7e63..421863f 100644 --- a/lib/server.js +++ b/lib/server.js @@ -63,7 +63,6 @@ exports.Server = function Server(bsClient, workers) { framework_scripts['jasmine'].forEach(function(script) { patch += '\n'; }); - patch += '\n'; } else if (framework === 'jasmine2') { framework_scripts['jasmine2'].forEach(function(script) { patch += '\n'; From 1dcb2282646ba1208bd21303b20c23ec1e20f4f9 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 19:31:14 +0100 Subject: [PATCH 078/162] Testing: remove QUnit 1.0.0, optional repo. --- tests/external-tests.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/tests/external-tests.js b/tests/external-tests.js index cb2fc72..a516ad4 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -113,29 +113,6 @@ var repositories = [ ]; var repositoriesOptional = [ - { - name: 'qunit', - tag: 'v1.0.0', - url: 'https://github.com/jquery/qunit.git', - test_framework: 'qunit', - browsers: [ - { - 'browser': 'firefox', - 'browser_version': '44.0', - 'os': 'OS X', - 'os_version': 'Snow Leopard' - } - ], - test_path: [ - 'test/index.html', - 'test/logs.html' - ], - expected_results: { - tests: 323, - passed: 323, - failed: 0 - } - }, { name: 'mocha', tag: '1.21.5', From e0c829cc6d4b9cf116f61a98a376aa0be1d67d0b Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 21:35:48 +0100 Subject: [PATCH 079/162] MochaReporter: update Mocha reporter based on js-reporters adapter. --- lib/_patch/mocha-plugin.js | 87 ++++++++++---------------------------- lib/server.js | 1 - 2 files changed, 23 insertions(+), 65 deletions(-) diff --git a/lib/_patch/mocha-plugin.js b/lib/_patch/mocha-plugin.js index a102661..6a26160 100644 --- a/lib/_patch/mocha-plugin.js +++ b/lib/_patch/mocha-plugin.js @@ -1,73 +1,32 @@ (function() { - function stack(err) { - var str = err.stack || err.toString(); + var runner = new JsReporters.MochaAdapter(mocha); + var tracebacks = []; + var total = 0, + passed = 0, + failed = 0; - if (!~str.indexOf(err.message)) { - str = err.message + '\n' + str; - } + runner.on('testEnd', function(test) { + total = total + 1 - if ('[object Error]' == str) { - str = err.message; - } + passed = passed + (test.status === 'passed' ? 1 : 0); + failed = failed + (test.status === 'failed' ? 1 : 0); - if (!err.stack && err.sourceURL && err.line !== undefined) { - str += '\n(' + err.sourceURL + ':' + err.line + ')'; - } - return str.replace(/^/gm, ' '); - } - - function title(test) { - return test.fullTitle().replace(/#/g, ''); - } - - var origReporter = mocha._reporter; - - Mocha.BrowserStack = function(runner, root) { - origReporter.apply(this, arguments); - - var count = 1, - that = this, - failures = 0, - passes = 0, - start = 0, - tracebacks = []; - - runner.on('start', function() { - start = (new Date).getTime(); - }); - - runner.on('test end', function(test) { - count += 1; + test.errors.forEach(function(error) { + tracebacks.push(error) }); + }); - runner.on('pass', function(test) { - passes += 1; - }); - - runner.on('fail', function(test, err) { - failures += 1; - - if (err) { - tracebacks.push(stack(err)); - } - }); - - runner.on('end', function() { - // delay posting results a little to capture "multiple-done" errors - setTimeout(function () { - results = {}; - results.runtime = (new Date).getTime() - start; - results.total = passes + failures; - results.passed = passes; - results.failed = failures; - results.tracebacks = tracebacks; - results.url = window.location.pathname; - BrowserStack.post("/_report", results, function(){}); - }, 1000); - }); - }; + runner.on('runEnd', function(globalSuite) { + var results = {}; - Mocha.BrowserStack.prototype = origReporter.prototype; + // TODO investigate why globalSuite.runtime is not working + results.runtime = 0; + results.total = total; + results.passed = passed; + results.failed = failed; + results.tracebacks = tracebacks; + results.url = window.location.pathname; - return Mocha.BrowserStack; + BrowserStack.post("/_report", results, function() {}); + }); })(); diff --git a/lib/server.js b/lib/server.js index 421863f..df0f41a 100644 --- a/lib/server.js +++ b/lib/server.js @@ -71,7 +71,6 @@ exports.Server = function Server(bsClient, workers) { framework_scripts['mocha'].forEach(function(script) { patch += '\n'; }); - patch += '\n'; } else if (framework === 'qunit') { framework_scripts['qunit'].forEach(function(script) { patch += '\n'; From 3100f07515230e4faaa397046462b9b7a3923be0 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 21:36:20 +0100 Subject: [PATCH 080/162] Testing: update expected results for Mocha 1.21.5. --- tests/external-tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/external-tests.js b/tests/external-tests.js index a516ad4..ee8b95f 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -132,9 +132,9 @@ var repositoriesOptional = [ 'test/browser/opts.html' ], expected_results: { - tests: 84, + tests: 83, passed: 77, - failed: 7 + failed: 6 } } ]; From 0a18c5a143eddbb9c97cbd918ff7734cd4c7fa9c Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 21:37:27 +0100 Subject: [PATCH 081/162] Testing: comment out Mocha 2.4.5. --- tests/external-tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/external-tests.js b/tests/external-tests.js index ee8b95f..cab1695 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -36,7 +36,7 @@ var repositories = [ failed: 0 } }, - { + /*{ name: 'mocha', tag: 'v2.4.5', url: 'https://github.com/mochajs/mocha.git', @@ -59,7 +59,7 @@ var repositories = [ passed: 80, failed: 9 } - }, + },*/ { name: 'spine', tag: 'v.1.6.2', From fb64f9bb7aca8914e4a4dc866da004caabf54ecb Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 21:52:27 +0100 Subject: [PATCH 082/162] JasmineReporter: add back script for Jasmine1. --- lib/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/server.js b/lib/server.js index df0f41a..ac71c80 100644 --- a/lib/server.js +++ b/lib/server.js @@ -63,6 +63,7 @@ exports.Server = function Server(bsClient, workers) { framework_scripts['jasmine'].forEach(function(script) { patch += '\n'; }); + patch += '\n'; } else if (framework === 'jasmine2') { framework_scripts['jasmine2'].forEach(function(script) { patch += '\n'; From e9a8acffc702697bbc013fed3bf7afaccb2ffc9d Mon Sep 17 00:00:00 2001 From: Florentin Date: Thu, 7 Jul 2016 23:23:27 +0100 Subject: [PATCH 083/162] Files: remove inutile file. --- lib/_patch/my-mocha.js | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 lib/_patch/my-mocha.js diff --git a/lib/_patch/my-mocha.js b/lib/_patch/my-mocha.js deleted file mode 100644 index 51e6ce3..0000000 --- a/lib/_patch/my-mocha.js +++ /dev/null @@ -1,34 +0,0 @@ -(function() { - var runner = new JsReporters.MochaAdapter(mocha); - var errors = [] - var passed = 0, failed = 0, total = 0; - var startTime; - - runner.on('runStart', function() { - startTime = new Date() - }); - - runner.on('testEnd', function(test) { - total = total + 1; - - passed = passed + (test.status === 'passed' ? 1 : 0); - failed = failed + (test.status === 'failed' ? 1 : 0); - - test.errors.forEach(function(error) { - errors.push(error) - }); - }); - - runner.on('runEnd', function() { - var results = {} - - results.runtime = new Date() - startTime; - results.total = total; - results.passed = passed; - results.failed = failed; - results.tracebacks = errors; - results.url = window.location.pathname; - - BrowserStack.post("/_report", results, function() {}); - }); -})(); From b38fe0321cbba59db04598e46c1cbd9eacbd8f97 Mon Sep 17 00:00:00 2001 From: Florentin Date: Thu, 7 Jul 2016 23:46:25 +0100 Subject: [PATCH 084/162] Reporter: merge QUnit, Mocha, Jasmine2 reporters into one. --- lib/_patch/mocha-plugin.js | 32 --------------- lib/_patch/qunit-plugin.js | 39 ------------------- .../{jasmine2-plugin.js => reporter.js} | 15 ++++++- lib/server.js | 19 ++------- 4 files changed, 16 insertions(+), 89 deletions(-) delete mode 100644 lib/_patch/mocha-plugin.js delete mode 100644 lib/_patch/qunit-plugin.js rename lib/_patch/{jasmine2-plugin.js => reporter.js} (58%) diff --git a/lib/_patch/mocha-plugin.js b/lib/_patch/mocha-plugin.js deleted file mode 100644 index 6a26160..0000000 --- a/lib/_patch/mocha-plugin.js +++ /dev/null @@ -1,32 +0,0 @@ -(function() { - var runner = new JsReporters.MochaAdapter(mocha); - var tracebacks = []; - var total = 0, - passed = 0, - failed = 0; - - runner.on('testEnd', function(test) { - total = total + 1 - - passed = passed + (test.status === 'passed' ? 1 : 0); - failed = failed + (test.status === 'failed' ? 1 : 0); - - test.errors.forEach(function(error) { - tracebacks.push(error) - }); - }); - - runner.on('runEnd', function(globalSuite) { - var results = {}; - - // TODO investigate why globalSuite.runtime is not working - results.runtime = 0; - results.total = total; - results.passed = passed; - results.failed = failed; - results.tracebacks = tracebacks; - results.url = window.location.pathname; - - BrowserStack.post("/_report", results, function() {}); - }); -})(); diff --git a/lib/_patch/qunit-plugin.js b/lib/_patch/qunit-plugin.js deleted file mode 100644 index 663a139..0000000 --- a/lib/_patch/qunit-plugin.js +++ /dev/null @@ -1,39 +0,0 @@ -// For logging assertions on the console, here's what grunt-contrib-qunit uses: -// https://github.com/gruntjs/grunt-contrib-qunit/blob/784597023e7235337ca9c0651aa45124a2d72341/tasks/qunit.js#L45 -(function (factory) { - if (typeof define === 'function' && define.amd) { - require(['qunit'], factory); - } else { - factory(QUnit); - } -}(function(QUnit) { - var runner = new JsReporters.QUnitAdapter(QUnit); - var tracebacks = []; - var total = 0, - passed = 0, - failed = 0; - - runner.on('testEnd', function(test) { - total = total + 1 - - passed = passed + (test.status === 'passed' ? 1 : 0); - failed = failed + (test.status === 'failed' ? 1 : 0); - - test.errors.forEach(function(error) { - tracebacks.push(error) - }); - }); - - runner.on('runEnd', function(globalSuite) { - var results = {}; - - results.runtime = globalSuite.runtime; - results.total = total; - results.passed = passed; - results.failed = failed; - results.tracebacks = tracebacks; - results.url = window.location.pathname; - - BrowserStack.post("/_report", results, function(){}); - }); -})); diff --git a/lib/_patch/jasmine2-plugin.js b/lib/_patch/reporter.js similarity index 58% rename from lib/_patch/jasmine2-plugin.js rename to lib/_patch/reporter.js index 05581e7..f8eb9ce 100644 --- a/lib/_patch/jasmine2-plugin.js +++ b/lib/_patch/reporter.js @@ -1,10 +1,20 @@ (function() { - var runner = new JsReporters.JasmineAdapter(jasmine.getEnv()); + var runner; var tracebacks = []; var total = 0, passed = 0, failed = 0; + if (window.QUnit) { + runner = new JsReporters.QUnitAdapter(QUnit); + } else if (window.jasmine) { + runner = new JsReporters.JasmineAdapter(jasmine.getEnv()); + } else if (window.mocha) { + runner = new JsReporters.MochaAdapter(mocha); + } else { + throw new Error('JsReporters: No testing framework was found'); + } + runner.on('testEnd', function(test) { total = total + 1 @@ -19,7 +29,8 @@ runner.on('runEnd', function(globalSuite) { var results = {}; - results.runtime = globalSuite.runtime; + // TODO Investigate why is the runtime not functioning for Mocha. + results.runtime = (window.mocha) ? 0 : globalSuite.runtime; results.total = total; results.passed = passed; results.failed = failed; diff --git a/lib/server.js b/lib/server.js index ac71c80..d98d15e 100644 --- a/lib/server.js +++ b/lib/server.js @@ -40,10 +40,7 @@ exports.Server = function Server(bsClient, workers) { ]; var framework_scripts = { - 'qunit': ['qunit-plugin.js'], - 'jasmine': ['jasmine-jsreporter.js', 'jasmine-plugin.js'], - 'jasmine2': ['jasmine2-plugin.js'], - 'mocha': ['mocha-plugin.js'] + 'jasmine': ['jasmine-jsreporter.js', 'jasmine-plugin.js'] }; var filePath = path.relative(process.cwd(), filename); @@ -64,18 +61,8 @@ exports.Server = function Server(bsClient, workers) { patch += '\n'; }); patch += '\n'; - } else if (framework === 'jasmine2') { - framework_scripts['jasmine2'].forEach(function(script) { - patch += '\n'; - }); - } else if (framework === 'mocha') { - framework_scripts['mocha'].forEach(function(script) { - patch += '\n'; - }); - } else if (framework === 'qunit') { - framework_scripts['qunit'].forEach(function(script) { - patch += '\n'; - }); + } else { + patch += '\n'; } patch += ''; return patch; From 7a9019f4c71b1eec4f885568da7587b78a0a565f Mon Sep 17 00:00:00 2001 From: Florentin Date: Mon, 11 Jul 2016 20:04:20 +0100 Subject: [PATCH 085/162] JsReporters: use npm version. --- lib/_patch/js-reporters.js | 1369 ------------------------------------ lib/server.js | 10 +- 2 files changed, 8 insertions(+), 1371 deletions(-) delete mode 100644 lib/_patch/js-reporters.js diff --git a/lib/_patch/js-reporters.js b/lib/_patch/js-reporters.js deleted file mode 100644 index 9860b75..0000000 --- a/lib/_patch/js-reporters.js +++ /dev/null @@ -1,1369 +0,0 @@ -/** - * JsReporters 1.0.0 - * https://github.com/js-reporters - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: Wed Jul 06 2016 - */ - -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.JsReporters = factory()); -}(this, function () { 'use strict'; - - function __commonjs(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } - - - var babelHelpers = {}; - babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; - }; - - babelHelpers.classCallCheck = function (instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - }; - - babelHelpers.createClass = function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; - }(); - - babelHelpers.inherits = function (subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); - } - - subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { - value: subClass, - enumerable: false, - writable: true, - configurable: true - } - }); - if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; - }; - - babelHelpers.possibleConstructorReturn = function (self, call) { - if (!self) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - - return call && (typeof call === "object" || typeof call === "function") ? call : self; - }; - - babelHelpers.slicedToArray = function () { - function sliceIterator(arr, i) { - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - - try { - for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"]) _i["return"](); - } finally { - if (_d) throw _e; - } - } - - return _arr; - } - - return function (arr, i) { - if (Array.isArray(arr)) { - return arr; - } else if (Symbol.iterator in Object(arr)) { - return sliceIterator(arr, i); - } else { - throw new TypeError("Invalid attempt to destructure non-iterable instance"); - } - }; - }(); - - babelHelpers; - - var events = __commonjs(function (module) { - // Copyright Joyent, Inc. and other Node contributors. - // - // Permission is hereby granted, free of charge, to any person obtaining a - // copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to permit - // persons to whom the Software is furnished to do so, subject to the - // following conditions: - // - // The above copyright notice and this permission notice shall be included - // in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN - // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE - // USE OR OTHER DEALINGS IN THE SOFTWARE. - - function EventEmitter() { - this._events = this._events || {}; - this._maxListeners = this._maxListeners || undefined; - } - module.exports = EventEmitter; - - // Backwards-compat with node 0.10.x - EventEmitter.EventEmitter = EventEmitter; - - EventEmitter.prototype._events = undefined; - EventEmitter.prototype._maxListeners = undefined; - - // By default EventEmitters will print a warning if more than 10 listeners are - // added to it. This is a useful default which helps finding memory leaks. - EventEmitter.defaultMaxListeners = 10; - - // Obviously not all Emitters should be limited to 10. This function allows - // that to be increased. Set to zero for unlimited. - EventEmitter.prototype.setMaxListeners = function (n) { - if (!isNumber(n) || n < 0 || isNaN(n)) throw TypeError('n must be a positive number'); - this._maxListeners = n; - return this; - }; - - EventEmitter.prototype.emit = function (type) { - var er, handler, len, args, i, listeners; - - if (!this._events) this._events = {}; - - // If there is no 'error' event listener then throw. - if (type === 'error') { - if (!this._events.error || isObject(this._events.error) && !this._events.error.length) { - er = arguments[1]; - if (er instanceof Error) { - throw er; // Unhandled 'error' event - } - throw TypeError('Uncaught, unspecified "error" event.'); - } - } - - handler = this._events[type]; - - if (isUndefined(handler)) return false; - - if (isFunction(handler)) { - switch (arguments.length) { - // fast cases - case 1: - handler.call(this); - break; - case 2: - handler.call(this, arguments[1]); - break; - case 3: - handler.call(this, arguments[1], arguments[2]); - break; - // slower - default: - args = Array.prototype.slice.call(arguments, 1); - handler.apply(this, args); - } - } else if (isObject(handler)) { - args = Array.prototype.slice.call(arguments, 1); - listeners = handler.slice(); - len = listeners.length; - for (i = 0; i < len; i++) { - listeners[i].apply(this, args); - } - } - - return true; - }; - - EventEmitter.prototype.addListener = function (type, listener) { - var m; - - if (!isFunction(listener)) throw TypeError('listener must be a function'); - - if (!this._events) this._events = {}; - - // To avoid recursion in the case that type === "newListener"! Before - // adding it to the listeners, first emit "newListener". - if (this._events.newListener) this.emit('newListener', type, isFunction(listener.listener) ? listener.listener : listener); - - if (!this._events[type]) - // Optimize the case of one listener. Don't need the extra array object. - this._events[type] = listener;else if (isObject(this._events[type])) - // If we've already got an array, just append. - this._events[type].push(listener);else - // Adding the second element, need to change to array. - this._events[type] = [this._events[type], listener]; - - // Check for listener leak - if (isObject(this._events[type]) && !this._events[type].warned) { - if (!isUndefined(this._maxListeners)) { - m = this._maxListeners; - } else { - m = EventEmitter.defaultMaxListeners; - } - - if (m && m > 0 && this._events[type].length > m) { - this._events[type].warned = true; - console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); - if (typeof console.trace === 'function') { - // not supported in IE 10 - console.trace(); - } - } - } - - return this; - }; - - EventEmitter.prototype.on = EventEmitter.prototype.addListener; - - EventEmitter.prototype.once = function (type, listener) { - if (!isFunction(listener)) throw TypeError('listener must be a function'); - - var fired = false; - - function g() { - this.removeListener(type, g); - - if (!fired) { - fired = true; - listener.apply(this, arguments); - } - } - - g.listener = listener; - this.on(type, g); - - return this; - }; - - // emits a 'removeListener' event iff the listener was removed - EventEmitter.prototype.removeListener = function (type, listener) { - var list, position, length, i; - - if (!isFunction(listener)) throw TypeError('listener must be a function'); - - if (!this._events || !this._events[type]) return this; - - list = this._events[type]; - length = list.length; - position = -1; - - if (list === listener || isFunction(list.listener) && list.listener === listener) { - delete this._events[type]; - if (this._events.removeListener) this.emit('removeListener', type, listener); - } else if (isObject(list)) { - for (i = length; i-- > 0;) { - if (list[i] === listener || list[i].listener && list[i].listener === listener) { - position = i; - break; - } - } - - if (position < 0) return this; - - if (list.length === 1) { - list.length = 0; - delete this._events[type]; - } else { - list.splice(position, 1); - } - - if (this._events.removeListener) this.emit('removeListener', type, listener); - } - - return this; - }; - - EventEmitter.prototype.removeAllListeners = function (type) { - var key, listeners; - - if (!this._events) return this; - - // not listening for removeListener, no need to emit - if (!this._events.removeListener) { - if (arguments.length === 0) this._events = {};else if (this._events[type]) delete this._events[type]; - return this; - } - - // emit removeListener for all listeners on all events - if (arguments.length === 0) { - for (key in this._events) { - if (key === 'removeListener') continue; - this.removeAllListeners(key); - } - this.removeAllListeners('removeListener'); - this._events = {}; - return this; - } - - listeners = this._events[type]; - - if (isFunction(listeners)) { - this.removeListener(type, listeners); - } else if (listeners) { - // LIFO order - while (listeners.length) { - this.removeListener(type, listeners[listeners.length - 1]); - } - } - delete this._events[type]; - - return this; - }; - - EventEmitter.prototype.listeners = function (type) { - var ret; - if (!this._events || !this._events[type]) ret = [];else if (isFunction(this._events[type])) ret = [this._events[type]];else ret = this._events[type].slice(); - return ret; - }; - - EventEmitter.prototype.listenerCount = function (type) { - if (this._events) { - var evlistener = this._events[type]; - - if (isFunction(evlistener)) return 1;else if (evlistener) return evlistener.length; - } - return 0; - }; - - EventEmitter.listenerCount = function (emitter, type) { - return emitter.listenerCount(type); - }; - - function isFunction(arg) { - return typeof arg === 'function'; - } - - function isNumber(arg) { - return typeof arg === 'number'; - } - - function isObject(arg) { - return (typeof arg === 'undefined' ? 'undefined' : babelHelpers.typeof(arg)) === 'object' && arg !== null; - } - - function isUndefined(arg) { - return arg === void 0; - } - }); - - var EventEmitter = events && (typeof events === 'undefined' ? 'undefined' : babelHelpers.typeof(events)) === 'object' && 'default' in events ? events['default'] : events; - - var Test = function Test(testName, suiteName, status, runtime, errors) { - babelHelpers.classCallCheck(this, Test); - - this.testName = testName; - this.suiteName = suiteName; - this.status = status; - this.runtime = runtime; - this.errors = errors; - }; - - var Suite = function () { - - /** - * - * @param name - * @param childSuites - * @param tests: array containing tests belonging to the suite but not to a child suite - */ - - function Suite(name, childSuites, tests) { - babelHelpers.classCallCheck(this, Suite); - - this.name = name; - this.childSuites = childSuites; - this.tests = tests; - } - - babelHelpers.createClass(Suite, [{ - key: 'getAllTests', - value: function getAllTests() { - var childSuiteTests = this.childSuites.map(function (suite) { - return suite.getAllTests(); - }).reduce(function (allTests, a) { - return allTests.concat(a); - }, []); - - return this.tests.concat(childSuiteTests); - } - }, { - key: 'runtime', - get: function get() { - var status = this.status; - - if (status === 'skipped' || status === undefined) { - return undefined; - } - - var runtime = this.getAllTests().map(function (test) { - return test.status === 'skipped' ? 0 : test.runtime; - }).reduce(function (sum, testRuntime) { - return sum + testRuntime; - }, 0); - - return runtime; - } - }, { - key: 'status', - get: function get() { - var passed = 0; - var failed = 0; - var skipped = 0; - - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = this.getAllTests()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var test = _step.value; - - // If a suite contains a test whose status is still undefined, - // there is no final status for the suite as well. - if (test.status === undefined) { - return undefined; - } else if (test.status === 'passed') { - passed++; - } else if (test.status === 'skipped') { - skipped++; - } else { - failed++; - } - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - if (failed > 0) { - return 'failed'; - } else if (skipped > 0 && passed === 0) { - return 'skipped'; - } else { - return 'passed'; - } - } - }]); - return Suite; - }(); - - Object.defineProperties(Suite.prototype, { - toJSON: { - value: function value() { - var ret = {}; - for (var x in this) { - ret[x] = this[x]; - } - return ret; - } - }, - runtime: { - enumerable: true - }, - status: { - enumerable: true - } - }); - - var QUnitAdapter = function (_EventEmitter) { - babelHelpers.inherits(QUnitAdapter, _EventEmitter); - - function QUnitAdapter(QUnit) { - babelHelpers.classCallCheck(this, QUnitAdapter); - - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(QUnitAdapter).call(this)); - - _this.QUnit = QUnit; - _this.tests = {}; - - QUnit.begin(_this.onBegin.bind(_this)); - QUnit.testStart(_this.onTestStart.bind(_this)); - QUnit.log(_this.onLog.bind(_this)); - QUnit.testDone(_this.onTestDone.bind(_this)); - QUnit.done(_this.onDone.bind(_this)); - return _this; - } - - babelHelpers.createClass(QUnitAdapter, [{ - key: 'convertModule', - value: function convertModule(qunitModule) { - var _this2 = this; - - return new Suite(qunitModule.name, [], qunitModule.tests.map(function (qunitTest) { - var test = new Test(qunitTest.name, qunitModule.name.replace(/> /g, '')); - - _this2.tests[qunitTest.testId] = test; - - return test; - })); - } - }, { - key: 'saveTestDetails', - value: function saveTestDetails(qunitTest) { - var test = this.tests[qunitTest.testId]; - - test.errors = this.errors; - - if (qunitTest.failed > 0) { - test.status = 'failed'; - } else if (qunitTest.skipped) { - test.status = 'skipped'; - } else { - test.status = 'passed'; - } - - // Workaround for QUnit skipped tests runtime which is a Number. - if (test.status !== 'skipped') { - test.runtime = qunitTest.runtime; - } else { - test.runtime = undefined; - } - } - }, { - key: 'createGlobalSuite', - value: function createGlobalSuite() { - var topLevelSuites = []; - var globalSuite; - var modules; - - // Access QUnit internals to get all suites and tests, working around - // missing event data. - - // Create the global suite first. - if (this.QUnit.config.modules.length > 0 && this.QUnit.config.modules[0].name === '') { - globalSuite = this.convertModule(this.QUnit.config.modules[0]); - globalSuite.name = undefined; - - // The suiteName of global tests must be undefined. - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = globalSuite.tests[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var test = _step.value; - - test.suiteName = undefined; - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - modules = this.QUnit.config.modules.slice(1); - } else { - globalSuite = new Suite(undefined, [], []); - modules = this.QUnit.config.modules; - } - - // Build a list with all suites. - var suites = modules.map(this.convertModule.bind(this)); - - // Iterate through the whole suites and check if they have composed names, - // like "suiteName1 > suiteName2 > ... > suiteNameN". - // - // If a suite has a composed name, its name will be the last in the sequence - // and its parent name will be the one right before it. Search the parent - // suite after its name and then add the suite with the composed name to the - // childSuites. - // - // If a suite does not have a composed name, add it to the topLevelSuites, - // this means that this suite is the direct child of the global suite. - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; - - try { - for (var _iterator2 = suites[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var suite = _step2.value; - - var indexEnd = suite.name.lastIndexOf(' > '); - - if (indexEnd !== -1) { - // Find the ' > ' characters that appears before the parent name. - var indexStart = suite.name.substring(0, indexEnd).lastIndexOf(' > '); - // If it is -1, the parent suite name starts at 0, else escape - // this characters ' > '. - indexStart = indexStart === -1 ? 0 : indexStart + 3; - - var parentSuiteName = suite.name.substring(indexStart, indexEnd); - - // Keep only the name of the suite itself. - suite.name = suite.name.substring(indexEnd + 3); - - var _iteratorNormalCompletion3 = true; - var _didIteratorError3 = false; - var _iteratorError3 = undefined; - - try { - for (var _iterator3 = suites[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { - var parentSuite = _step3.value; - - if (parentSuite.name === parentSuiteName) { - parentSuite.childSuites.push(suite); - } - } - } catch (err) { - _didIteratorError3 = true; - _iteratorError3 = err; - } finally { - try { - if (!_iteratorNormalCompletion3 && _iterator3.return) { - _iterator3.return(); - } - } finally { - if (_didIteratorError3) { - throw _iteratorError3; - } - } - } - } else { - topLevelSuites.push(suite); - } - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } - - globalSuite.childSuites = topLevelSuites; - - return globalSuite; - } - }, { - key: 'createSuiteStart', - value: function createSuiteStart(suite) { - return new Suite(suite.name, suite.childSuites.map(this.createSuiteStart.bind(this)), suite.tests.map(this.createTestStart.bind(this))); - } - }, { - key: 'createSuiteEnd', - value: function createSuiteEnd(suite) { - return new Suite(suite.name, suite.childSuites.map(this.createSuiteEnd.bind(this)), suite.tests.map(this.createTestEnd.bind(this))); - } - }, { - key: 'createTestStart', - value: function createTestStart(test) { - return new Test(test.testName, test.suiteName); - } - }, { - key: 'createTestEnd', - value: function createTestEnd(test) { - return new Test(test.testName, test.suiteName, test.status, test.runtime, test.errors); - } - }, { - key: 'emitData', - value: function emitData(suite) { - var _iteratorNormalCompletion4 = true; - var _didIteratorError4 = false; - var _iteratorError4 = undefined; - - try { - for (var _iterator4 = suite.tests[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { - var test = _step4.value; - - this.emit('testStart', this.createTestStart(test)); - this.emit('testEnd', this.createTestEnd(test)); - } - } catch (err) { - _didIteratorError4 = true; - _iteratorError4 = err; - } finally { - try { - if (!_iteratorNormalCompletion4 && _iterator4.return) { - _iterator4.return(); - } - } finally { - if (_didIteratorError4) { - throw _iteratorError4; - } - } - } - - var _iteratorNormalCompletion5 = true; - var _didIteratorError5 = false; - var _iteratorError5 = undefined; - - try { - for (var _iterator5 = suite.childSuites[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { - var _suite = _step5.value; - - this.emit('suiteStart', this.createSuiteStart(_suite)); - this.emitData(_suite); - this.emit('suiteEnd', this.createSuiteEnd(_suite)); - } - } catch (err) { - _didIteratorError5 = true; - _iteratorError5 = err; - } finally { - try { - if (!_iteratorNormalCompletion5 && _iterator5.return) { - _iterator5.return(); - } - } finally { - if (_didIteratorError5) { - throw _iteratorError5; - } - } - } - } - }, { - key: 'onBegin', - value: function onBegin() { - this.globalSuite = this.createGlobalSuite(); - } - }, { - key: 'onTestStart', - value: function onTestStart(details) { - this.errors = []; - } - }, { - key: 'onLog', - value: function onLog(details) { - if (!details.result) { - this.errors.push(details); - } - } - }, { - key: 'onTestDone', - value: function onTestDone(details) { - this.saveTestDetails(details); - } - }, { - key: 'onDone', - value: function onDone() { - this.emit('runStart', this.createSuiteStart(this.globalSuite)); - this.emitData(this.globalSuite); - this.emit('runEnd', this.createSuiteEnd(this.globalSuite)); - } - }]); - return QUnitAdapter; - }(EventEmitter); - - /** - * Limitations: - * - Errors in afterAll are ignored. - */ - - var JasmineAdapter = function (_EventEmitter) { - babelHelpers.inherits(JasmineAdapter, _EventEmitter); - - function JasmineAdapter(jasmine) { - babelHelpers.classCallCheck(this, JasmineAdapter); - - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(JasmineAdapter).call(this)); - - _this.jasmine = jasmine; - jasmine.addReporter({ - jasmineStarted: _this.onJasmineStarted.bind(_this), - specDone: _this.onSpecDone.bind(_this), - specStarted: _this.onSpecStarted.bind(_this), - suiteStarted: _this.onSuiteStarted.bind(_this), - suiteDone: _this.onSuiteDone.bind(_this), - jasmineDone: _this.onJasmineDone.bind(_this) - }); - - _this.suites = {}; - _this.tests = {}; - return _this; - } - - babelHelpers.createClass(JasmineAdapter, [{ - key: 'createSuiteStart', - value: function createSuiteStart(suite) { - return new Suite(suite.name, suite.childSuites.map(this.createSuiteStart.bind(this)), suite.tests.map(this.createTestStart.bind(this))); - } - }, { - key: 'createSuiteEnd', - value: function createSuiteEnd(suite) { - return new Suite(suite.name, suite.childSuites.map(this.createSuiteEnd.bind(this)), suite.tests.map(this.createTestEnd.bind(this))); - } - }, { - key: 'createTestStart', - value: function createTestStart(test) { - return new Test(test.testName, test.suiteName); - } - }, { - key: 'createTestEnd', - value: function createTestEnd(test) { - return new Test(test.testName, test.suiteName, test.status, test.runtime, test.errors); - } - }, { - key: 'saveTestDetails', - value: function saveTestDetails(jasmineSpec) { - var test = this.tests[jasmineSpec.id]; - - test.errors = jasmineSpec.failedExpectations; - - if (jasmineSpec.status === 'pending') { - test.status = 'skipped'; - } else { - test.status = jasmineSpec.status; - test.runtime = new Date() - this.startTime; - } - } - }, { - key: 'isJasmineGlobalSuite', - value: function isJasmineGlobalSuite(suite) { - return suite.description === 'Jasmine__TopLevel__Suite'; - } - - /** - * Jasmine provides details about childSuites and tests only in the structure - * returned by "this.jasmine.topSuite()". - * - * This function creates the global suite for the runStart event, as also - * saves the created suites and tests compliant with the CRI standard in an - * object using as key their unique ids provided by Jasmine. - */ - - }, { - key: 'createGlobalSuite', - value: function createGlobalSuite(jasmineSuite) { - var childSuites = []; - var tests = []; - - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = jasmineSuite.children[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var child = _step.value; - - if (child.id.indexOf('suite') === 0) { - childSuites.push(this.createGlobalSuite(child)); - } else { - var suiteName = void 0; - var test = void 0; - - // Jasmine full description is of form "suite1 suite2 ... suiteN test", - // for the "suiteName" property we need to remove test name. - if (!this.isJasmineGlobalSuite(jasmineSuite)) { - suiteName = child.result.fullName.substring(0, child.result.fullName.indexOf(child.description) - 1); - } - - test = new Test(child.description, suiteName); - - tests.push(test); - this.tests[child.id] = test; - } - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - var name = this.isJasmineGlobalSuite(jasmineSuite) ? undefined : jasmineSuite.description; - var suite = new Suite(name, childSuites, tests); - - this.suites[jasmineSuite.id] = suite; - - return suite; - } - }, { - key: 'onJasmineStarted', - value: function onJasmineStarted() { - this.globalSuite = this.createGlobalSuite(this.jasmine.topSuite()); - this.emit('runStart', this.createSuiteStart(this.globalSuite)); - } - }, { - key: 'onSpecStarted', - value: function onSpecStarted(details) { - this.startTime = new Date(); - this.emit('testStart', this.createTestStart(this.tests[details.id])); - } - }, { - key: 'onSpecDone', - value: function onSpecDone(details) { - this.saveTestDetails(details); - this.emit('testEnd', this.createTestEnd(this.tests[details.id])); - } - }, { - key: 'onSuiteStarted', - value: function onSuiteStarted(details) { - this.emit('suiteStart', this.createSuiteStart(this.suites[details.id])); - } - }, { - key: 'onSuiteDone', - value: function onSuiteDone(details) { - this.emit('suiteEnd', this.createSuiteEnd(this.suites[details.id])); - } - }, { - key: 'onJasmineDone', - value: function onJasmineDone() { - this.emit('runEnd', this.createSuiteEnd(this.globalSuite)); - } - }]); - return JasmineAdapter; - }(EventEmitter); - - var MochaAdapter = function (_EventEmitter) { - babelHelpers.inherits(MochaAdapter, _EventEmitter); - - function MochaAdapter(mocha) { - babelHelpers.classCallCheck(this, MochaAdapter); - - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(MochaAdapter).call(this)); - - _this.mocha = mocha; - - mocha.reporter(function (runner) { - _this.runner = runner; - - runner.on('start', _this.onStart.bind(_this)); - runner.on('suite', _this.onSuite.bind(_this)); - runner.on('test', _this.onTest.bind(_this)); - runner.on('pending', _this.onPending.bind(_this)); - runner.on('fail', _this.onFail.bind(_this)); - runner.on('test end', _this.onTestEnd.bind(_this)); - runner.on('suite end', _this.onSuiteEnd.bind(_this)); - runner.on('end', _this.onEnd.bind(_this)); - }); - return _this; - } - - babelHelpers.createClass(MochaAdapter, [{ - key: 'convertSuite', - value: function convertSuite(mochaSuite) { - return new Suite(mochaSuite.title, mochaSuite.suites.map(this.convertSuite.bind(this)), mochaSuite.tests.map(this.convertTest.bind(this))); - } - }, { - key: 'convertTest', - value: function convertTest(mochaTest) { - var suiteName; - - if (!mochaTest.parent.root) { - suiteName = this.buildSuiteName(mochaTest.parent); - } - - // If the test has the errors attached a "test end" must be emitted, else - // a "test start". - if (mochaTest.errors !== undefined) { - var status = mochaTest.state === undefined ? 'skipped' : mochaTest.state; - - // Test end. - return new Test(mochaTest.title, suiteName, status, mochaTest.duration, mochaTest.errors); - } - - // Test start. - return new Test(mochaTest.title, suiteName); - } - - /** - * Builds a concatenated name from nested suites. - */ - - }, { - key: 'buildSuiteName', - value: function buildSuiteName(mochaSuite) { - var suiteName = mochaSuite.title; - var parent = mochaSuite.parent; - - while (!parent.root) { - suiteName = parent.title + ' ' + suiteName; - parent = parent.parent; - } - - return suiteName; - } - }, { - key: 'onStart', - value: function onStart() { - var globalSuiteStart = this.convertSuite(this.runner.suite); - globalSuiteStart.name = undefined; - - this.emit('runStart', globalSuiteStart); - } - }, { - key: 'onSuite', - value: function onSuite(mochaSuite) { - if (!mochaSuite.root) { - this.emit('suiteStart', this.convertSuite(mochaSuite)); - } - } - }, { - key: 'onTest', - value: function onTest(mochaTest) { - this.errors = []; - - this.emit('testStart', this.convertTest(mochaTest)); - } - - /** - * Emits the start of pending tests, because Mocha does not emit skipped tests - * on its "test" event. - */ - - }, { - key: 'onPending', - value: function onPending(mochaTest) { - this.emit('testStart', this.convertTest(mochaTest)); - } - }, { - key: 'onFail', - value: function onFail(test, error) { - this.errors.push(error); - } - }, { - key: 'onTestEnd', - value: function onTestEnd(mochaTest) { - // Save the errors on Mocha's test object, because when the suite that - // contains this test is emitted on the "suiteEnd" event, it should contain - // also this test with all its details (errors, status, runtime). Runtime - // and status are already attached to the test, but the errors don't. - mochaTest.errors = this.errors; - - this.emit('testEnd', this.convertTest(mochaTest)); - } - }, { - key: 'onSuiteEnd', - value: function onSuiteEnd(mochaSuite) { - if (!mochaSuite.root) { - this.emit('suiteEnd', this.convertSuite(mochaSuite)); - } - } - }, { - key: 'onEnd', - value: function onEnd() { - var globalSuiteEnd = this.convertSuite(this.runner.suite); - globalSuiteEnd.name = undefined; - - this.emit('runEnd', globalSuiteEnd); - } - }]); - return MochaAdapter; - }(EventEmitter); - - var TapReporter = function () { - function TapReporter(runner) { - babelHelpers.classCallCheck(this, TapReporter); - - this.testCount = 0; - - runner.on('runStart', this.onRunStart.bind(this)); - runner.on('testEnd', this.onTestEnd.bind(this)); - runner.on('runEnd', this.onRunEnd.bind(this)); - } - - babelHelpers.createClass(TapReporter, [{ - key: 'onRunStart', - value: function onRunStart(globalSuite) { - console.log('TAP version 13'); - } - }, { - key: 'onTestEnd', - value: function onTestEnd(test) { - this.testCount = this.testCount + 1; - - // TODO maybe switch to test.fullName - // @see https://github.com/js-reporters/js-reporters/issues/65 - if (test.status === 'passed') { - console.log('ok ' + this.testCount + ' ' + test.testName); - } else if (test.status === 'skipped') { - console.log('ok ' + this.testCount + ' ' + test.testName + ' # SKIP'); - } else { - console.log('not ok ' + this.testCount + ' ' + test.testName); - - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = test.errors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var error = _step.value; - - console.log(' ---'); - console.log(' message: "' + error.toString() + '"'); - console.log(' severity: failed'); - console.log(' ...'); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - } - } - }, { - key: 'onRunEnd', - value: function onRunEnd(globalSuite) { - console.log('1..' + this.testCount); - } - }], [{ - key: 'init', - value: function init(runner) { - return new TapReporter(runner); - } - }]); - return TapReporter; - }(); - - // TODO: finish grouping once suiteStart is implemented - var hasGrouping = 'group' in console && 'groupEnd' in console; - - var ConsoleReporter = function () { - function ConsoleReporter(runner) { - babelHelpers.classCallCheck(this, ConsoleReporter); - - runner.on('runStart', this.onRunStart); - runner.on('suiteStart', this.onSuiteStart); - runner.on('testStart', this.onTestStart); - runner.on('testEnd', this.onTestEnd); - runner.on('suiteEnd', this.onSuiteEnd); - runner.on('runEnd', this.onRunEnd); - } - - babelHelpers.createClass(ConsoleReporter, [{ - key: 'onRunStart', - value: function onRunStart(suite) { - console.log('runStart', suite); - } - }, { - key: 'onSuiteStart', - value: function onSuiteStart(suite) { - if (hasGrouping) { - console.group(suite.name); - } - console.log('suiteStart', suite); - } - }, { - key: 'onTestStart', - value: function onTestStart(test) { - console.log('testStart', test); - } - }, { - key: 'onTestEnd', - value: function onTestEnd(test) { - console.log('testEnd', test); - } - }, { - key: 'onSuiteEnd', - value: function onSuiteEnd(suite) { - console.log('suiteEnd', suite); - if (hasGrouping) { - console.groupEnd(); - } - } - }, { - key: 'onRunEnd', - value: function onRunEnd(globalSuite) { - console.log('runEnd', globalSuite); - } - }], [{ - key: 'init', - value: function init(runner) { - return new ConsoleReporter(runner); - } - }]); - return ConsoleReporter; - }(); - - /* - The TestReporter verifies that a test runner outputs the right data in the right order. - To do so, it compares the actual output with the provided reference data. - The result is given in the ok attribute. - */ - - var TestReporter = function () { - - /** - * @param runner: standardized test runner (or adapter) - * @param referenceData: An array of all expected (eventName, eventData) tuples in the right order - */ - - function TestReporter(runner, referenceData) { - babelHelpers.classCallCheck(this, TestReporter); - - this.referenceData = referenceData.slice(); - this.error = false; - runner.on('runStart', this.onEvent.bind(this, 'runStart')); - runner.on('suiteStart', this.onEvent.bind(this, 'suiteStart')); - runner.on('testStart', this.onEvent.bind(this, 'testStart')); - runner.on('testEnd', this.onEvent.bind(this, 'testEnd')); - runner.on('suiteEnd', this.onEvent.bind(this, 'suiteEnd')); - runner.on('runEnd', this.onEvent.bind(this, 'runEnd')); - } - - /** - * Gets called on each event emitted by the runner. Checks if the actual event matches the expected event. - */ - - - babelHelpers.createClass(TestReporter, [{ - key: 'onEvent', - value: function onEvent(eventName, eventData) { - var _referenceData$shift = this.referenceData.shift(); - - var _referenceData$shift2 = babelHelpers.slicedToArray(_referenceData$shift, 2); - - var expectedEventName = _referenceData$shift2[0]; - var expectedEventData = _referenceData$shift2[1]; - - - if (eventName !== expectedEventName || !this.equal(eventData, expectedEventData)) { - this.error = true; - console.error('expected:', expectedEventName, expectedEventData, '\r\n', 'actual:', eventName, eventData); - } - } - }, { - key: 'equal', - - - /** - * Helper function to compare - * - two Test objects - * - two Suite objects - * - two arrays of Test or Suite objects - * The equality check is not completely strict, e.g. the runtime of a Test does not have to be equal. - * @returns {boolean}: true if both objects are equal, false otherwise - */ - value: function equal(actual, expected) { - if (expected instanceof Suite) { - if (actual.name !== expected.name) { - return false; - } - if (!this.equal(actual.childSuites, expected.childSuites)) { - return false; - } - - if (!this.equal(actual.tests, expected.tests)) { - return false; - } - } else if (expected instanceof Test) { - var _arr = ['testName', 'suiteName', 'status']; - - for (var _i = 0; _i < _arr.length; _i++) { - var property = _arr[_i]; - if (actual[property] !== expected[property]) { - return false; - } - } - if (typeof actual.runtime !== 'number' && actual.runtime !== undefined) { - return false; - } - - if (!(actual.errors === undefined && expected.errors === undefined) && actual.errors.length !== expected.errors.length) { - return false; - } - } else if (Array.isArray(expected)) { - if (actual.length !== expected.length) { - return false; - } - - for (var i = 0; i < expected.length; i++) { - if (!this.equal(actual[i], expected[i])) { - return false; - } - } - } else { - return false; - } - return true; - } - }, { - key: 'ok', - get: function get() { - return !this.error && this.referenceData.length === 0; - } - }]); - return TestReporter; - }(); - - var index = { - QUnitAdapter: QUnitAdapter, - JasmineAdapter: JasmineAdapter, - MochaAdapter: MochaAdapter, - TapReporter: TapReporter, - ConsoleReporter: ConsoleReporter, - TestReporter: TestReporter, - Test: Test, - Suite: Suite - }; - - return index; - -})); \ No newline at end of file diff --git a/lib/server.js b/lib/server.js index d98d15e..88f2ca5 100644 --- a/lib/server.js +++ b/lib/server.js @@ -35,8 +35,7 @@ exports.Server = function Server(bsClient, workers) { var scripts = [ 'json2.js', 'browserstack.js', - 'browserstack-util.js', - 'js-reporters.js' + 'browserstack-util.js' ]; var framework_scripts = { @@ -46,6 +45,11 @@ exports.Server = function Server(bsClient, workers) { var filePath = path.relative(process.cwd(), filename); var pathMatches = (testFilePaths.indexOf(filePath) !== -1); + var jsReportersPath = path.join(__dirname, '../node_modules/js-reporters/dist/js-reporters.js'); + var jsReportersScript = fs.readFileSync(jsReportersPath, { + encoding: 'utf8' + }); + if (pathMatches) { var framework = config['test_framework']; var tag_name = (framework === 'mocha') ? 'head' : 'body'; @@ -55,6 +59,8 @@ exports.Server = function Server(bsClient, workers) { patch += '\n'; }); + patch += ''; + // adding framework scripts if (framework === 'jasmine') { framework_scripts['jasmine'].forEach(function(script) { From b73d6a6030b84e2bb2ffe79258c297c7f41cc2c9 Mon Sep 17 00:00:00 2001 From: Florentin Date: Mon, 11 Jul 2016 20:04:53 +0100 Subject: [PATCH 086/162] Package.json: add js-reporters to deps. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index abc76b1..1032cb9 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dependencies": { "browserstack": "1.3.0", "chalk": "0.4.0", + "js-reporters": "^1.0.0", "mime": "1.3.4", "send": "0.13.0", "tunnel": "0.0.3" From 13535c6f7f258945ceb160eb744aeea484467957 Mon Sep 17 00:00:00 2001 From: Florentin Date: Mon, 11 Jul 2016 22:53:31 +0100 Subject: [PATCH 087/162] Testing: run also tests for mocha v2.4.5 --- tests/external-tests.js | 49 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/tests/external-tests.js b/tests/external-tests.js index cab1695..e879f3f 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -13,6 +13,47 @@ var runnerPath = path.resolve(path.join(__dirname, '..', 'bin', 'cli.js')); var testHome = path.resolve(__dirname); process.chdir(testHome); +/** + * Mocha v2.4.5 - to change with another Mocha version or + * something with Mocha tests + * + * index.html - 22 tests, 18 passed, 4 failed -> one test is displayed twice, + * so they are displayed 5 failing tests, but counted only 4 + * large.html - 64 tests, 60 passed, 4 failed -> only 2 tests are failing, but + * they are displayed twice + * opts.html - 8 tests, 2 passed, 6 failed -> only 3 tests are failing, but + * they are displayed twice + * + * By "displayed" it is referred the Mocha HTML Reporter. + * + * From the above explanations it is clear that there are some inconsistencies, + * also because Mocha's HTML Reporter counted number of tests does not match + * the number of displyed tests. + * + * The cause is (snippet from Mocha's HTML reporter): + * + * runner.on('fail', function(test) { + * // For type = 'test' its possible that the test failed due to multiple + * // done() calls. So report the issue here. + * if (test.type === 'hook' + * || test.type === 'test') { + * runner.emit('test end', test); + * } + * }); + * + * This is why failed tests are displayed twice... + * + * The JsReporters is counting the tests on the "test end" event, that's why + * it is capturing the failing tests twice, in the "index.html" it does not + * capture everything, because there is an async test, which failure is + * triggered after a timeout and the JsReporters is not waiting, because + * it cannot know how much to wait. + * + * + * This been said, the JsReporter MochaAdapter is functioning well, this + * version of Mocha is not reliable and should be changed. + */ + var repositories = [ { name: 'qunit', @@ -36,7 +77,7 @@ var repositories = [ failed: 0 } }, - /*{ + { name: 'mocha', tag: 'v2.4.5', url: 'https://github.com/mochajs/mocha.git', @@ -55,11 +96,11 @@ var repositories = [ 'test/browser/opts.html' ], expected_results: { - tests: 89, + tests: 94, passed: 80, - failed: 9 + failed: 14 } - },*/ + }, { name: 'spine', tag: 'v.1.6.2', From fbfbaf7ed9d13a19761fd88a8b130690484092b2 Mon Sep 17 00:00:00 2001 From: Florentin Date: Fri, 15 Jul 2016 16:03:47 +0100 Subject: [PATCH 088/162] Reporter: compute runtime also for Mocha. --- lib/_patch/reporter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/_patch/reporter.js b/lib/_patch/reporter.js index f8eb9ce..b527b10 100644 --- a/lib/_patch/reporter.js +++ b/lib/_patch/reporter.js @@ -29,8 +29,7 @@ runner.on('runEnd', function(globalSuite) { var results = {}; - // TODO Investigate why is the runtime not functioning for Mocha. - results.runtime = (window.mocha) ? 0 : globalSuite.runtime; + results.runtime = globalSuite.runtime; results.total = total; results.passed = passed; results.failed = failed; From d86ffed5e4ab8c283ee1c3ce6dd4fabe5ce38d7f Mon Sep 17 00:00:00 2001 From: Florentin Date: Sun, 17 Jul 2016 21:36:11 +0100 Subject: [PATCH 089/162] Reporter: report tracebacks to _progress. --- lib/_patch/reporter.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/_patch/reporter.js b/lib/_patch/reporter.js index b527b10..e8006ef 100644 --- a/lib/_patch/reporter.js +++ b/lib/_patch/reporter.js @@ -1,6 +1,5 @@ (function() { var runner; - var tracebacks = []; var total = 0, passed = 0, failed = 0; @@ -22,7 +21,15 @@ failed = failed + (test.status === 'failed' ? 1 : 0); test.errors.forEach(function(error) { - tracebacks.push(error) + BrowserStack.post("/_progress", { + tracebacks: [{ + actual: error.actual, + expected: error.expected, + message: error.message, + source: error.source || error.stack, + testName: test.testName + }] + }, function() {}); }); }); @@ -33,7 +40,6 @@ results.total = total; results.passed = passed; results.failed = failed; - results.tracebacks = tracebacks; results.url = window.location.pathname; BrowserStack.post("/_report", results, function() {}); From 5e8943a7280194c1f651b6651cdaac84e17b9050 Mon Sep 17 00:00:00 2001 From: Vibhaj Rajan Date: Sat, 23 Jul 2016 16:36:18 +0530 Subject: [PATCH 090/162] Updated readme links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b66a83..e67a717 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ To run browser tests on BrowserStack infrastructure, you need to create a `brows * `test_path`: Path to the test page which will run the tests when opened in a browser. * `test_framework`: Specify test framework which will run the tests. Currently supporting qunit, jasmine, jasmine2 and mocha. * `timeout`: Specify worker timeout with BrowserStack. - * `browsers`: A list of browsers on which tests are to be run. Find a [list of all supported browsers and platforms on browerstack.com](http://www.browserstack.com/list-of-browsers-and-platforms?product=live). + * `browsers`: A list of browsers on which tests are to be run. Find a [list of all supported browsers and platforms on browerstack.com](https://www.browserstack.com/list-of-browsers-and-platforms?product=js_testing). * `build`: A string to identify your test run in Browserstack. In `TRAVIS` setup `TRAVIS_COMMIT` will be the default identifier. * `proxy`: Specify a proxy to use for the local tunnel. Object with `host`, `port`, `username` and `password` properties. @@ -99,7 +99,7 @@ Example: ] ``` -For a full list of supported browsers, platforms and other details, [visit the BrowserStack site](https://www.browserstack.com/list-of-browsers-and-platforms?product=automate). +For a full list of supported browsers, platforms and other details, [visit the BrowserStack site](https://www.browserstack.com/list-of-browsers-and-platforms?product=js_testing). #### Compact `browsers` configuration From df5e037515ccdf9d4d60b12af592fe54c98cdd46 Mon Sep 17 00:00:00 2001 From: Vibhaj Rajan Date: Tue, 26 Jul 2016 23:09:24 +0530 Subject: [PATCH 091/162] support standard BROWSERSTACK_ACCESS_KEY environment variable too --- lib/config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/config.js b/lib/config.js index 38ccfe7..8a5a9c0 100644 --- a/lib/config.js +++ b/lib/config.js @@ -31,6 +31,10 @@ if (process.env.BROWSERSTACK_KEY) { config.key = process.env.BROWSERSTACK_KEY; } +if (process.env.BROWSERSTACK_ACCESS_KEY) { + config.key = process.env.BROWSERSTACK_ACCESS_KEY; +} + if (process.env.BROWSERSTACK_USERNAME) { config.username = process.env.BROWSERSTACK_USERNAME; } From 18d68afabf99149765880c164326d036143c9cea Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Mon, 1 Aug 2016 15:42:10 +0530 Subject: [PATCH 092/162] Modular Structure for Runner and Config --- bin/cli.js | 444 ++------------------------------------------------ bin/runner.js | 426 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/config.js | 149 ++++++++--------- lib/local.js | 8 +- lib/server.js | 3 +- lib/utils.js | 76 --------- package.json | 3 +- 7 files changed, 520 insertions(+), 589 deletions(-) create mode 100755 bin/runner.js diff --git a/bin/cli.js b/bin/cli.js index 03dc16b..7740bba 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -16,436 +16,14 @@ if (todo === 'init') { return; } -var Log = require('../lib/logger'), - logger = new Log(global.logLevel), - BrowserStack = require('browserstack'), - fs = require('fs'), - qs = require('querystring'), - chalk = require('chalk'), - config = require('../lib/config'), - utils = require('../lib/utils'), - Server = require('../lib/server').Server, - Tunnel = require('../lib/local').Tunnel, - tunnel = require('tunnel'), - http = require('http'), - ConfigParser = require('../lib/configParser').ConfigParser, - serverPort = 8888, - server, - timeout, - activityTimeout, - ackTimeout, - workers = {}, - workerKeys = {}, - tunnelingAgent, - tunnel; - -function terminateAllWorkers(callback) { - logger.trace('terminateAllWorkers'); - - var cleanWorker = function(id, key) { - logger.trace('cleanWorker(%s, %s)', id, key); - - client.terminateWorker(id, function() { - var worker = workers[key]; - if(worker) { - logger.debug('[%s] Terminated', worker.string); - clearTimeout(worker.ackTimeout); - clearTimeout(worker.activityTimeout); - clearTimeout(worker.testActivityTimeout); - delete workers[key]; - delete workerKeys[worker.id]; - } - if (utils.objectSize(workers) === 0) { - logger.trace('terminateAllWorkers: done'); - callback(); - } - }); - }; - - if (utils.objectSize(workers) === 0) { - logger.trace('terminateAllWorkers: done'); - callback(); - } else { - for (var key in workers){ - var worker = workers[key]; - if (worker.id) { - cleanWorker(worker.id, key); - } else { - delete workers[key]; - if (utils.objectSize(workers) === 0) { - logger.trace('terminateAllWorkers: done'); - callback(); - } - } - } - } -} - -function cleanUpAndExit(signal, status) { - logger.trace('cleanUpAndExit: signal: %s', signal); - - try { - server.close(); - } catch (e) { - logger.debug('Server already closed'); - } - - if (statusPoller) { - statusPoller.stop(); - } - - try { - process.kill(tunnel.process.pid, 'SIGKILL'); - } catch (e) { - logger.debug('Non existent tunnel'); - } - try { - fs.unlinkSync(pid_file); - } catch (e) { - logger.debug('Non existent pid file.'); - } - - if (signal === 'SIGTERM') { - logger.debug('Exiting'); - process.exit(status); - } else { - terminateAllWorkers(function() { - logger.debug('Exiting'); - process.exit(1); - }); - } -} - -function getTestBrowserInfo(browserString, path) { - var info = browserString; - if (config.multipleTest) { - info += ', ' + path; - } - - logger.trace('getTestBrowserInfo(%s, %s): %s', browserString, path, info); - return info; -} - - -function buildTestUrl(test_path, worker_key, browser_string) { - var url = 'http://localhost:' + serverPort + '/' + test_path; - - var querystring = qs.stringify({ - _worker_key: worker_key, - _browser_string: browser_string - }); - - url += ((url.indexOf('?') > 0) ? '&' : '?') + querystring; - logger.trace('buildTestUrl:', url); - return url; -} - - -function launchServer() { - logger.trace('launchServer:', serverPort); - logger.debug('Launching server on port:', serverPort); - - server = new Server(client, workers); - server.listen(parseInt(serverPort, 10)); -} - -function launchBrowser(browser, path) { - var key = utils.uuid(); - var browserString = utils.browserString(browser); - var browserInfo = getTestBrowserInfo(browserString, path); - logger.debug('[%s] Launching', browserInfo); - - browser.url = buildTestUrl(path.replace(/\\/g, '/'), key, browserString); - - if (config.project) { - browser.project = config.project; - } - if (config.build) { - browser.build = config.build; - } - - if(config.tunnelIdentifier) { - browser['tunnel_identifier'] = config.tunnelIdentifier; - } - - timeout = parseInt(config.timeout); - if (!isNaN(timeout)) { - browser.timeout = timeout; - } else { - timeout = 300; - } - activityTimeout = timeout - 10; - ackTimeout = parseInt(config.ackTimeout) || 60; - - logger.trace('[%s] client.createWorker', browserInfo, browser); - - client.createWorker(browser, function (err, worker) { - logger.trace('[%s] client.createWorker | response:', browserInfo, worker, err); - - if (err || typeof worker !== 'object') { - logger.info('Error from BrowserStack: ', err); - utils.alertBrowserStack('Failed to launch worker', - 'Arguments: ' + JSON.stringify({ - err: err, - worker: worker - }, null, 4)); - return; - } - - worker.config = browser; - worker.string = browserString; - worker.test_path = path; - worker.path_index = 0; - - // attach helper methods to manage worker state - attachWorkerHelpers(worker); - - workers[key] = worker; - workerKeys[worker.id] = {key: key, marked: false}; - }); -} - -function launchBrowsers(config, browser) { - setTimeout(function () { - logger.trace('launchBrowsers', browser); - - if (Array.isArray(config.test_path)){ - config.multipleTest = config.test_path.length > 1? true : false; - launchBrowser(browser, config.test_path[0]); - } else { - config.multipleTest = false; - launchBrowser(browser, config.test_path); - } - }, 100); -} - - -function attachWorkerHelpers(worker) { - // TODO: Consider creating instances of a proper 'Worker' class - - worker.buildUrl = function buildUrl(test_path) { - var workerKey = workerKeys[this.id] ? workerKeys[this.id].key : null; - var url = buildTestUrl(test_path || this.test_path, workerKey, this.getTestBrowserInfo()); - logger.trace('[%s] worker.buildUrl: %s', this.id, url); - return url; - }; - - worker.getTestBrowserInfo = function getTestBrowserInfo(test_path) { - var info = this.string; - if (config.multipleTest) { - info += ', ' + (test_path || this.test_path); - } - return info; - }; - - worker.awaitAck = function awaitAck() { - var self = this; - - if (this.ackTimeout) { - logger.trace('[%s] worker.awaitAck: already awaiting ack, or awaited ack once and failed', self.id); - return; - } - - logger.trace('[%s] worker.awaitAck: timeout in %d secs', self.id, ackTimeout); - - this.ackTimeout = setTimeout(function () { - if (self.isAckd) { - logger.trace('[%s] worker.awaitAck: already ackd', self.id); - return; - } - - var url = self.buildUrl(); - logger.trace('[%s] worker.awaitAck: client.changeUrl: %s', self.id, url); - - client.changeUrl(self.id, { url: url }, function (err, data) { - logger.trace('[%s] worker.awaitAck: client.changeUrl: %s | response:', self.id, url, data, err); - logger.debug('[%s] Sent Request to reload url', self.getTestBrowserInfo()); - }); - - }, ackTimeout * 1000); - - logger.debug('[%s] Awaiting ack', this.getTestBrowserInfo()); - }; - - worker.markAckd = function markAckd() { - this.resetAck(); - this.isAckd = true; - - logger.trace('[%s] worker.markAckd', this.id); - logger.debug('[%s] Received ack', this.getTestBrowserInfo()); - }; - - worker.resetAck = function resetAck() { - logger.trace('[%s] worker.resetAck', this.id); - - clearTimeout(this.ackTimeout); - this.ackTimeout = null; - this.isAckd = false; - }; - - return worker; -} - - -var statusPoller = { - poller: null, - - start: function() { - logger.trace('statusPoller.start'); - - statusPoller.poller = setInterval(function () { - client.getWorkers(function (err, _workers) { - logger.trace('client.getWorkers | response: worker count: %d', (_workers || []).length, err); - - if (!_workers) { - logger.info(chalk.red('Error found: ' + err)); - return; - } - _workers.filter(function(currentValue) { - return currentValue.status === 'running' && workerKeys[currentValue.id] && !workerKeys[currentValue.id].marked; - }).forEach(function(_worker) { - var workerData = workerKeys[_worker.id]; - var worker = workers[workerData.key]; - if (worker.launched) { - return; - } - - if (_worker.status === 'running') { - //clearInterval(statusPoller); - logger.debug('[%s] Launched', worker.getTestBrowserInfo()); - worker.launched = true; - workerData.marked = true; - - // Await ack from browser-worker - worker.awaitAck(); - logger.trace('[%s] worker.activityTimeout: timeout in %d secs', worker.id, activityTimeout); - - worker.activityTimeout = setTimeout(function () { - if (!worker.isAckd) { - logger.trace('[%s] worker.activityTimeout', worker.id); - - var subject = 'Worker inactive for too long: ' + worker.string; - var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); - utils.alertBrowserStack(subject, content, null, function(){}); - delete workers[workerData.key]; - delete workerKeys[worker.id]; - config.status += 1; - if (utils.objectSize(workers) === 0) { - var color = config.status > 0 ? 'red' : 'green'; - logger.info(chalk[color]('All tests done, failures: %d.'), config.status); - - if (config.status > 0) { - config.status = 1; - } - - logger.trace('[%s] worker.activityTimeout: all tests done', worker.id, config.status && 'with failures'); - process.exit('SIGTERM'); - } - } else { - logger.trace('[%s] worker.activityTimeout: already ackd', worker.id); - } - }, activityTimeout * 1000); - - - logger.trace('[%s] worker.testActivityTimeout: timeout in %d secs', worker.id, activityTimeout); - - worker.testActivityTimeout = setTimeout(function () { - if (worker.isAckd) { - logger.trace('[%s] worker.testActivityTimeout', worker.id); - - var subject = 'Tests timed out on: ' + worker.string; - var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); - utils.alertBrowserStack(subject, content, null, function(){}); - delete workers[workerData.key]; - delete workerKeys[worker.id]; - config.status += 1; - if (utils.objectSize(workers) === 0) { - var color = config.status > 0 ? 'red' : 'green'; - logger.info(chalk[color]('All tests done, failures: %d.'), config.status); - - if (config.status > 0) { - config.status = 1; - } - - logger.trace('[%s] worker.testActivityTimeout: all tests done', worker.id, config.status && 'with failures'); - process.exit('SIGTERM'); - } - } else { - logger.trace('[%s] worker.testActivityTimeout: not ackd', worker.id); - } - }, (activityTimeout * 1000)); - } - }); - }); - }, 2000); - }, - - stop: function() { - logger.trace('statusPoller.poller'); - clearInterval(statusPoller.poller); - } -}; - -function runTests() { - if (config.proxy) { - logger.trace('runTests: with proxy', config.proxy); - - tunnelingAgent = tunnel.httpOverHttp({ - proxy: config.proxy - }); - var oldhttpreq = http.request; - http.request = function (options, callback) { - options.agent = tunnelingAgent; - return oldhttpreq.call(null, options, callback); - }; - } - if (config.browsers && config.browsers.length > 0) { - ConfigParser.parse(client, config.browsers, function(browsers){ - launchServer(); - - logger.trace('runTests: creating tunnel'); - tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, function () { - logger.trace('runTests: created tunnel'); - - statusPoller.start(); - var total_runs = config.browsers.length * (Array.isArray(config.test_path) ? config.test_path.length : 1); - logger.info('Launching ' + config.browsers.length + ' worker(s) for ' + total_runs + ' run(s).'); - browsers.forEach(function(browser) { - if (browser.browser_version === 'latest') { - logger.debug('[%s] Finding version.', utils.browserString(browser)); - logger.trace('runTests: client.getLatest'); - - client.getLatest(browser, function(err, version) { - logger.trace('runTests: client.getLatest | response:', version, err); - logger.debug('[%s] Version is %s.', - utils.browserString(browser), version); - browser.browser_version = version; - // So that all latest logs come in together - launchBrowsers(config, browser); - }); - } else { - launchBrowsers(config, browser); - } - }); - }); - }); - } else { - launchServer(); - } -} - -try { - var client = BrowserStack.createClient({ - username: config.username, - password: config.key - }); - runTests(); - var pid_file = process.cwd() + '/browserstack-run.pid'; - fs.writeFileSync(pid_file, process.pid, 'utf-8'); - process.on('exit', function(signal){ - cleanUpAndExit(signal, config.status); - }); -} catch (e) { - console.log(e); - console.log('Invalid command.'); -} +var runner = require('./runner.js'); +runner.test(process.env.BROWSERSTACK_JSON || 'browserstack.json', function(err) { + if(err) { + console.log(err); + console.log(err.stack); + console.log('Invalid Command'); + } +}); +process.on('exit', function(signal) { + runner.cleanUpAndExit(signal); +}); diff --git a/bin/runner.js b/bin/runner.js new file mode 100755 index 0000000..a525217 --- /dev/null +++ b/bin/runner.js @@ -0,0 +1,426 @@ +var Log = require('../lib/logger'), + logger = new Log(global.logLevel), + BrowserStack = require('browserstack'), + fs = require('fs'), + qs = require('querystring'), + chalk = require('chalk'), + utils = require('../lib/utils'), + Server = require('../lib/server').Server, + Tunnel = require('../lib/local').Tunnel, + tunnel = require('tunnel'), + http = require('http'), + ConfigParser = require('../lib/configParser').ConfigParser, + serverPort = 8888, + config, + server, + timeout, + activityTimeout, + ackTimeout, + client, + pid_file = process.cwd() + '/browserstack-run.pid', + workers = {}, + workerKeys = {}, + tunnelingAgent, + tunnel; + +function terminateAllWorkers(callback) { + logger.trace('terminateAllWorkers'); + + var cleanWorker = function(id, key) { + logger.trace('cleanWorker(%s, %s)', id, key); + + client.terminateWorker(id, function() { + var worker = workers[key]; + if(worker) { + logger.debug('[%s] Terminated', worker.string); + clearTimeout(worker.ackTimeout); + clearTimeout(worker.activityTimeout); + clearTimeout(worker.testActivityTimeout); + delete workers[key]; + delete workerKeys[worker.id]; + } + if (utils.objectSize(workers) === 0) { + logger.trace('terminateAllWorkers: done'); + callback(); + } + }); + }; + + if (utils.objectSize(workers) === 0) { + logger.trace('terminateAllWorkers: done'); + callback(); + } else { + for (var key in workers){ + var worker = workers[key]; + if (worker.id) { + cleanWorker(worker.id, key); + } else { + delete workers[key]; + if (utils.objectSize(workers) === 0) { + logger.trace('terminateAllWorkers: done'); + callback(); + } + } + } + } +} + +function cleanUpAndExit(signal) { + logger.trace('cleanUpAndExit: signal: %s', signal); + + try { + server.close(); + } catch (e) { + logger.debug('Server already closed'); + } + + if (statusPoller) { + statusPoller.stop(); + } + + try { + process.kill(tunnel.process.pid, 'SIGKILL'); + } catch (e) { + logger.debug('Non existent tunnel'); + } + try { + fs.unlinkSync(pid_file); + } catch (e) { + logger.debug('Non existent pid file.'); + } + + if (signal === 'SIGTERM') { + logger.debug('Exiting'); + process.exit(config.status); + } else { + terminateAllWorkers(function() { + logger.debug('Exiting'); + process.exit(1); + }); + } +} + +function getTestBrowserInfo(browserString, path) { + var info = browserString; + if (config.multipleTest) { + info += ', ' + path; + } + + logger.trace('getTestBrowserInfo(%s, %s): %s', browserString, path, info); + return info; +} + +function buildTestUrl(test_path, worker_key, browser_string) { + var url = 'http://localhost:' + serverPort + '/' + test_path; + + var querystring = qs.stringify({ + _worker_key: worker_key, + _browser_string: browser_string + }); + + url += ((url.indexOf('?') > 0) ? '&' : '?') + querystring; + logger.trace('buildTestUrl:', url); + return url; +} + +function launchServer(config) { + logger.trace('launchServer:', serverPort); + logger.debug('Launching server on port:', serverPort); + + server = new Server(client, workers, config); + server.listen(parseInt(serverPort, 10)); +} + +function launchBrowser(browser, path) { + var key = utils.uuid(); + var browserString = utils.browserString(browser); + var browserInfo = getTestBrowserInfo(browserString, path); + logger.debug('[%s] Launching', browserInfo); + + browser.url = buildTestUrl(path.replace(/\\/g, '/'), key, browserString); + + if (config.project) { + browser.project = config.project; + } + if (config.build) { + browser.build = config.build; + } + + if(config.tunnelIdentifier) { + browser['tunnel_identifier'] = config.tunnelIdentifier; + } + + timeout = parseInt(config.timeout); + if (!isNaN(timeout)) { + browser.timeout = timeout; + } else { + timeout = 300; + } + activityTimeout = timeout - 10; + ackTimeout = parseInt(config.ackTimeout) || 60; + + logger.trace('[%s] client.createWorker', browserInfo, browser); + + client.createWorker(browser, function (err, worker) { + logger.trace('[%s] client.createWorker | response:', browserInfo, worker, err); + + if (err || typeof worker !== 'object') { + logger.info('Error from BrowserStack: ', err); + return; + } + + worker.config = browser; + worker.string = browserString; + worker.test_path = path; + worker.path_index = 0; + + // attach helper methods to manage worker state + attachWorkerHelpers(worker); + + workers[key] = worker; + workerKeys[worker.id] = {key: key, marked: false}; + }); +} + +function launchBrowsers(config, browser) { + setTimeout(function () { + logger.trace('launchBrowsers', browser); + + if (Array.isArray(config.test_path)){ + config.multipleTest = config.test_path.length > 1? true : false; + launchBrowser(browser, config.test_path[0]); + } else { + config.multipleTest = false; + launchBrowser(browser, config.test_path); + } + }, 100); +} + +function attachWorkerHelpers(worker) { + // TODO: Consider creating instances of a proper 'Worker' class + + worker.buildUrl = function buildUrl(test_path) { + var workerKey = workerKeys[this.id] ? workerKeys[this.id].key : null; + var url = buildTestUrl(test_path || this.test_path, workerKey, this.getTestBrowserInfo()); + logger.trace('[%s] worker.buildUrl: %s', this.id, url); + return url; + }; + + worker.getTestBrowserInfo = function getTestBrowserInfo(test_path) { + var info = this.string; + if (config.multipleTest) { + info += ', ' + (test_path || this.test_path); + } + return info; + }; + + worker.awaitAck = function awaitAck() { + var self = this; + + if (this.ackTimeout) { + logger.trace('[%s] worker.awaitAck: already awaiting ack, or awaited ack once and failed', self.id); + return; + } + + logger.trace('[%s] worker.awaitAck: timeout in %d secs', self.id, ackTimeout); + + this.ackTimeout = setTimeout(function () { + if (self.isAckd) { + logger.trace('[%s] worker.awaitAck: already ackd', self.id); + return; + } + + var url = self.buildUrl(); + logger.trace('[%s] worker.awaitAck: client.changeUrl: %s', self.id, url); + + client.changeUrl(self.id, { url: url }, function (err, data) { + logger.trace('[%s] worker.awaitAck: client.changeUrl: %s | response:', self.id, url, data, err); + logger.debug('[%s] Sent Request to reload url', self.getTestBrowserInfo()); + }); + + }, ackTimeout * 1000); + + logger.debug('[%s] Awaiting ack', this.getTestBrowserInfo()); + }; + + worker.markAckd = function markAckd() { + this.resetAck(); + this.isAckd = true; + + logger.trace('[%s] worker.markAckd', this.id); + logger.debug('[%s] Received ack', this.getTestBrowserInfo()); + }; + + worker.resetAck = function resetAck() { + logger.trace('[%s] worker.resetAck', this.id); + + clearTimeout(this.ackTimeout); + this.ackTimeout = null; + this.isAckd = false; + }; + + return worker; +} + +var statusPoller = { + poller: null, + + start: function() { + logger.trace('statusPoller.start'); + + statusPoller.poller = setInterval(function () { + client.getWorkers(function (err, _workers) { + logger.trace('client.getWorkers | response: worker count: %d', (_workers || []).length, err); + + if (!_workers) { + logger.info(chalk.red('Error found: ' + err)); + return; + } + _workers.filter(function(currentValue) { + return currentValue.status === 'running' && workerKeys[currentValue.id] && !workerKeys[currentValue.id].marked; + }).forEach(function(_worker) { + var workerData = workerKeys[_worker.id]; + var worker = workers[workerData.key]; + if (worker.launched) { + return; + } + + if (_worker.status === 'running') { + //clearInterval(statusPoller); + logger.debug('[%s] Launched', worker.getTestBrowserInfo()); + worker.launched = true; + workerData.marked = true; + + // Await ack from browser-worker + worker.awaitAck(); + logger.trace('[%s] worker.activityTimeout: timeout in %d secs', worker.id, activityTimeout); + + worker.activityTimeout = setTimeout(function () { + if (!worker.isAckd) { + logger.trace('[%s] worker.activityTimeout', worker.id); + + var subject = 'Worker inactive for too long: ' + worker.string; + var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); + delete workers[workerData.key]; + delete workerKeys[worker.id]; + config.status += 1; + if (utils.objectSize(workers) === 0) { + var color = config.status > 0 ? 'red' : 'green'; + logger.info(chalk[color]('All tests done, failures: %d.'), config.status); + + if (config.status > 0) { + config.status = 1; + } + + logger.trace('[%s] worker.activityTimeout: all tests done', worker.id, config.status && 'with failures'); + process.exit('SIGTERM'); + } + } else { + logger.trace('[%s] worker.activityTimeout: already ackd', worker.id); + } + }, activityTimeout * 1000); + + + logger.trace('[%s] worker.testActivityTimeout: timeout in %d secs', worker.id, activityTimeout); + + worker.testActivityTimeout = setTimeout(function () { + if (worker.isAckd) { + logger.trace('[%s] worker.testActivityTimeout', worker.id); + + var subject = 'Tests timed out on: ' + worker.string; + var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); + delete workers[workerData.key]; + delete workerKeys[worker.id]; + config.status += 1; + if (utils.objectSize(workers) === 0) { + var color = config.status > 0 ? 'red' : 'green'; + logger.info(chalk[color]('All tests done, failures: %d.'), config.status); + + if (config.status > 0) { + config.status = 1; + } + + logger.trace('[%s] worker.testActivityTimeout: all tests done', worker.id, config.status && 'with failures'); + process.exit('SIGTERM'); + } + } else { + logger.trace('[%s] worker.testActivityTimeout: not ackd', worker.id); + } + }, (activityTimeout * 1000)); + } + }); + }); + }, 2000); + }, + + stop: function() { + logger.trace('statusPoller.poller'); + clearInterval(statusPoller.poller); + } +}; + +function runTests(config) { + if (config.proxy) { + logger.trace('runTests: with proxy', config.proxy); + + tunnelingAgent = tunnel.httpOverHttp({ + proxy: config.proxy + }); + var oldhttpreq = http.request; + http.request = function (options, callback) { + options.agent = tunnelingAgent; + return oldhttpreq.call(null, options, callback); + }; + } + if (config.browsers && config.browsers.length > 0) { + ConfigParser.parse(client, config.browsers, function(browsers){ + launchServer(config); + + logger.trace('runTests: creating tunnel'); + tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, config, function () { + logger.trace('runTests: created tunnel'); + + statusPoller.start(); + var total_runs = config.browsers.length * (Array.isArray(config.test_path) ? config.test_path.length : 1); + logger.info('Launching ' + config.browsers.length + ' worker(s) for ' + total_runs + ' run(s).'); + browsers.forEach(function(browser) { + if (browser.browser_version === 'latest') { + logger.debug('[%s] Finding version.', utils.browserString(browser)); + logger.trace('runTests: client.getLatest'); + + client.getLatest(browser, function(err, version) { + logger.trace('runTests: client.getLatest | response:', version, err); + logger.debug('[%s] Version is %s.', + utils.browserString(browser), version); + browser.browser_version = version; + // So that all latest logs come in together + launchBrowsers(config, browser); + }); + } else { + launchBrowsers(config, browser); + } + }); + }); + }); + } else { + launchServer(config); + } +} + +exports.test = function(config_file, callback) { + callback = callback || function() {}; + + try { + config = new (require('../lib/config').config)(config_file); + + client = BrowserStack.createClient({ + username: config.username, + password: config.key + }); + runTests(config); + fs.writeFileSync(pid_file, process.pid, 'utf-8'); + } catch (e) { + callback(e); + } +} +exports.cleanUpAndExit = cleanUpAndExit; diff --git a/lib/config.js b/lib/config.js index 8a5a9c0..c702d3a 100644 --- a/lib/config.js +++ b/lib/config.js @@ -4,69 +4,11 @@ var Log = require('./logger'), fs = require('fs'), pwd = process.cwd(); -var config_path = process.env.BROWSERSTACK_JSON || 'browserstack.json'; -config_path = path.resolve(path.relative(process.cwd(), config_path)); -logger.debug('Using config:', config_path); - -try { - var config = require(config_path); -} catch (e) { - if (e.code === 'MODULE_NOT_FOUND') { - logger.info('Configuration file `browserstack.json` is missing.'); - } else { - logger.info('Invalid configuration in `browserstack.json` file'); - logger.info(e.message); - logger.info(e.stack); - } - process.exit(1); -} - -var package_json = {}; -try { - package_json = require(process.cwd() + '/package.json'); -} catch (e) { -} - -if (process.env.BROWSERSTACK_KEY) { - config.key = process.env.BROWSERSTACK_KEY; -} - -if (process.env.BROWSERSTACK_ACCESS_KEY) { - config.key = process.env.BROWSERSTACK_ACCESS_KEY; -} - -if (process.env.BROWSERSTACK_USERNAME) { - config.username = process.env.BROWSERSTACK_USERNAME; -} - -if (!config.project) { - var fallback_project; - - if (config.username === 'OpensourceJSLib') { - fallback_project = 'Anonymous OpenSource Project'; - } - - config.project = process.env.TRAVIS_REPO_SLUG || package_json.name; -} - -var commit_id = process.env.TRAVIS_COMMIT; - -if(!config.build) { - config.build = commit_id ? 'Commit-' + commit_id.slice(0, commit_id.length / 2) : 'Local run, ' + new Date().toISOString(); -} - -['username', 'key', 'browsers', 'test_path'].forEach(function(param) { - if (typeof config[param] === 'undefined') { - console.error('Configuration parameter `%s` is required.', param); - process.exit(1); - } -}); - var formatPath = function(path) { if (/^win/.test(process.platform)) { path = path.replace(/\//g, '\\'); } - + if (path.indexOf(pwd) === 0) { path = path.slice(pwd.length + 1); } @@ -77,24 +19,85 @@ var formatPath = function(path) { return path; }; -config.tunnelIdentifier = process.env.TUNNEL_ID || process.env.TRAVIS_JOB_ID || process.env.TRAVIS_BUILD_ID; +exports.config = function(config_path) { + var config; + + config_path = path.resolve(path.relative(process.cwd(), config_path)); + logger.debug('Using config:', config_path); + + try { + config = require(config_path); + } catch (e) { + if (e.code === 'MODULE_NOT_FOUND') { + logger.info('Configuration file `browserstack.json` is missing.'); + } else { + logger.info('Invalid configuration in `browserstack.json` file'); + logger.info(e.message); + logger.info(e.stack); + } + process.exit(1); + } + + var package_json = {}; + try { + package_json = require(process.cwd() + '/package.json'); + } catch (e) { + } -if (typeof(config['test_server']) === 'undefined') { - if (Object.prototype.toString.call(config.test_path) === '[object Array]') { - config.test_path.forEach(function(path) { - path = formatPath(path); - }); + if (process.env.BROWSERSTACK_KEY) { + this.key = process.env.BROWSERSTACK_KEY; + } - } else { - //Backward Compatibility, if test_path is not array of path - config.test_path = formatPath(config.test_path); + if (process.env.BROWSERSTACK_ACCESS_KEY) { + this.key = process.env.BROWSERSTACK_ACCESS_KEY; + } + + if (process.env.BROWSERSTACK_USERNAME) { + this.username = process.env.BROWSERSTACK_USERNAME; + } + + if (!config.project) { + var fallback_project; + + if (this.username === 'OpensourceJSLib') { + fallback_project = 'Anonymous OpenSource Project'; + } + + this.project = process.env.TRAVIS_REPO_SLUG || package_json.name; + } + + var commit_id = process.env.TRAVIS_COMMIT; + + if(!config.build) { + this.build = commit_id ? 'Commit-' + commit_id.slice(0, commit_id.length / 2) : 'Local run, ' + new Date().toISOString(); + } + + ['username', 'key', 'browsers', 'test_path'].forEach(function(param) { + if (typeof config[param] === 'undefined' && typeof this[param] === 'undefined') { + console.error('Configuration parameter `%s` is required.', param); + process.exit(1); + } + }); + + this.tunnelIdentifier = process.env.TUNNEL_ID || process.env.TRAVIS_JOB_ID || process.env.TRAVIS_BUILD_ID; + + if (typeof(config['test_server']) === 'undefined') { + this.test_path = config.test_path; + if (Object.prototype.toString.call(this.test_path) === '[object Array]') { + this.test_path.forEach(function(path) { + path = formatPath(path); + }); + + } else { + //Backward Compatibility, if test_path is not array of path + this.test_path = formatPath(this.test_path); + } + delete config.test_path; } -} -config.status = 0; + this.status = 0; -for (var key in config) { - if (config.hasOwnProperty(key)) { - exports[key] = config[key]; + for (var key in config) { + this[key] = config[key]; } } diff --git a/lib/local.js b/lib/local.js index ea46d46..23b1382 100644 --- a/lib/local.js +++ b/lib/local.js @@ -5,10 +5,9 @@ var Log = require('./logger'), https = require('https'), windows = ((process.platform.match(/win32/) || process.platform.match(/win64/)) !== null), localBinary = __dirname + '/BrowserStackLocal' + (windows ? '.exe' : ''), - utils = require('./utils'), - config = require('./config'); + utils = require('./utils'); -var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { +var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { var that = {}; function tunnelLauncher() { @@ -36,7 +35,8 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, callback) { setTimeout(function() { if (!running) { - utils.alertBrowserStack('Tunnel launch timeout', 'Stdout:\n' + data); + logger.error('BrowserStackLocal failed to launch within 30 seconds.'); + process.exit(1); } }, 30 * 1000); diff --git a/lib/server.js b/lib/server.js index 88f2ca5..b7628ee 100644 --- a/lib/server.js +++ b/lib/server.js @@ -7,7 +7,6 @@ var Log = require('./logger'), fs = require('fs'), qs = require('querystring'), utils = require('./utils'), - config = require('../lib/config'), proxyServer = require('./proxy').proxyServer, chalk = require('chalk'), mime = require('mime'), @@ -15,7 +14,7 @@ var Log = require('./logger'), vm = require('vm'); -exports.Server = function Server(bsClient, workers) { +exports.Server = function Server(bsClient, workers, config) { var testFilePaths = (Array.isArray(config.test_path) ? config.test_path : [ config.test_path ]) .map(function (path) { diff --git a/lib/utils.js b/lib/utils.js index 779eae1..2e3bde8 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -50,83 +50,7 @@ var objectSize = function objectSize(obj) { return size; }; -var alertBrowserStack = function alertBrowserStack(subject, content, params, fn) { - if (!config) { - config = require('./config'); - } - - var endpoint = config.alert_endpoint || 'http://www.browserstack.com/automate/alert'; - var urlObject = url.parse(endpoint); - - var context = config.alert_context || 'Runner alert'; - logger.info('[%s] [%s] %s, %s', new Date().toISOString(), context, subject, content); - - if (typeof fn !== 'function') { - if (typeof params === 'function') { - } else { - fn = function() { - process.exit('SIGINT'); - }; - } - } - - if (!params || typeof(params) !== 'object') { - params = {}; - } - - params.subject = subject; - params.content = content; - params.context = context; - - var body = querystring.stringify(params); - var options = { - hostname: urlObject.hostname, - port: urlObject.port, - path: urlObject.path, - method: 'POST', - auth: config.username + ':' + config.key, - headers: { - 'Content-Length': body.length - } - }; - - var callback = function(res) { - var response = ''; - res.setEncoding('utf8'); - res.on('data', function(chunk) { - response += chunk; - }); - res.on('end', function() { - if (res.statusCode !== 200) { - var message; - if (res.headers['content-type'].indexOf('json') !== -1) { - var resp = JSON.parse(response); - message = resp.message; - message += ' - ' + resp.errors.map(function(err) { - return '`' + err.field + '`' + ' ' + err.code; - }).join(', '); - } else { - message = response; - } - if (!message && res.statusCode === 403) { - message = 'Forbidden'; - } - fn(new Error(message)); - } else { - fn(null, JSON.parse(response)); - } - }); - }; - - var request = http.request(options, callback); - request.write(body); - request.end(); - - return request; -}; - exports.titleCase = titleCase; exports.uuid = uuid; exports.browserString = browserString; exports.objectSize = objectSize; -exports.alertBrowserStack = alertBrowserStack; diff --git a/package.json b/package.json index 1032cb9..7d7c7c4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.4.4", + "version": "0.4.5", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", @@ -28,6 +28,7 @@ "bin": { "browserstack-runner": "bin/cli.js" }, + "main": "bin/runner.js", "scripts": { "lint": "node_modules/.bin/jshint lib/*.js bin/ tests/*.js", "test-unit": "node_modules/.bin/mocha tests/unit", From 7fa9b3713aab1f7e3d5e5999cd683a93f4a9c294 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Mon, 1 Aug 2016 20:17:13 +0530 Subject: [PATCH 093/162] lint errors --- bin/runner.js | 6 +----- lib/config.js | 8 ++++++-- lib/local.js | 3 +-- lib/utils.js | 7 ------- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/bin/runner.js b/bin/runner.js index a525217..f9d6d34 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -299,8 +299,6 @@ var statusPoller = { if (!worker.isAckd) { logger.trace('[%s] worker.activityTimeout', worker.id); - var subject = 'Worker inactive for too long: ' + worker.string; - var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); delete workers[workerData.key]; delete workerKeys[worker.id]; config.status += 1; @@ -327,8 +325,6 @@ var statusPoller = { if (worker.isAckd) { logger.trace('[%s] worker.testActivityTimeout', worker.id); - var subject = 'Tests timed out on: ' + worker.string; - var content = 'Worker details:\n' + JSON.stringify(worker.config, null, 4); delete workers[workerData.key]; delete workerKeys[worker.id]; config.status += 1; @@ -422,5 +418,5 @@ exports.test = function(config_file, callback) { } catch (e) { callback(e); } -} +}; exports.cleanUpAndExit = cleanUpAndExit; diff --git a/lib/config.js b/lib/config.js index c702d3a..3feaae6 100644 --- a/lib/config.js +++ b/lib/config.js @@ -46,14 +46,17 @@ exports.config = function(config_path) { if (process.env.BROWSERSTACK_KEY) { this.key = process.env.BROWSERSTACK_KEY; + delete config.key; } if (process.env.BROWSERSTACK_ACCESS_KEY) { this.key = process.env.BROWSERSTACK_ACCESS_KEY; + delete config.key; } if (process.env.BROWSERSTACK_USERNAME) { this.username = process.env.BROWSERSTACK_USERNAME; + delete config.username; } if (!config.project) { @@ -72,8 +75,9 @@ exports.config = function(config_path) { this.build = commit_id ? 'Commit-' + commit_id.slice(0, commit_id.length / 2) : 'Local run, ' + new Date().toISOString(); } + var that = this; ['username', 'key', 'browsers', 'test_path'].forEach(function(param) { - if (typeof config[param] === 'undefined' && typeof this[param] === 'undefined') { + if (typeof config[param] === 'undefined' && typeof that[param] === 'undefined') { console.error('Configuration parameter `%s` is required.', param); process.exit(1); } @@ -100,4 +104,4 @@ exports.config = function(config_path) { for (var key in config) { this[key] = config[key]; } -} +}; diff --git a/lib/local.js b/lib/local.js index 23b1382..b7f18f0 100644 --- a/lib/local.js +++ b/lib/local.js @@ -4,8 +4,7 @@ var Log = require('./logger'), fs = require('fs'), https = require('https'), windows = ((process.platform.match(/win32/) || process.platform.match(/win64/)) !== null), - localBinary = __dirname + '/BrowserStackLocal' + (windows ? '.exe' : ''), - utils = require('./utils'); + localBinary = __dirname + '/BrowserStackLocal' + (windows ? '.exe' : ''); var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { var that = {}; diff --git a/lib/utils.js b/lib/utils.js index 2e3bde8..dab1f82 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,10 +1,3 @@ -var Log = require('./logger'), - logger = new Log(global.logLevel), - http = require('http'), - url = require('url'), - querystring = require('querystring'), - config; - String.prototype.escapeSpecialChars = function() { return this.replace(/\n/g, '\\n') .replace(/\r/g, '\\r') From 327aabc80263aee558568a46b929dfffd497a845 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Tue, 2 Aug 2016 14:31:22 +0530 Subject: [PATCH 094/162] Use Callbacks --- bin/cli.js | 3 --- bin/runner.js | 51 ++++++++++++++++++++++++--------------------------- lib/server.js | 7 +++---- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 7740bba..aa7b2ae 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -24,6 +24,3 @@ runner.test(process.env.BROWSERSTACK_JSON || 'browserstack.json', function(err) console.log('Invalid Command'); } }); -process.on('exit', function(signal) { - runner.cleanUpAndExit(signal); -}); diff --git a/bin/runner.js b/bin/runner.js index f9d6d34..2209237 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -1,7 +1,6 @@ var Log = require('../lib/logger'), logger = new Log(global.logLevel), BrowserStack = require('browserstack'), - fs = require('fs'), qs = require('querystring'), chalk = require('chalk'), utils = require('../lib/utils'), @@ -17,11 +16,9 @@ var Log = require('../lib/logger'), activityTimeout, ackTimeout, client, - pid_file = process.cwd() + '/browserstack-run.pid', workers = {}, workerKeys = {}, - tunnelingAgent, - tunnel; + tunnelingAgent; function terminateAllWorkers(callback) { logger.trace('terminateAllWorkers'); @@ -65,7 +62,8 @@ function terminateAllWorkers(callback) { } } -function cleanUpAndExit(signal) { +function cleanUpAndExit(signal, callback) { + callback = callback || function() {}; logger.trace('cleanUpAndExit: signal: %s', signal); try { @@ -79,23 +77,18 @@ function cleanUpAndExit(signal) { } try { - process.kill(tunnel.process.pid, 'SIGKILL'); + process.kill(tunnel.process.pid, 'SIGTERM'); } catch (e) { logger.debug('Non existent tunnel'); } - try { - fs.unlinkSync(pid_file); - } catch (e) { - logger.debug('Non existent pid file.'); - } if (signal === 'SIGTERM') { logger.debug('Exiting'); - process.exit(config.status); + callback(null, config.status); } else { terminateAllWorkers(function() { logger.debug('Exiting'); - process.exit(1); + callback(null, 1); }); } } @@ -123,11 +116,11 @@ function buildTestUrl(test_path, worker_key, browser_string) { return url; } -function launchServer(config) { +function launchServer(config, callback) { logger.trace('launchServer:', serverPort); logger.debug('Launching server on port:', serverPort); - server = new Server(client, workers, config); + server = new Server(client, workers, config, callback); server.listen(parseInt(serverPort, 10)); } @@ -265,7 +258,7 @@ function attachWorkerHelpers(worker) { var statusPoller = { poller: null, - start: function() { + start: function(callback) { logger.trace('statusPoller.start'); statusPoller.poller = setInterval(function () { @@ -311,7 +304,7 @@ var statusPoller = { } logger.trace('[%s] worker.activityTimeout: all tests done', worker.id, config.status && 'with failures'); - process.exit('SIGTERM'); + callback(null, 'All Tests Done'); } } else { logger.trace('[%s] worker.activityTimeout: already ackd', worker.id); @@ -337,7 +330,7 @@ var statusPoller = { } logger.trace('[%s] worker.testActivityTimeout: all tests done', worker.id, config.status && 'with failures'); - process.exit('SIGTERM'); + callback(null, 'All Tests Done'); } } else { logger.trace('[%s] worker.testActivityTimeout: not ackd', worker.id); @@ -355,7 +348,7 @@ var statusPoller = { } }; -function runTests(config) { +function runTests(config, callback) { if (config.proxy) { logger.trace('runTests: with proxy', config.proxy); @@ -363,20 +356,20 @@ function runTests(config) { proxy: config.proxy }); var oldhttpreq = http.request; - http.request = function (options, callback) { + http.request = function (options, reqCallback) { options.agent = tunnelingAgent; - return oldhttpreq.call(null, options, callback); + return oldhttpreq.call(null, options, reqCallback); }; } if (config.browsers && config.browsers.length > 0) { ConfigParser.parse(client, config.browsers, function(browsers){ - launchServer(config); + launchServer(config, callback); logger.trace('runTests: creating tunnel'); tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, config, function () { logger.trace('runTests: created tunnel'); - statusPoller.start(); + statusPoller.start(callback); var total_runs = config.browsers.length * (Array.isArray(config.test_path) ? config.test_path.length : 1); logger.info('Launching ' + config.browsers.length + ' worker(s) for ' + total_runs + ' run(s).'); browsers.forEach(function(browser) { @@ -399,7 +392,7 @@ function runTests(config) { }); }); } else { - launchServer(config); + launchServer(config, callback); } } @@ -413,10 +406,14 @@ exports.test = function(config_file, callback) { username: config.username, password: config.key }); - runTests(config); - fs.writeFileSync(pid_file, process.pid, 'utf-8'); + runTests(config, function(error) { + if(error) { + callback(error); + } else { + cleanUpAndExit('SIGTERM', callback); + } + }); } catch (e) { callback(e); } }; -exports.cleanUpAndExit = cleanUpAndExit; diff --git a/lib/server.js b/lib/server.js index b7628ee..3a22162 100644 --- a/lib/server.js +++ b/lib/server.js @@ -13,9 +13,7 @@ var Log = require('./logger'), send = require('send'), vm = require('vm'); - -exports.Server = function Server(bsClient, workers, config) { - +exports.Server = function Server(bsClient, workers, config, callback) { var testFilePaths = (Array.isArray(config.test_path) ? config.test_path : [ config.test_path ]) .map(function (path) { return path.split(/[?#]/)[0]; @@ -314,7 +312,8 @@ exports.Server = function Server(bsClient, workers, config) { } logger.trace('[%s] _report: checkAndTerminateWorker: all tests done', worker.id, config.status && 'with failures'); - process.exit('SIGTERM'); + callback(null, 'All Tests Done'); + //process.exit('SIGTERM'); } }); }); From 3f53a2bd162d31fb68f46ac275bc5e173b6f860c Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Tue, 2 Aug 2016 14:47:51 +0530 Subject: [PATCH 095/162] rename files --- bin/cli.js | 431 ++++++++++++++++++++++++++++++++++++++-- bin/runner.js | 431 ++-------------------------------------- package.json | 4 +- tests/external-tests.js | 2 +- 4 files changed, 434 insertions(+), 434 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index aa7b2ae..2209237 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,26 +1,419 @@ -#! /usr/bin/env node +var Log = require('../lib/logger'), + logger = new Log(global.logLevel), + BrowserStack = require('browserstack'), + qs = require('querystring'), + chalk = require('chalk'), + utils = require('../lib/utils'), + Server = require('../lib/server').Server, + Tunnel = require('../lib/local').Tunnel, + tunnel = require('tunnel'), + http = require('http'), + ConfigParser = require('../lib/configParser').ConfigParser, + serverPort = 8888, + config, + server, + timeout, + activityTimeout, + ackTimeout, + client, + workers = {}, + workerKeys = {}, + tunnelingAgent; -var todo = process.argv[2]; +function terminateAllWorkers(callback) { + logger.trace('terminateAllWorkers'); -if (todo === '--verbose') { - global.logLevel = process.env.LOG_LEVEL || 'debug'; -} else { - global.logLevel = 'info'; + var cleanWorker = function(id, key) { + logger.trace('cleanWorker(%s, %s)', id, key); + + client.terminateWorker(id, function() { + var worker = workers[key]; + if(worker) { + logger.debug('[%s] Terminated', worker.string); + clearTimeout(worker.ackTimeout); + clearTimeout(worker.activityTimeout); + clearTimeout(worker.testActivityTimeout); + delete workers[key]; + delete workerKeys[worker.id]; + } + if (utils.objectSize(workers) === 0) { + logger.trace('terminateAllWorkers: done'); + callback(); + } + }); + }; + + if (utils.objectSize(workers) === 0) { + logger.trace('terminateAllWorkers: done'); + callback(); + } else { + for (var key in workers){ + var worker = workers[key]; + if (worker.id) { + cleanWorker(worker.id, key); + } else { + delete workers[key]; + if (utils.objectSize(workers) === 0) { + logger.trace('terminateAllWorkers: done'); + callback(); + } + } + } + } +} + +function cleanUpAndExit(signal, callback) { + callback = callback || function() {}; + logger.trace('cleanUpAndExit: signal: %s', signal); + + try { + server.close(); + } catch (e) { + logger.debug('Server already closed'); + } + + if (statusPoller) { + statusPoller.stop(); + } + + try { + process.kill(tunnel.process.pid, 'SIGTERM'); + } catch (e) { + logger.debug('Non existent tunnel'); + } + + if (signal === 'SIGTERM') { + logger.debug('Exiting'); + callback(null, config.status); + } else { + terminateAllWorkers(function() { + logger.debug('Exiting'); + callback(null, 1); + }); + } +} + +function getTestBrowserInfo(browserString, path) { + var info = browserString; + if (config.multipleTest) { + info += ', ' + path; + } + + logger.trace('getTestBrowserInfo(%s, %s): %s', browserString, path, info); + return info; +} + +function buildTestUrl(test_path, worker_key, browser_string) { + var url = 'http://localhost:' + serverPort + '/' + test_path; + + var querystring = qs.stringify({ + _worker_key: worker_key, + _browser_string: browser_string + }); + + url += ((url.indexOf('?') > 0) ? '&' : '?') + querystring; + logger.trace('buildTestUrl:', url); + return url; +} + +function launchServer(config, callback) { + logger.trace('launchServer:', serverPort); + logger.debug('Launching server on port:', serverPort); + + server = new Server(client, workers, config, callback); + server.listen(parseInt(serverPort, 10)); +} + +function launchBrowser(browser, path) { + var key = utils.uuid(); + var browserString = utils.browserString(browser); + var browserInfo = getTestBrowserInfo(browserString, path); + logger.debug('[%s] Launching', browserInfo); + + browser.url = buildTestUrl(path.replace(/\\/g, '/'), key, browserString); + + if (config.project) { + browser.project = config.project; + } + if (config.build) { + browser.build = config.build; + } + + if(config.tunnelIdentifier) { + browser['tunnel_identifier'] = config.tunnelIdentifier; + } + + timeout = parseInt(config.timeout); + if (!isNaN(timeout)) { + browser.timeout = timeout; + } else { + timeout = 300; + } + activityTimeout = timeout - 10; + ackTimeout = parseInt(config.ackTimeout) || 60; + + logger.trace('[%s] client.createWorker', browserInfo, browser); + + client.createWorker(browser, function (err, worker) { + logger.trace('[%s] client.createWorker | response:', browserInfo, worker, err); + + if (err || typeof worker !== 'object') { + logger.info('Error from BrowserStack: ', err); + return; + } + + worker.config = browser; + worker.string = browserString; + worker.test_path = path; + worker.path_index = 0; + + // attach helper methods to manage worker state + attachWorkerHelpers(worker); + + workers[key] = worker; + workerKeys[worker.id] = {key: key, marked: false}; + }); +} + +function launchBrowsers(config, browser) { + setTimeout(function () { + logger.trace('launchBrowsers', browser); + + if (Array.isArray(config.test_path)){ + config.multipleTest = config.test_path.length > 1? true : false; + launchBrowser(browser, config.test_path[0]); + } else { + config.multipleTest = false; + launchBrowser(browser, config.test_path); + } + }, 100); } -if (todo === 'init') { - require('./init.js'); - return; -} else if (todo === '--version') { - require('./version.js'); - return; +function attachWorkerHelpers(worker) { + // TODO: Consider creating instances of a proper 'Worker' class + + worker.buildUrl = function buildUrl(test_path) { + var workerKey = workerKeys[this.id] ? workerKeys[this.id].key : null; + var url = buildTestUrl(test_path || this.test_path, workerKey, this.getTestBrowserInfo()); + logger.trace('[%s] worker.buildUrl: %s', this.id, url); + return url; + }; + + worker.getTestBrowserInfo = function getTestBrowserInfo(test_path) { + var info = this.string; + if (config.multipleTest) { + info += ', ' + (test_path || this.test_path); + } + return info; + }; + + worker.awaitAck = function awaitAck() { + var self = this; + + if (this.ackTimeout) { + logger.trace('[%s] worker.awaitAck: already awaiting ack, or awaited ack once and failed', self.id); + return; + } + + logger.trace('[%s] worker.awaitAck: timeout in %d secs', self.id, ackTimeout); + + this.ackTimeout = setTimeout(function () { + if (self.isAckd) { + logger.trace('[%s] worker.awaitAck: already ackd', self.id); + return; + } + + var url = self.buildUrl(); + logger.trace('[%s] worker.awaitAck: client.changeUrl: %s', self.id, url); + + client.changeUrl(self.id, { url: url }, function (err, data) { + logger.trace('[%s] worker.awaitAck: client.changeUrl: %s | response:', self.id, url, data, err); + logger.debug('[%s] Sent Request to reload url', self.getTestBrowserInfo()); + }); + + }, ackTimeout * 1000); + + logger.debug('[%s] Awaiting ack', this.getTestBrowserInfo()); + }; + + worker.markAckd = function markAckd() { + this.resetAck(); + this.isAckd = true; + + logger.trace('[%s] worker.markAckd', this.id); + logger.debug('[%s] Received ack', this.getTestBrowserInfo()); + }; + + worker.resetAck = function resetAck() { + logger.trace('[%s] worker.resetAck', this.id); + + clearTimeout(this.ackTimeout); + this.ackTimeout = null; + this.isAckd = false; + }; + + return worker; } -var runner = require('./runner.js'); -runner.test(process.env.BROWSERSTACK_JSON || 'browserstack.json', function(err) { - if(err) { - console.log(err); - console.log(err.stack); - console.log('Invalid Command'); +var statusPoller = { + poller: null, + + start: function(callback) { + logger.trace('statusPoller.start'); + + statusPoller.poller = setInterval(function () { + client.getWorkers(function (err, _workers) { + logger.trace('client.getWorkers | response: worker count: %d', (_workers || []).length, err); + + if (!_workers) { + logger.info(chalk.red('Error found: ' + err)); + return; + } + _workers.filter(function(currentValue) { + return currentValue.status === 'running' && workerKeys[currentValue.id] && !workerKeys[currentValue.id].marked; + }).forEach(function(_worker) { + var workerData = workerKeys[_worker.id]; + var worker = workers[workerData.key]; + if (worker.launched) { + return; + } + + if (_worker.status === 'running') { + //clearInterval(statusPoller); + logger.debug('[%s] Launched', worker.getTestBrowserInfo()); + worker.launched = true; + workerData.marked = true; + + // Await ack from browser-worker + worker.awaitAck(); + logger.trace('[%s] worker.activityTimeout: timeout in %d secs', worker.id, activityTimeout); + + worker.activityTimeout = setTimeout(function () { + if (!worker.isAckd) { + logger.trace('[%s] worker.activityTimeout', worker.id); + + delete workers[workerData.key]; + delete workerKeys[worker.id]; + config.status += 1; + if (utils.objectSize(workers) === 0) { + var color = config.status > 0 ? 'red' : 'green'; + logger.info(chalk[color]('All tests done, failures: %d.'), config.status); + + if (config.status > 0) { + config.status = 1; + } + + logger.trace('[%s] worker.activityTimeout: all tests done', worker.id, config.status && 'with failures'); + callback(null, 'All Tests Done'); + } + } else { + logger.trace('[%s] worker.activityTimeout: already ackd', worker.id); + } + }, activityTimeout * 1000); + + + logger.trace('[%s] worker.testActivityTimeout: timeout in %d secs', worker.id, activityTimeout); + + worker.testActivityTimeout = setTimeout(function () { + if (worker.isAckd) { + logger.trace('[%s] worker.testActivityTimeout', worker.id); + + delete workers[workerData.key]; + delete workerKeys[worker.id]; + config.status += 1; + if (utils.objectSize(workers) === 0) { + var color = config.status > 0 ? 'red' : 'green'; + logger.info(chalk[color]('All tests done, failures: %d.'), config.status); + + if (config.status > 0) { + config.status = 1; + } + + logger.trace('[%s] worker.testActivityTimeout: all tests done', worker.id, config.status && 'with failures'); + callback(null, 'All Tests Done'); + } + } else { + logger.trace('[%s] worker.testActivityTimeout: not ackd', worker.id); + } + }, (activityTimeout * 1000)); + } + }); + }); + }, 2000); + }, + + stop: function() { + logger.trace('statusPoller.poller'); + clearInterval(statusPoller.poller); + } +}; + +function runTests(config, callback) { + if (config.proxy) { + logger.trace('runTests: with proxy', config.proxy); + + tunnelingAgent = tunnel.httpOverHttp({ + proxy: config.proxy + }); + var oldhttpreq = http.request; + http.request = function (options, reqCallback) { + options.agent = tunnelingAgent; + return oldhttpreq.call(null, options, reqCallback); + }; + } + if (config.browsers && config.browsers.length > 0) { + ConfigParser.parse(client, config.browsers, function(browsers){ + launchServer(config, callback); + + logger.trace('runTests: creating tunnel'); + tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, config, function () { + logger.trace('runTests: created tunnel'); + + statusPoller.start(callback); + var total_runs = config.browsers.length * (Array.isArray(config.test_path) ? config.test_path.length : 1); + logger.info('Launching ' + config.browsers.length + ' worker(s) for ' + total_runs + ' run(s).'); + browsers.forEach(function(browser) { + if (browser.browser_version === 'latest') { + logger.debug('[%s] Finding version.', utils.browserString(browser)); + logger.trace('runTests: client.getLatest'); + + client.getLatest(browser, function(err, version) { + logger.trace('runTests: client.getLatest | response:', version, err); + logger.debug('[%s] Version is %s.', + utils.browserString(browser), version); + browser.browser_version = version; + // So that all latest logs come in together + launchBrowsers(config, browser); + }); + } else { + launchBrowsers(config, browser); + } + }); + }); + }); + } else { + launchServer(config, callback); + } +} + +exports.test = function(config_file, callback) { + callback = callback || function() {}; + + try { + config = new (require('../lib/config').config)(config_file); + + client = BrowserStack.createClient({ + username: config.username, + password: config.key + }); + runTests(config, function(error) { + if(error) { + callback(error); + } else { + cleanUpAndExit('SIGTERM', callback); + } + }); + } catch (e) { + callback(e); } -}); +}; diff --git a/bin/runner.js b/bin/runner.js index 2209237..a2d076c 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -1,419 +1,26 @@ -var Log = require('../lib/logger'), - logger = new Log(global.logLevel), - BrowserStack = require('browserstack'), - qs = require('querystring'), - chalk = require('chalk'), - utils = require('../lib/utils'), - Server = require('../lib/server').Server, - Tunnel = require('../lib/local').Tunnel, - tunnel = require('tunnel'), - http = require('http'), - ConfigParser = require('../lib/configParser').ConfigParser, - serverPort = 8888, - config, - server, - timeout, - activityTimeout, - ackTimeout, - client, - workers = {}, - workerKeys = {}, - tunnelingAgent; +#! /usr/bin/env node -function terminateAllWorkers(callback) { - logger.trace('terminateAllWorkers'); +var todo = process.argv[2]; - var cleanWorker = function(id, key) { - logger.trace('cleanWorker(%s, %s)', id, key); - - client.terminateWorker(id, function() { - var worker = workers[key]; - if(worker) { - logger.debug('[%s] Terminated', worker.string); - clearTimeout(worker.ackTimeout); - clearTimeout(worker.activityTimeout); - clearTimeout(worker.testActivityTimeout); - delete workers[key]; - delete workerKeys[worker.id]; - } - if (utils.objectSize(workers) === 0) { - logger.trace('terminateAllWorkers: done'); - callback(); - } - }); - }; - - if (utils.objectSize(workers) === 0) { - logger.trace('terminateAllWorkers: done'); - callback(); - } else { - for (var key in workers){ - var worker = workers[key]; - if (worker.id) { - cleanWorker(worker.id, key); - } else { - delete workers[key]; - if (utils.objectSize(workers) === 0) { - logger.trace('terminateAllWorkers: done'); - callback(); - } - } - } - } -} - -function cleanUpAndExit(signal, callback) { - callback = callback || function() {}; - logger.trace('cleanUpAndExit: signal: %s', signal); - - try { - server.close(); - } catch (e) { - logger.debug('Server already closed'); - } - - if (statusPoller) { - statusPoller.stop(); - } - - try { - process.kill(tunnel.process.pid, 'SIGTERM'); - } catch (e) { - logger.debug('Non existent tunnel'); - } - - if (signal === 'SIGTERM') { - logger.debug('Exiting'); - callback(null, config.status); - } else { - terminateAllWorkers(function() { - logger.debug('Exiting'); - callback(null, 1); - }); - } -} - -function getTestBrowserInfo(browserString, path) { - var info = browserString; - if (config.multipleTest) { - info += ', ' + path; - } - - logger.trace('getTestBrowserInfo(%s, %s): %s', browserString, path, info); - return info; -} - -function buildTestUrl(test_path, worker_key, browser_string) { - var url = 'http://localhost:' + serverPort + '/' + test_path; - - var querystring = qs.stringify({ - _worker_key: worker_key, - _browser_string: browser_string - }); - - url += ((url.indexOf('?') > 0) ? '&' : '?') + querystring; - logger.trace('buildTestUrl:', url); - return url; -} - -function launchServer(config, callback) { - logger.trace('launchServer:', serverPort); - logger.debug('Launching server on port:', serverPort); - - server = new Server(client, workers, config, callback); - server.listen(parseInt(serverPort, 10)); -} - -function launchBrowser(browser, path) { - var key = utils.uuid(); - var browserString = utils.browserString(browser); - var browserInfo = getTestBrowserInfo(browserString, path); - logger.debug('[%s] Launching', browserInfo); - - browser.url = buildTestUrl(path.replace(/\\/g, '/'), key, browserString); - - if (config.project) { - browser.project = config.project; - } - if (config.build) { - browser.build = config.build; - } - - if(config.tunnelIdentifier) { - browser['tunnel_identifier'] = config.tunnelIdentifier; - } - - timeout = parseInt(config.timeout); - if (!isNaN(timeout)) { - browser.timeout = timeout; - } else { - timeout = 300; - } - activityTimeout = timeout - 10; - ackTimeout = parseInt(config.ackTimeout) || 60; - - logger.trace('[%s] client.createWorker', browserInfo, browser); - - client.createWorker(browser, function (err, worker) { - logger.trace('[%s] client.createWorker | response:', browserInfo, worker, err); - - if (err || typeof worker !== 'object') { - logger.info('Error from BrowserStack: ', err); - return; - } - - worker.config = browser; - worker.string = browserString; - worker.test_path = path; - worker.path_index = 0; - - // attach helper methods to manage worker state - attachWorkerHelpers(worker); - - workers[key] = worker; - workerKeys[worker.id] = {key: key, marked: false}; - }); -} - -function launchBrowsers(config, browser) { - setTimeout(function () { - logger.trace('launchBrowsers', browser); - - if (Array.isArray(config.test_path)){ - config.multipleTest = config.test_path.length > 1? true : false; - launchBrowser(browser, config.test_path[0]); - } else { - config.multipleTest = false; - launchBrowser(browser, config.test_path); - } - }, 100); +if (todo === '--verbose') { + global.logLevel = process.env.LOG_LEVEL || 'debug'; +} else { + global.logLevel = 'info'; } -function attachWorkerHelpers(worker) { - // TODO: Consider creating instances of a proper 'Worker' class - - worker.buildUrl = function buildUrl(test_path) { - var workerKey = workerKeys[this.id] ? workerKeys[this.id].key : null; - var url = buildTestUrl(test_path || this.test_path, workerKey, this.getTestBrowserInfo()); - logger.trace('[%s] worker.buildUrl: %s', this.id, url); - return url; - }; - - worker.getTestBrowserInfo = function getTestBrowserInfo(test_path) { - var info = this.string; - if (config.multipleTest) { - info += ', ' + (test_path || this.test_path); - } - return info; - }; - - worker.awaitAck = function awaitAck() { - var self = this; - - if (this.ackTimeout) { - logger.trace('[%s] worker.awaitAck: already awaiting ack, or awaited ack once and failed', self.id); - return; - } - - logger.trace('[%s] worker.awaitAck: timeout in %d secs', self.id, ackTimeout); - - this.ackTimeout = setTimeout(function () { - if (self.isAckd) { - logger.trace('[%s] worker.awaitAck: already ackd', self.id); - return; - } - - var url = self.buildUrl(); - logger.trace('[%s] worker.awaitAck: client.changeUrl: %s', self.id, url); - - client.changeUrl(self.id, { url: url }, function (err, data) { - logger.trace('[%s] worker.awaitAck: client.changeUrl: %s | response:', self.id, url, data, err); - logger.debug('[%s] Sent Request to reload url', self.getTestBrowserInfo()); - }); - - }, ackTimeout * 1000); - - logger.debug('[%s] Awaiting ack', this.getTestBrowserInfo()); - }; - - worker.markAckd = function markAckd() { - this.resetAck(); - this.isAckd = true; - - logger.trace('[%s] worker.markAckd', this.id); - logger.debug('[%s] Received ack', this.getTestBrowserInfo()); - }; - - worker.resetAck = function resetAck() { - logger.trace('[%s] worker.resetAck', this.id); - - clearTimeout(this.ackTimeout); - this.ackTimeout = null; - this.isAckd = false; - }; - - return worker; +if (todo === 'init') { + require('./init.js'); + return; +} else if (todo === '--version') { + require('./version.js'); + return; } -var statusPoller = { - poller: null, - - start: function(callback) { - logger.trace('statusPoller.start'); - - statusPoller.poller = setInterval(function () { - client.getWorkers(function (err, _workers) { - logger.trace('client.getWorkers | response: worker count: %d', (_workers || []).length, err); - - if (!_workers) { - logger.info(chalk.red('Error found: ' + err)); - return; - } - _workers.filter(function(currentValue) { - return currentValue.status === 'running' && workerKeys[currentValue.id] && !workerKeys[currentValue.id].marked; - }).forEach(function(_worker) { - var workerData = workerKeys[_worker.id]; - var worker = workers[workerData.key]; - if (worker.launched) { - return; - } - - if (_worker.status === 'running') { - //clearInterval(statusPoller); - logger.debug('[%s] Launched', worker.getTestBrowserInfo()); - worker.launched = true; - workerData.marked = true; - - // Await ack from browser-worker - worker.awaitAck(); - logger.trace('[%s] worker.activityTimeout: timeout in %d secs', worker.id, activityTimeout); - - worker.activityTimeout = setTimeout(function () { - if (!worker.isAckd) { - logger.trace('[%s] worker.activityTimeout', worker.id); - - delete workers[workerData.key]; - delete workerKeys[worker.id]; - config.status += 1; - if (utils.objectSize(workers) === 0) { - var color = config.status > 0 ? 'red' : 'green'; - logger.info(chalk[color]('All tests done, failures: %d.'), config.status); - - if (config.status > 0) { - config.status = 1; - } - - logger.trace('[%s] worker.activityTimeout: all tests done', worker.id, config.status && 'with failures'); - callback(null, 'All Tests Done'); - } - } else { - logger.trace('[%s] worker.activityTimeout: already ackd', worker.id); - } - }, activityTimeout * 1000); - - - logger.trace('[%s] worker.testActivityTimeout: timeout in %d secs', worker.id, activityTimeout); - - worker.testActivityTimeout = setTimeout(function () { - if (worker.isAckd) { - logger.trace('[%s] worker.testActivityTimeout', worker.id); - - delete workers[workerData.key]; - delete workerKeys[worker.id]; - config.status += 1; - if (utils.objectSize(workers) === 0) { - var color = config.status > 0 ? 'red' : 'green'; - logger.info(chalk[color]('All tests done, failures: %d.'), config.status); - - if (config.status > 0) { - config.status = 1; - } - - logger.trace('[%s] worker.testActivityTimeout: all tests done', worker.id, config.status && 'with failures'); - callback(null, 'All Tests Done'); - } - } else { - logger.trace('[%s] worker.testActivityTimeout: not ackd', worker.id); - } - }, (activityTimeout * 1000)); - } - }); - }); - }, 2000); - }, - - stop: function() { - logger.trace('statusPoller.poller'); - clearInterval(statusPoller.poller); - } -}; - -function runTests(config, callback) { - if (config.proxy) { - logger.trace('runTests: with proxy', config.proxy); - - tunnelingAgent = tunnel.httpOverHttp({ - proxy: config.proxy - }); - var oldhttpreq = http.request; - http.request = function (options, reqCallback) { - options.agent = tunnelingAgent; - return oldhttpreq.call(null, options, reqCallback); - }; - } - if (config.browsers && config.browsers.length > 0) { - ConfigParser.parse(client, config.browsers, function(browsers){ - launchServer(config, callback); - - logger.trace('runTests: creating tunnel'); - tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, config, function () { - logger.trace('runTests: created tunnel'); - - statusPoller.start(callback); - var total_runs = config.browsers.length * (Array.isArray(config.test_path) ? config.test_path.length : 1); - logger.info('Launching ' + config.browsers.length + ' worker(s) for ' + total_runs + ' run(s).'); - browsers.forEach(function(browser) { - if (browser.browser_version === 'latest') { - logger.debug('[%s] Finding version.', utils.browserString(browser)); - logger.trace('runTests: client.getLatest'); - - client.getLatest(browser, function(err, version) { - logger.trace('runTests: client.getLatest | response:', version, err); - logger.debug('[%s] Version is %s.', - utils.browserString(browser), version); - browser.browser_version = version; - // So that all latest logs come in together - launchBrowsers(config, browser); - }); - } else { - launchBrowsers(config, browser); - } - }); - }); - }); - } else { - launchServer(config, callback); - } -} - -exports.test = function(config_file, callback) { - callback = callback || function() {}; - - try { - config = new (require('../lib/config').config)(config_file); - - client = BrowserStack.createClient({ - username: config.username, - password: config.key - }); - runTests(config, function(error) { - if(error) { - callback(error); - } else { - cleanUpAndExit('SIGTERM', callback); - } - }); - } catch (e) { - callback(e); +var runner = require('./cli.js'); +runner.test(process.env.BROWSERSTACK_JSON || 'browserstack.json', function(err) { + if(err) { + console.log(err); + console.log(err.stack); + console.log('Invalid Command'); } -}; +}); diff --git a/package.json b/package.json index 7d7c7c4..b3030b7 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,9 @@ } ], "bin": { - "browserstack-runner": "bin/cli.js" + "browserstack-runner": "bin/runner.js" }, - "main": "bin/runner.js", + "main": "bin/cli.js", "scripts": { "lint": "node_modules/.bin/jshint lib/*.js bin/ tests/*.js", "test-unit": "node_modules/.bin/mocha tests/unit", diff --git a/tests/external-tests.js b/tests/external-tests.js index e879f3f..92ed62a 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -9,7 +9,7 @@ var browserstackConfig = { }; var mode = (process.env.TEST_MODE || 'all').toLowerCase(); -var runnerPath = path.resolve(path.join(__dirname, '..', 'bin', 'cli.js')); +var runnerPath = path.resolve(path.join(__dirname, '..', 'bin', 'runner.js')); var testHome = path.resolve(__dirname); process.chdir(testHome); From bd42cdaf122861c5fb3a5a131035a10c5b55dd21 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Tue, 2 Aug 2016 15:33:14 +0530 Subject: [PATCH 096/162] Avoid process.exit, throw and catch errors --- lib/config.js | 7 ++++--- lib/configParser.js | 2 +- lib/local.js | 6 +++--- lib/server.js | 1 - package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/config.js b/lib/config.js index 3feaae6..7ae1c0a 100644 --- a/lib/config.js +++ b/lib/config.js @@ -14,7 +14,7 @@ var formatPath = function(path) { } if (!fs.existsSync(path) && !fs.existsSync(path.split('?')[0])) { console.error('Test path: ' + path + ' is invalid.'); - process.exit(1); + throw new Error('Test path: ' + path + ' is invalid.'); } return path; }; @@ -30,12 +30,13 @@ exports.config = function(config_path) { } catch (e) { if (e.code === 'MODULE_NOT_FOUND') { logger.info('Configuration file `browserstack.json` is missing.'); + throw new Error('Configuration file `browserstack.json` is missing.'); } else { logger.info('Invalid configuration in `browserstack.json` file'); logger.info(e.message); logger.info(e.stack); + throw new Error('Invalid configuration in `browserstack.json` file'); } - process.exit(1); } var package_json = {}; @@ -79,7 +80,7 @@ exports.config = function(config_path) { ['username', 'key', 'browsers', 'test_path'].forEach(function(param) { if (typeof config[param] === 'undefined' && typeof that[param] === 'undefined') { console.error('Configuration parameter `%s` is required.', param); - process.exit(1); + throw new Error('Configuration parameter `%s` is required.', param); } }); diff --git a/lib/configParser.js b/lib/configParser.js index d1554ca..b98c7be 100644 --- a/lib/configParser.js +++ b/lib/configParser.js @@ -13,7 +13,7 @@ var ConfigParser = { if(error) { logger.info('Error getting browsers list from BrowserStack'); logger.info(error); - process.exit(1); + throw new Error('Error getting browsers list from BrowserStack'); } ConfigParser.bsBrowsers = browsers; for (var key in browser_config) { diff --git a/lib/local.js b/lib/local.js index b7f18f0..27b8be4 100644 --- a/lib/local.js +++ b/lib/local.js @@ -24,7 +24,7 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { if (stdout.indexOf('Error') >= 0 || error) { logger.debug('[%s] Tunnel launching failed', new Date()); logger.debug(stdout); - process.exit('SIGINT'); + throw new Error('[%s] Tunnel launching failed', new Date()); } }); @@ -35,7 +35,7 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { setTimeout(function() { if (!running) { logger.error('BrowserStackLocal failed to launch within 30 seconds.'); - process.exit(1); + throw new Error('BrowserStackLocal failed to launch within 30 seconds.'); } }, 30 * 1000); @@ -181,7 +181,7 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { }, 100); }).on('error', function(e) { logger.info('Got error while downloading binary: ' + e.message); - process.exit('SIGINT'); + throw new Error('Got error while downloading binary: ' + e.message); }); }); }); diff --git a/lib/server.js b/lib/server.js index 3a22162..f4301c5 100644 --- a/lib/server.js +++ b/lib/server.js @@ -313,7 +313,6 @@ exports.Server = function Server(bsClient, workers, config, callback) { logger.trace('[%s] _report: checkAndTerminateWorker: all tests done', worker.id, config.status && 'with failures'); callback(null, 'All Tests Done'); - //process.exit('SIGTERM'); } }); }); diff --git a/package.json b/package.json index b3030b7..0fb7ca5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.4.5", + "version": "0.4.4", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 0542ff3274680d4ac1b92a91f00378b6f7616099 Mon Sep 17 00:00:00 2001 From: Vibhaj Rajan Date: Fri, 5 Aug 2016 01:05:04 +0530 Subject: [PATCH 097/162] Add testing steps in README Would be nice wiki for Issue #130 --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index e67a717..618e41d 100644 --- a/README.md +++ b/README.md @@ -170,3 +170,27 @@ These can also be provided by a build server, for example [using secure environm Check out code sample [here]. [here]:https://github.com/browserstack/browserstack-runner-sample + +### Tests + +Testing involves pulling sample repos with test cases and running tests against them. +``` +git submodule update --init --recursive +npm test +``` + +Sample `browserstack.json`: +``` +{ + "username": "BROWSERSTACK_USERNAME", + "key": "BROWSERSTACK_KEY", + "test_path": "tests/external-repos//test/test.html", + "debug": true, + "browsers": [ + "chrome_latest", + "firefox_latest", + "ie_11", + ... + ] +} +``` From 2ce4493148f55f16ec8f946fa031faf9ff4cc676 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Fri, 5 Aug 2016 16:54:26 +0530 Subject: [PATCH 098/162] Added basic tests --- bin/cli.js | 17 ++- bin/runner.js | 32 +++- lib/config.js | 30 +--- lib/configParser.js | 2 +- lib/local.js | 2 +- lib/logger.js | 2 +- lib/server.js | 28 ++-- package.json | 8 +- tests/behaviour/resources/even.js | 3 + tests/behaviour/resources/odd.js | 3 + tests/behaviour/resources/sample.html | 15 ++ tests/behaviour/resources/sample_failing.html | 16 ++ tests/behaviour/resources/test.js | 7 + tests/behaviour/resources/test_failing.js | 13 ++ tests/behaviour/runner.js | 138 ++++++++++++++++++ 15 files changed, 261 insertions(+), 55 deletions(-) create mode 100644 tests/behaviour/resources/even.js create mode 100644 tests/behaviour/resources/odd.js create mode 100644 tests/behaviour/resources/sample.html create mode 100644 tests/behaviour/resources/sample_failing.html create mode 100644 tests/behaviour/resources/test.js create mode 100644 tests/behaviour/resources/test_failing.js create mode 100644 tests/behaviour/runner.js diff --git a/bin/cli.js b/bin/cli.js index 2209237..ddb30c1 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,5 +1,5 @@ var Log = require('../lib/logger'), - logger = new Log(global.logLevel), + logger = new Log(global.logLevel || 'info'), BrowserStack = require('browserstack'), qs = require('querystring'), chalk = require('chalk'), @@ -62,8 +62,9 @@ function terminateAllWorkers(callback) { } } -function cleanUpAndExit(signal, callback) { +function cleanUpAndExit(signal, report, callback) { callback = callback || function() {}; + report = report || {}; logger.trace('cleanUpAndExit: signal: %s', signal); try { @@ -84,11 +85,11 @@ function cleanUpAndExit(signal, callback) { if (signal === 'SIGTERM') { logger.debug('Exiting'); - callback(null, config.status); + callback(null, report); } else { terminateAllWorkers(function() { logger.debug('Exiting'); - callback(null, 1); + callback(null, report); }); } } @@ -396,21 +397,21 @@ function runTests(config, callback) { } } -exports.test = function(config_file, callback) { +exports.run = function(user_config, callback) { callback = callback || function() {}; try { - config = new (require('../lib/config').config)(config_file); + config = new (require('../lib/config').config)(user_config); client = BrowserStack.createClient({ username: config.username, password: config.key }); - runTests(config, function(error) { + runTests(config, function(error, report) { if(error) { callback(error); } else { - cleanUpAndExit('SIGTERM', callback); + cleanUpAndExit('SIGTERM', report, callback); } }); } catch (e) { diff --git a/bin/runner.js b/bin/runner.js index a2d076c..0deb8f1 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -1,6 +1,8 @@ #! /usr/bin/env node -var todo = process.argv[2]; +var todo = process.argv[2], + path = require('path'), + config; if (todo === '--verbose') { global.logLevel = process.env.LOG_LEVEL || 'debug'; @@ -16,11 +18,31 @@ if (todo === 'init') { return; } +var config_path = process.env.BROWSERSTACK_JSON || 'browserstack.json'; +config_path = path.resolve(path.relative(process.cwd(), config_path)); + +console.log('Using config:', config_path); +try { + config = require(config_path); +} catch (e) { + if (e.code === 'MODULE_NOT_FOUND') { + console.err('Configuration file `browserstack.json` is missing.'); + throw new Error('Configuration file `browserstack.json` is missing.'); + } else { + console.err('Invalid configuration in `browserstack.json` file'); + console.err(e.message); + console.err(e.stack); + throw new Error('Invalid configuration in `browserstack.json` file'); + } +} + var runner = require('./cli.js'); -runner.test(process.env.BROWSERSTACK_JSON || 'browserstack.json', function(err) { +runner.run(config, function(err) { if(err) { - console.log(err); - console.log(err.stack); - console.log('Invalid Command'); + console.err(err); + console.err(err.stack); + console.err('Invalid Command'); + process.exit(1); } + process.exit(0); }); diff --git a/lib/config.js b/lib/config.js index 7ae1c0a..6345b8e 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,7 +1,4 @@ -var Log = require('./logger'), - logger = new Log(global.logLevel), - path = require('path'), - fs = require('fs'), +var fs = require('fs'), pwd = process.cwd(); var formatPath = function(path) { @@ -13,32 +10,12 @@ var formatPath = function(path) { path = path.slice(pwd.length + 1); } if (!fs.existsSync(path) && !fs.existsSync(path.split('?')[0])) { - console.error('Test path: ' + path + ' is invalid.'); throw new Error('Test path: ' + path + ' is invalid.'); } return path; }; -exports.config = function(config_path) { - var config; - - config_path = path.resolve(path.relative(process.cwd(), config_path)); - logger.debug('Using config:', config_path); - - try { - config = require(config_path); - } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') { - logger.info('Configuration file `browserstack.json` is missing.'); - throw new Error('Configuration file `browserstack.json` is missing.'); - } else { - logger.info('Invalid configuration in `browserstack.json` file'); - logger.info(e.message); - logger.info(e.stack); - throw new Error('Invalid configuration in `browserstack.json` file'); - } - } - +exports.config = function(config) { var package_json = {}; try { package_json = require(process.cwd() + '/package.json'); @@ -79,8 +56,7 @@ exports.config = function(config_path) { var that = this; ['username', 'key', 'browsers', 'test_path'].forEach(function(param) { if (typeof config[param] === 'undefined' && typeof that[param] === 'undefined') { - console.error('Configuration parameter `%s` is required.', param); - throw new Error('Configuration parameter `%s` is required.', param); + throw new Error('Configuration parameter ' + param + ' is required.'); } }); diff --git a/lib/configParser.js b/lib/configParser.js index b98c7be..6afdb1b 100644 --- a/lib/configParser.js +++ b/lib/configParser.js @@ -1,7 +1,7 @@ //beta browsers not handled //+ not handled var Log = require('./logger'), - logger = new Log(global.logLevel); + logger = new Log(global.logLevel || 'info'); var ConfigParser = { finalBrowsers: [], diff --git a/lib/local.js b/lib/local.js index 27b8be4..1c26bf7 100644 --- a/lib/local.js +++ b/lib/local.js @@ -1,5 +1,5 @@ var Log = require('./logger'), - logger = new Log(global.logLevel), + logger = new Log(global.logLevel || 'info'), exec = require('child_process').execFile, fs = require('fs'), https = require('https'), diff --git a/lib/logger.js b/lib/logger.js index ca9c2e0..e387ab2 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,5 +1,5 @@ var fmt = require('util').format; -var logLevels = {ERROR: 3, INFO: 6, DEBUG: 7, TRACE: 8}; +var logLevels = { SILENT: 0, ERROR: 3, INFO: 6, DEBUG: 7, TRACE: 8 }; function Log(level){ if ('string' === typeof level) { diff --git a/lib/server.js b/lib/server.js index f4301c5..9987ebb 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,5 +1,5 @@ var Log = require('./logger'), - logger = new Log(global.logLevel), + logger = new Log(global.logLevel || 'info'), http = require('http'), url = require('url'), path = require('path'), @@ -11,7 +11,8 @@ var Log = require('./logger'), chalk = require('chalk'), mime = require('mime'), send = require('send'), - vm = require('vm'); + vm = require('vm'), + report = {}; exports.Server = function Server(bsClient, workers, config, callback) { var testFilePaths = (Array.isArray(config.test_path) ? config.test_path : [ config.test_path ]) @@ -222,6 +223,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { } var worker = workers[uuid]; + var browserInfo = worker.getTestBrowserInfo(); var query = null; try { @@ -231,11 +233,15 @@ exports.Server = function Server(bsClient, workers, config, callback) { logger.info('[%s] Log: ' + qs.parse(body).data, worker.string); } + + report[browserInfo] = report[browserInfo] || { assertions: [], tests: [] }; + logger.trace('[%s] _progress', worker.id, query); if (query && query.tracebacks) { query.tracebacks.forEach(function(traceback) { - logger.info('[%s] ' + chalk.red('Error:'), worker.getTestBrowserInfo(), formatTraceback(traceback)); + report[browserInfo]['assertions'].push(traceback); + logger.info('[%s] ' + chalk.red('Error:'), browserInfo, formatTraceback(traceback)); }); } response.end(); @@ -250,6 +256,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { var worker = workers[uuid]; worker._worker_key = uuid; + var browserInfo = worker.getTestBrowserInfo(); var query = null; try { @@ -261,14 +268,17 @@ exports.Server = function Server(bsClient, workers, config, callback) { if (query === null) { logger.info('[%s] Null response from remote Browser', request.headers['x-browser-string']); } else { + report[browserInfo] = report[browserInfo] || { assertions: [], tests: [] }; + report[browserInfo]['tests'].push(query); + if (query.tracebacks && query.tracebacks.length > 0) { - logger.info('[%s] ' + chalk['red']('Tracebacks:'), worker.getTestBrowserInfo()); + logger.info('[%s] ' + chalk['red']('Tracebacks:'), browserInfo); query.tracebacks.forEach(function(traceback) { logger.info(traceback); }); } var color = query.failed ? 'red' : 'green'; - logger.info('[%s] ' + chalk[color](query.failed ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed; ran for %dms', worker.getTestBrowserInfo(), query.total, query.passed, query.failed, query.runtime); + logger.info('[%s] ' + chalk[color](query.failed ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed; ran for %dms', browserInfo, query.total, query.passed, query.failed, query.runtime); config.status += query.failed; } @@ -278,7 +288,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { logger.trace('[%s] _report: client.takeScreenshot | response:', worker.id, screenshot, error); if (!error && screenshot.url && query && query.failed) { - logger.info('[%s] ' + chalk.yellow('Screenshot:') + ' %s', worker.getTestBrowserInfo(), screenshot.url); + logger.info('[%s] ' + chalk.yellow('Screenshot:') + ' %s', browserInfo, screenshot.url); } checkAndTerminateWorker(worker, function(reusedWorker) { @@ -289,14 +299,14 @@ exports.Server = function Server(bsClient, workers, config, callback) { if (reusedWorker) { logger.trace('[%s] _report: checkAndTerminateWorker: reused worker', worker.id); - logger.debug('[%s] Reused', worker.getTestBrowserInfo()); + logger.debug('[%s] Reused', browserInfo); worker.resetAck(); worker.awaitAck(); return; } logger.trace('[%s] _report: checkAndTerminateWorker: terminated', worker.id); - logger.debug('[%s] Terminated', worker.getTestBrowserInfo()); + logger.debug('[%s] Terminated', browserInfo); clearTimeout(workers[uuid].ackTimeout); clearTimeout(workers[uuid].activityTimeout); @@ -312,7 +322,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { } logger.trace('[%s] _report: checkAndTerminateWorker: all tests done', worker.id, config.status && 'with failures'); - callback(null, 'All Tests Done'); + callback(null, JSON.stringify(report)); } }); }); diff --git a/package.json b/package.json index 0fb7ca5..f38797f 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ }, "devDependencies": { "jshint": "2.5.6", - "mocha": "1.15.1" + "mocha": "1.15.1", + "sinon": "^1.17.5" }, "licenses": [ { @@ -32,8 +33,9 @@ "scripts": { "lint": "node_modules/.bin/jshint lib/*.js bin/ tests/*.js", "test-unit": "node_modules/.bin/mocha tests/unit", - "test-ci": "npm run lint && npm run test-unit && TEST_MODE=all tests/external-tests.js", - "test": "npm run lint && npm run test-unit && TEST_MODE=required tests/external-tests.js", + "test-behaviour": "node_modules/.bin/mocha tests/behaviour -R Spec", + "test-ci": "npm run lint && npm run test-unit && npm run test-behaviour && TEST_MODE=all tests/external-tests.js", + "test": "npm run lint && npm run test-unit && npm run test-behaviour && TEST_MODE=required tests/external-tests.js", "update-util": "webpack" } } diff --git a/tests/behaviour/resources/even.js b/tests/behaviour/resources/even.js new file mode 100644 index 0000000..adc9d50 --- /dev/null +++ b/tests/behaviour/resources/even.js @@ -0,0 +1,3 @@ +function isEven(val) { + return val % 2 === 0; +} diff --git a/tests/behaviour/resources/odd.js b/tests/behaviour/resources/odd.js new file mode 100644 index 0000000..3875785 --- /dev/null +++ b/tests/behaviour/resources/odd.js @@ -0,0 +1,3 @@ +function isOdd(val) { + return val % 2 === 1; +} diff --git a/tests/behaviour/resources/sample.html b/tests/behaviour/resources/sample.html new file mode 100644 index 0000000..9408aeb --- /dev/null +++ b/tests/behaviour/resources/sample.html @@ -0,0 +1,15 @@ + + + + +QUnit Example + + + + +
+
+ + + + diff --git a/tests/behaviour/resources/sample_failing.html b/tests/behaviour/resources/sample_failing.html new file mode 100644 index 0000000..969262f --- /dev/null +++ b/tests/behaviour/resources/sample_failing.html @@ -0,0 +1,16 @@ + + + + +QUnit Example + + + + +
+
+ + + + + diff --git a/tests/behaviour/resources/test.js b/tests/behaviour/resources/test.js new file mode 100644 index 0000000..6fbea02 --- /dev/null +++ b/tests/behaviour/resources/test.js @@ -0,0 +1,7 @@ +QUnit.module('Odd Tests'); + +QUnit.test('isOdd()', function(assert) { + assert.ok(isOdd(1), 'One is an odd number'); + assert.ok(isOdd(3), 'Three is an odd number'); + assert.ok(!isOdd(0), 'Zero is not odd number'); +}); diff --git a/tests/behaviour/resources/test_failing.js b/tests/behaviour/resources/test_failing.js new file mode 100644 index 0000000..8f8762f --- /dev/null +++ b/tests/behaviour/resources/test_failing.js @@ -0,0 +1,13 @@ +QUnit.module('Odd Tests'); + +QUnit.test('isOdd()', function(assert) { + assert.ok(isOdd(2), '2 is an odd number'); + assert.ok(isOdd(222), '222 is an odd number'); + assert.ok(!isOdd(133), '133 is not an odd number'); +}); + +QUnit.test('isEven()', function(assert) { + assert.ok(isEven(3), '2 is an odd number'); + assert.ok(isEven(221), '222 is an odd number'); + assert.ok(!isEven(132), '133 is not an odd number'); +}); diff --git a/tests/behaviour/runner.js b/tests/behaviour/runner.js new file mode 100644 index 0000000..d286e14 --- /dev/null +++ b/tests/behaviour/runner.js @@ -0,0 +1,138 @@ +'use strict'; + +global.logLevel = "silent"; + +let assert = require('assert'), + sinon = require('sinon'), + path = require('path'), + browserstackRunner = require('../../bin/cli.js'); + +let getBaseConfig = function() { + return { + username: 'BROWSERSTACK_USER', + key: 'BROWSERSTACK_KEY', + test_framework: 'qunit', + test_path: path.resolve(__dirname, 'resources', 'sample.html'), + build: "BrowserStack Runner Behaviour Tests", + browsers: [ { + browser: 'firefox', + browser_version: 'latest', + os: 'Windows', + os_version: '7' + }, { + browser: 'chrome', + browser_version: 'latest', + os: 'Windows', + os_version: '7' + } ] + } +}; + +describe('Config Assertions', function() { + this.timeout(0); + + it('should run normally with valid config', function(done) { + browserstackRunner.run(getBaseConfig(), function(err) { + assert.equal(err, null); + done(); + }); + }); + it('should have an error if test path is not valid', function(done) { + let config = getBaseConfig(); + config.test_path = "Some invalid path"; + browserstackRunner.run(config, function(err) { + assert.equal(err.message, "Test path: " + config.test_path + " is invalid."); + done(); + }); + }); + it('should have an error if config does not have a browsers key', function(done) { + let config = getBaseConfig(); + delete(config.browsers); + browserstackRunner.run(config, function(err) { + assert.equal(err.message, "Configuration parameter browsers is required."); + done(); + }); + }); + it('should have an error if config does not have a test_path key', function(done) { + let config = getBaseConfig(); + delete(config.test_path); + browserstackRunner.run(config, function(err) { + assert.equal(err.message, "Configuration parameter test_path is required."); + done(); + }); + }); + describe('Check Behaviour with invalid username or key', function() { + var sandbox; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + sandbox.stub(process, 'env', {}); + }); + + it('should have an error if config does not have a username', function(done) { + let config = getBaseConfig(); + delete(config.username); + browserstackRunner.run(config, function(err) { + assert.equal(err.message, "Configuration parameter username is required."); + done(); + }); + }); + it('should have an error if config does not have a key', function(done) { + let config = getBaseConfig(); + delete(config.key); + browserstackRunner.run(config, function(err) { + assert.equal(err.message, "Configuration parameter key is required."); + done(); + }); + }); + + afterEach(function() { + sandbox.restore(); + }); + }); +}); + +describe('Pass/Fail reporting', function() { + this.timeout(0); + + it('report keys should have browser names', function(done) { + let config = getBaseConfig(); + config.test_path = path.resolve(__dirname, 'resources', 'sample_failing.html'); + browserstackRunner.run(config, function(err, report) { + assert.equal(err, null); + var parsedReport = JSON.parse(report); + assert.notEqual(parsedReport["Windows 7, Chrome 52.0"], null); + assert.notEqual(parsedReport["Windows 7, Firefox 47.0"], null); + done(); + }); + }); + it('report keys should have assertions and tests', function(done) { + let config = getBaseConfig(); + config.test_path = path.resolve(__dirname, 'resources', 'sample_failing.html'); + browserstackRunner.run(config, function(err, report) { + assert.equal(err, null); + var parsedReport = JSON.parse(report); + assert.notEqual(parsedReport["Windows 7, Chrome 52.0"].assertions, null); + assert.notEqual(parsedReport["Windows 7, Chrome 52.0"].tests, null); + assert.notEqual(parsedReport["Windows 7, Firefox 47.0"].assertions, null); + assert.notEqual(parsedReport["Windows 7, Firefox 47.0"].tests, null); + done(); + }); + }); + describe('Test Assertions', function() { + it('report should have proper assertions for tests', function(done) { + let config = getBaseConfig(); + config.test_path = path.resolve(__dirname, 'resources', 'sample_failing.html'); + browserstackRunner.run(config, function(err, report) { + assert.equal(err, null); + var parsedReport = JSON.parse(report); + //assert.equal(parsedReport["Windows 7, Chrome 52.0"].assertions.length, 6); + //assert.equal(parsedReport["Windows 7, Firefox 47.0"].assertions.length, 6); + //console.log("REPORT!! " + JSON.stringify( parsedReport["Windows 7, Chrome 52.0"] )); + done(); + }); + }); + }); + describe('Test tests', function() { + }); +}); From e822ed99f8fb9a0c3233465031ed8e0fb5371f6c Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Sat, 6 Aug 2016 01:08:23 +0530 Subject: [PATCH 099/162] restructured tests --- bin/cli.js | 69 ++++++++------- lib/local.js | 2 +- ...{sample_failing.html => qunit_sample.html} | 3 +- tests/behaviour/resources/qunit_test1.js | 13 +++ tests/behaviour/resources/qunit_test2.js | 19 ++++ tests/behaviour/resources/sample.html | 15 ---- tests/behaviour/resources/test.js | 7 -- tests/behaviour/resources/test_failing.js | 13 --- tests/behaviour/runner.js | 88 ++++++++++++++++--- 9 files changed, 150 insertions(+), 79 deletions(-) rename tests/behaviour/resources/{sample_failing.html => qunit_sample.html} (82%) create mode 100644 tests/behaviour/resources/qunit_test1.js create mode 100644 tests/behaviour/resources/qunit_test2.js delete mode 100644 tests/behaviour/resources/sample.html delete mode 100644 tests/behaviour/resources/test.js delete mode 100644 tests/behaviour/resources/test_failing.js diff --git a/bin/cli.js b/bin/cli.js index ddb30c1..ebf1df3 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -62,7 +62,8 @@ function terminateAllWorkers(callback) { } } -function cleanUpAndExit(signal, report, callback) { +function cleanUpAndExit(signal, error, report, callback) { + ConfigParser.finalBrowsers = []; callback = callback || function() {}; report = report || {}; logger.trace('cleanUpAndExit: signal: %s', signal); @@ -85,11 +86,11 @@ function cleanUpAndExit(signal, report, callback) { if (signal === 'SIGTERM') { logger.debug('Exiting'); - callback(null, report); + callback(error, report); } else { terminateAllWorkers(function() { logger.debug('Exiting'); - callback(null, report); + callback(error, report); }); } } @@ -350,6 +351,10 @@ var statusPoller = { }; function runTests(config, callback) { + var runTestsCallback = function(error, report) { + ConfigParser.finalBrowsers = []; + callback(error, report); + }; if (config.proxy) { logger.trace('runTests: with proxy', config.proxy); @@ -364,32 +369,36 @@ function runTests(config, callback) { } if (config.browsers && config.browsers.length > 0) { ConfigParser.parse(client, config.browsers, function(browsers){ - launchServer(config, callback); + launchServer(config, runTestsCallback); logger.trace('runTests: creating tunnel'); - tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, config, function () { - logger.trace('runTests: created tunnel'); - - statusPoller.start(callback); - var total_runs = config.browsers.length * (Array.isArray(config.test_path) ? config.test_path.length : 1); - logger.info('Launching ' + config.browsers.length + ' worker(s) for ' + total_runs + ' run(s).'); - browsers.forEach(function(browser) { - if (browser.browser_version === 'latest') { - logger.debug('[%s] Finding version.', utils.browserString(browser)); - logger.trace('runTests: client.getLatest'); - - client.getLatest(browser, function(err, version) { - logger.trace('runTests: client.getLatest | response:', version, err); - logger.debug('[%s] Version is %s.', - utils.browserString(browser), version); - browser.browser_version = version; - // So that all latest logs come in together - launchBrowsers(config, browser); - }); - } else { - launchBrowsers(config, browser); - } - }); + tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, config, function (err) { + if(err) { + cleanUpAndExit(null, err, '{}', callback); + } else { + logger.trace('runTests: created tunnel'); + + statusPoller.start(runTestsCallback); + var total_runs = config.browsers.length * (Array.isArray(config.test_path) ? config.test_path.length : 1); + logger.info('Launching ' + config.browsers.length + ' worker(s) for ' + total_runs + ' run(s).'); + browsers.forEach(function(browser) { + if (browser.browser_version === 'latest') { + logger.debug('[%s] Finding version.', utils.browserString(browser)); + logger.trace('runTests: client.getLatest'); + + client.getLatest(browser, function(err, version) { + logger.trace('runTests: client.getLatest | response:', version, err); + logger.debug('[%s] Version is %s.', + utils.browserString(browser), version); + browser.browser_version = version; + // So that all latest logs come in together + launchBrowsers(config, browser); + }); + } else { + launchBrowsers(config, browser); + } + }); + } }); }); } else { @@ -408,11 +417,7 @@ exports.run = function(user_config, callback) { password: config.key }); runTests(config, function(error, report) { - if(error) { - callback(error); - } else { - cleanUpAndExit('SIGTERM', report, callback); - } + cleanUpAndExit('SIGTERM', error, report, callback); }); } catch (e) { callback(e); diff --git a/lib/local.js b/lib/local.js index 1c26bf7..b1b4dd9 100644 --- a/lib/local.js +++ b/lib/local.js @@ -35,7 +35,7 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { setTimeout(function() { if (!running) { logger.error('BrowserStackLocal failed to launch within 30 seconds.'); - throw new Error('BrowserStackLocal failed to launch within 30 seconds.'); + callback(new Error('BrowserStackLocal failed to launch within 30 seconds.')); } }, 30 * 1000); diff --git a/tests/behaviour/resources/sample_failing.html b/tests/behaviour/resources/qunit_sample.html similarity index 82% rename from tests/behaviour/resources/sample_failing.html rename to tests/behaviour/resources/qunit_sample.html index 969262f..5e1fce6 100644 --- a/tests/behaviour/resources/sample_failing.html +++ b/tests/behaviour/resources/qunit_sample.html @@ -11,6 +11,7 @@
- + + diff --git a/tests/behaviour/resources/qunit_test1.js b/tests/behaviour/resources/qunit_test1.js new file mode 100644 index 0000000..3f14542 --- /dev/null +++ b/tests/behaviour/resources/qunit_test1.js @@ -0,0 +1,13 @@ +QUnit.module('Partial Tests', function() { + QUnit.test('Partial Tests', function(assert) { + // Fails + assert.ok(isOdd(2), '2 is an odd number'); + assert.ok(isEven(5), '5 is an even number'); + + // Passes + assert.ok(isOdd(3), '3 is an odd number'); + assert.ok(!isOdd(4), '4 is not odd number'); + assert.ok(isEven(6), '6 is an even number'); + assert.ok(!isEven(7), '7 is not an even number'); + }); +}); diff --git a/tests/behaviour/resources/qunit_test2.js b/tests/behaviour/resources/qunit_test2.js new file mode 100644 index 0000000..e9a6e6e --- /dev/null +++ b/tests/behaviour/resources/qunit_test2.js @@ -0,0 +1,19 @@ +QUnit.module('All Pass/Fail tests', function() { + QUnit.test('All Pass', function(assert) { + assert.ok(isOdd(13), '13 is an odd number'); + assert.ok(isOdd(15), '15 is an odd number'); + assert.ok(!isOdd(12), '12 is not an odd number'); + assert.ok(isEven(14), '14 is an even number'); + assert.ok(isEven(16), '16 is an even number'); + assert.ok(!isEven(17), '17 is not an even number'); + }); + + QUnit.test('All Fail', function(assert) { + assert.ok(isOdd(22), '22 is an odd number'); + assert.ok(isOdd(24), '24 is an odd number'); + assert.ok(!isOdd(21), '21 is not an odd number'); + assert.ok(isEven(23), '23 is an even number'); + assert.ok(isEven(25), '25 is an even number'); + assert.ok(!isEven(26), '26 is not an even number'); + }); +}); diff --git a/tests/behaviour/resources/sample.html b/tests/behaviour/resources/sample.html deleted file mode 100644 index 9408aeb..0000000 --- a/tests/behaviour/resources/sample.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - -QUnit Example - - - - -
-
- - - - diff --git a/tests/behaviour/resources/test.js b/tests/behaviour/resources/test.js deleted file mode 100644 index 6fbea02..0000000 --- a/tests/behaviour/resources/test.js +++ /dev/null @@ -1,7 +0,0 @@ -QUnit.module('Odd Tests'); - -QUnit.test('isOdd()', function(assert) { - assert.ok(isOdd(1), 'One is an odd number'); - assert.ok(isOdd(3), 'Three is an odd number'); - assert.ok(!isOdd(0), 'Zero is not odd number'); -}); diff --git a/tests/behaviour/resources/test_failing.js b/tests/behaviour/resources/test_failing.js deleted file mode 100644 index 8f8762f..0000000 --- a/tests/behaviour/resources/test_failing.js +++ /dev/null @@ -1,13 +0,0 @@ -QUnit.module('Odd Tests'); - -QUnit.test('isOdd()', function(assert) { - assert.ok(isOdd(2), '2 is an odd number'); - assert.ok(isOdd(222), '222 is an odd number'); - assert.ok(!isOdd(133), '133 is not an odd number'); -}); - -QUnit.test('isEven()', function(assert) { - assert.ok(isEven(3), '2 is an odd number'); - assert.ok(isEven(221), '222 is an odd number'); - assert.ok(!isEven(132), '133 is not an odd number'); -}); diff --git a/tests/behaviour/runner.js b/tests/behaviour/runner.js index d286e14..cd277a3 100644 --- a/tests/behaviour/runner.js +++ b/tests/behaviour/runner.js @@ -12,16 +12,16 @@ let getBaseConfig = function() { username: 'BROWSERSTACK_USER', key: 'BROWSERSTACK_KEY', test_framework: 'qunit', - test_path: path.resolve(__dirname, 'resources', 'sample.html'), + test_path: path.resolve(__dirname, 'resources', 'qunit_sample.html'), build: "BrowserStack Runner Behaviour Tests", browsers: [ { browser: 'firefox', - browser_version: 'latest', + browser_version: '47.0', os: 'Windows', os_version: '7' }, { browser: 'chrome', - browser_version: 'latest', + browser_version: '52.0', os: 'Windows', os_version: '7' } ] @@ -97,7 +97,6 @@ describe('Pass/Fail reporting', function() { it('report keys should have browser names', function(done) { let config = getBaseConfig(); - config.test_path = path.resolve(__dirname, 'resources', 'sample_failing.html'); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); var parsedReport = JSON.parse(report); @@ -108,7 +107,6 @@ describe('Pass/Fail reporting', function() { }); it('report keys should have assertions and tests', function(done) { let config = getBaseConfig(); - config.test_path = path.resolve(__dirname, 'resources', 'sample_failing.html'); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); var parsedReport = JSON.parse(report); @@ -120,19 +118,89 @@ describe('Pass/Fail reporting', function() { }); }); describe('Test Assertions', function() { - it('report should have proper assertions for tests', function(done) { + it('report should have proper number of assertions for tests', function(done) { let config = getBaseConfig(); - config.test_path = path.resolve(__dirname, 'resources', 'sample_failing.html'); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); + console.log(report); var parsedReport = JSON.parse(report); - //assert.equal(parsedReport["Windows 7, Chrome 52.0"].assertions.length, 6); - //assert.equal(parsedReport["Windows 7, Firefox 47.0"].assertions.length, 6); - //console.log("REPORT!! " + JSON.stringify( parsedReport["Windows 7, Chrome 52.0"] )); + // Only failed assertions are emitted + assert.equal(parsedReport["Windows 7, Chrome 52.0"].assertions.length, 8); + assert.equal(parsedReport["Windows 7, Firefox 47.0"].assertions.length, 8); + done(); + }); + }); + it('report should have specific keys', function(done) { + let config = getBaseConfig(); + console.log(JSON.stringify(config)); + browserstackRunner.run(config, function(err, report) { + assert.equal(err, null); + var parsedReport = JSON.parse(report); + Object.keys(parsedReport).forEach(function(reportKey) { + parsedReport[reportKey].assertions.forEach(function(assertion) { + [ "actual", "expected", "message", "source" ].forEach(function(key) { + assert.notEqual(assertion[key], null); + }); + }); + }); + done(); + }); + }); + it('report should have message in assertions', function(done) { + let config = getBaseConfig(); + console.log(JSON.stringify(config)); + browserstackRunner.run(config, function(err, report) { + assert.equal(err, null); + var parsedReport = JSON.parse(report); + Object.keys(parsedReport).forEach(function(reportKey) { + parsedReport[reportKey].assertions.forEach(function(assertion) { + assert.notEqual(assertion["message"].match(/\d+ is .*an .* number/), null); + }); + }); done(); }); }); }); describe('Test tests', function() { + it('report should have proper number of tests', function(done) { + let config = getBaseConfig(); + browserstackRunner.run(config, function(err, report) { + assert.equal(err, null); + var parsedReport = JSON.parse(report); + assert.equal(parsedReport["Windows 7, Chrome 52.0"].tests.length, 1); + assert.equal(parsedReport["Windows 7, Firefox 47.0"].tests.length, 1); + done(); + }); + }); + it('report should have specific keys', function(done) { + let config = getBaseConfig(); + browserstackRunner.run(config, function(err, report) { + assert.equal(err, null); + var parsedReport = JSON.parse(report); + Object.keys(parsedReport).forEach(function(reportKey) { + parsedReport[reportKey].tests.forEach(function(test) { + [ "runtime", "total", "passed", "failed", "url" ].forEach(function(key) { + assert.notEqual(test[key], null); + }); + }); + }); + done(); + }); + }); + it('report should have message in assertions', function(done) { + let config = getBaseConfig(); + browserstackRunner.run(config, function(err, report) { + assert.equal(err, null); + var parsedReport = JSON.parse(report); + Object.keys(parsedReport).forEach(function(reportKey) { + parsedReport[reportKey].tests.forEach(function(test) { + assert.equal(test["total"], 3); + assert.equal(test["passed"], 1); + assert.equal(test["failed"], 2); + }); + }); + done(); + }); + }); }); }); From 1ce5fc511ba70c912516bcd36139b747802735ce Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Sat, 6 Aug 2016 08:20:28 +0530 Subject: [PATCH 100/162] assertions count issue --- lib/server.js | 6 +++--- package.json | 2 +- tests/behaviour/runner.js | 3 --- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/server.js b/lib/server.js index 9987ebb..18de194 100644 --- a/lib/server.js +++ b/lib/server.js @@ -11,14 +11,14 @@ var Log = require('./logger'), chalk = require('chalk'), mime = require('mime'), send = require('send'), - vm = require('vm'), - report = {}; + vm = require('vm'); exports.Server = function Server(bsClient, workers, config, callback) { var testFilePaths = (Array.isArray(config.test_path) ? config.test_path : [ config.test_path ]) .map(function (path) { return path.split(/[?#]/)[0]; - }); + }), + report = {}; function handleFile(filename, request, response, doNotUseProxy) { var url_parts = url.parse(request.url, true); diff --git a/package.json b/package.json index f38797f..fe69166 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "scripts": { "lint": "node_modules/.bin/jshint lib/*.js bin/ tests/*.js", "test-unit": "node_modules/.bin/mocha tests/unit", - "test-behaviour": "node_modules/.bin/mocha tests/behaviour -R Spec", + "test-behaviour": "node_modules/.bin/mocha tests/behaviour", "test-ci": "npm run lint && npm run test-unit && npm run test-behaviour && TEST_MODE=all tests/external-tests.js", "test": "npm run lint && npm run test-unit && npm run test-behaviour && TEST_MODE=required tests/external-tests.js", "update-util": "webpack" diff --git a/tests/behaviour/runner.js b/tests/behaviour/runner.js index cd277a3..a922607 100644 --- a/tests/behaviour/runner.js +++ b/tests/behaviour/runner.js @@ -122,7 +122,6 @@ describe('Pass/Fail reporting', function() { let config = getBaseConfig(); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); - console.log(report); var parsedReport = JSON.parse(report); // Only failed assertions are emitted assert.equal(parsedReport["Windows 7, Chrome 52.0"].assertions.length, 8); @@ -132,7 +131,6 @@ describe('Pass/Fail reporting', function() { }); it('report should have specific keys', function(done) { let config = getBaseConfig(); - console.log(JSON.stringify(config)); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); var parsedReport = JSON.parse(report); @@ -148,7 +146,6 @@ describe('Pass/Fail reporting', function() { }); it('report should have message in assertions', function(done) { let config = getBaseConfig(); - console.log(JSON.stringify(config)); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); var parsedReport = JSON.parse(report); From f74129d1ec71402fdc589c6c290733e2dfb4a120 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Mon, 8 Aug 2016 11:40:54 +0530 Subject: [PATCH 101/162] return object instead of stringified JSON --- .gitignore | 4 ++ bin/cli.js | 14 +++-- lib/local.js | 6 +-- lib/server.js | 2 +- package.json | 2 +- tests/behaviour/runner.js | 106 ++++++++++++++++++-------------------- 6 files changed, 70 insertions(+), 64 deletions(-) diff --git a/.gitignore b/.gitignore index bffa194..35e66d1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ node_modules browserstack.json browserstack-runner.pid lib/BrowserStackLocal +tests/jasmine +tests/jasmine2 +tests/mocha +tests/qunit diff --git a/bin/cli.js b/bin/cli.js index ebf1df3..498c922 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -306,7 +306,11 @@ var statusPoller = { } logger.trace('[%s] worker.activityTimeout: all tests done', worker.id, config.status && 'with failures'); - callback(null, 'All Tests Done'); + if(server && server.report) { + callback(null, server.report); + } else { + callback(null, {}); + } } } else { logger.trace('[%s] worker.activityTimeout: already ackd', worker.id); @@ -332,7 +336,11 @@ var statusPoller = { } logger.trace('[%s] worker.testActivityTimeout: all tests done', worker.id, config.status && 'with failures'); - callback(null, 'All Tests Done'); + if(server && server.report) { + callback(null, server.report); + } else { + callback(null, {}); + } } } else { logger.trace('[%s] worker.testActivityTimeout: not ackd', worker.id); @@ -374,7 +382,7 @@ function runTests(config, callback) { logger.trace('runTests: creating tunnel'); tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, config, function (err) { if(err) { - cleanUpAndExit(null, err, '{}', callback); + cleanUpAndExit(null, err, {}, callback); } else { logger.trace('runTests: created tunnel'); diff --git a/lib/local.js b/lib/local.js index b1b4dd9..ef46884 100644 --- a/lib/local.js +++ b/lib/local.js @@ -24,13 +24,13 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { if (stdout.indexOf('Error') >= 0 || error) { logger.debug('[%s] Tunnel launching failed', new Date()); logger.debug(stdout); - throw new Error('[%s] Tunnel launching failed', new Date()); + callback(new Error(new Date() + ': Tunnel launching failed')); } }); var data = ''; var running = false; - var runMatcher = 'You can now access your local server(s)'; + var runMatchers = [ 'You can now access your local server(s)', 'Press Ctrl-C to exit' ]; setTimeout(function() { if (!running) { @@ -46,7 +46,7 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { data += _data; - if (data.indexOf(runMatcher) >= 0) { + if (data.indexOf(runMatchers[0]) >= 0 && data.indexOf(runMatchers[1]) >= 0) { running = true; logger.debug('[%s] Tunnel launched', new Date()); setTimeout(function(){ diff --git a/lib/server.js b/lib/server.js index 18de194..a8c0178 100644 --- a/lib/server.js +++ b/lib/server.js @@ -322,7 +322,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { } logger.trace('[%s] _report: checkAndTerminateWorker: all tests done', worker.id, config.status && 'with failures'); - callback(null, JSON.stringify(report)); + callback(null, report); } }); }); diff --git a/package.json b/package.json index fe69166..3fd741b 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "scripts": { "lint": "node_modules/.bin/jshint lib/*.js bin/ tests/*.js", "test-unit": "node_modules/.bin/mocha tests/unit", - "test-behaviour": "node_modules/.bin/mocha tests/behaviour", + "test-behaviour": "node_modules/.bin/mocha tests/behaviour -R spec", "test-ci": "npm run lint && npm run test-unit && npm run test-behaviour && TEST_MODE=all tests/external-tests.js", "test": "npm run lint && npm run test-unit && npm run test-behaviour && TEST_MODE=required tests/external-tests.js", "update-util": "webpack" diff --git a/tests/behaviour/runner.js b/tests/behaviour/runner.js index a922607..0fa1d82 100644 --- a/tests/behaviour/runner.js +++ b/tests/behaviour/runner.js @@ -1,19 +1,21 @@ 'use strict'; -global.logLevel = "silent"; +global.logLevel = 'silent'; -let assert = require('assert'), +var assert = require('assert'), sinon = require('sinon'), path = require('path'), - browserstackRunner = require('../../bin/cli.js'); + http = require('http'), + browserstackRunner = require('../../bin/cli.js'), + Tunnel = require('../../lib/local.js').Tunnel; -let getBaseConfig = function() { +var getBaseConfig = function() { return { username: 'BROWSERSTACK_USER', key: 'BROWSERSTACK_KEY', test_framework: 'qunit', test_path: path.resolve(__dirname, 'resources', 'qunit_sample.html'), - build: "BrowserStack Runner Behaviour Tests", + build: 'BrowserStack Runner Behaviour Tests', browsers: [ { browser: 'firefox', browser_version: '47.0', @@ -38,26 +40,26 @@ describe('Config Assertions', function() { }); }); it('should have an error if test path is not valid', function(done) { - let config = getBaseConfig(); - config.test_path = "Some invalid path"; + var config = getBaseConfig(); + config.test_path = 'Some invalid path'; browserstackRunner.run(config, function(err) { - assert.equal(err.message, "Test path: " + config.test_path + " is invalid."); + assert.equal(err.message, 'Test path: ' + config.test_path + ' is invalid.'); done(); }); }); it('should have an error if config does not have a browsers key', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); delete(config.browsers); browserstackRunner.run(config, function(err) { - assert.equal(err.message, "Configuration parameter browsers is required."); + assert.equal(err.message, 'Configuration parameter browsers is required.'); done(); }); }); it('should have an error if config does not have a test_path key', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); delete(config.test_path); browserstackRunner.run(config, function(err) { - assert.equal(err.message, "Configuration parameter test_path is required."); + assert.equal(err.message, 'Configuration parameter test_path is required.'); done(); }); }); @@ -70,18 +72,18 @@ describe('Config Assertions', function() { }); it('should have an error if config does not have a username', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); delete(config.username); browserstackRunner.run(config, function(err) { - assert.equal(err.message, "Configuration parameter username is required."); + assert.equal(err.message, 'Configuration parameter username is required.'); done(); }); }); it('should have an error if config does not have a key', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); delete(config.key); browserstackRunner.run(config, function(err) { - assert.equal(err.message, "Configuration parameter key is required."); + assert.equal(err.message, 'Configuration parameter key is required.'); done(); }); }); @@ -96,47 +98,43 @@ describe('Pass/Fail reporting', function() { this.timeout(0); it('report keys should have browser names', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); - var parsedReport = JSON.parse(report); - assert.notEqual(parsedReport["Windows 7, Chrome 52.0"], null); - assert.notEqual(parsedReport["Windows 7, Firefox 47.0"], null); + assert.notEqual(report['Windows 7, Chrome 52.0'], null); + assert.notEqual(report['Windows 7, Firefox 47.0'], null); done(); }); }); it('report keys should have assertions and tests', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); - var parsedReport = JSON.parse(report); - assert.notEqual(parsedReport["Windows 7, Chrome 52.0"].assertions, null); - assert.notEqual(parsedReport["Windows 7, Chrome 52.0"].tests, null); - assert.notEqual(parsedReport["Windows 7, Firefox 47.0"].assertions, null); - assert.notEqual(parsedReport["Windows 7, Firefox 47.0"].tests, null); + assert.notEqual(report['Windows 7, Chrome 52.0'].assertions, null); + assert.notEqual(report['Windows 7, Chrome 52.0'].tests, null); + assert.notEqual(report['Windows 7, Firefox 47.0'].assertions, null); + assert.notEqual(report['Windows 7, Firefox 47.0'].tests, null); done(); }); }); describe('Test Assertions', function() { it('report should have proper number of assertions for tests', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); - var parsedReport = JSON.parse(report); // Only failed assertions are emitted - assert.equal(parsedReport["Windows 7, Chrome 52.0"].assertions.length, 8); - assert.equal(parsedReport["Windows 7, Firefox 47.0"].assertions.length, 8); + assert.equal(report['Windows 7, Chrome 52.0'].assertions.length, 8); + assert.equal(report['Windows 7, Firefox 47.0'].assertions.length, 8); done(); }); }); it('report should have specific keys', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); - var parsedReport = JSON.parse(report); - Object.keys(parsedReport).forEach(function(reportKey) { - parsedReport[reportKey].assertions.forEach(function(assertion) { - [ "actual", "expected", "message", "source" ].forEach(function(key) { + Object.keys(report).forEach(function(reportKey) { + report[reportKey].assertions.forEach(function(assertion) { + [ 'actual', 'expected', 'message', 'source' ].forEach(function(key) { assert.notEqual(assertion[key], null); }); }); @@ -145,13 +143,12 @@ describe('Pass/Fail reporting', function() { }); }); it('report should have message in assertions', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); - var parsedReport = JSON.parse(report); - Object.keys(parsedReport).forEach(function(reportKey) { - parsedReport[reportKey].assertions.forEach(function(assertion) { - assert.notEqual(assertion["message"].match(/\d+ is .*an .* number/), null); + Object.keys(report).forEach(function(reportKey) { + report[reportKey].assertions.forEach(function(assertion) { + assert.notEqual(assertion['message'].match(/\d+ is .*an .* number/), null); }); }); done(); @@ -160,23 +157,21 @@ describe('Pass/Fail reporting', function() { }); describe('Test tests', function() { it('report should have proper number of tests', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); - var parsedReport = JSON.parse(report); - assert.equal(parsedReport["Windows 7, Chrome 52.0"].tests.length, 1); - assert.equal(parsedReport["Windows 7, Firefox 47.0"].tests.length, 1); + assert.equal(report['Windows 7, Chrome 52.0'].tests.length, 1); + assert.equal(report['Windows 7, Firefox 47.0'].tests.length, 1); done(); }); }); it('report should have specific keys', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); - var parsedReport = JSON.parse(report); - Object.keys(parsedReport).forEach(function(reportKey) { - parsedReport[reportKey].tests.forEach(function(test) { - [ "runtime", "total", "passed", "failed", "url" ].forEach(function(key) { + Object.keys(report).forEach(function(reportKey) { + report[reportKey].tests.forEach(function(test) { + [ 'runtime', 'total', 'passed', 'failed', 'url' ].forEach(function(key) { assert.notEqual(test[key], null); }); }); @@ -185,15 +180,14 @@ describe('Pass/Fail reporting', function() { }); }); it('report should have message in assertions', function(done) { - let config = getBaseConfig(); + var config = getBaseConfig(); browserstackRunner.run(config, function(err, report) { assert.equal(err, null); - var parsedReport = JSON.parse(report); - Object.keys(parsedReport).forEach(function(reportKey) { - parsedReport[reportKey].tests.forEach(function(test) { - assert.equal(test["total"], 3); - assert.equal(test["passed"], 1); - assert.equal(test["failed"], 2); + Object.keys(report).forEach(function(reportKey) { + report[reportKey].tests.forEach(function(test) { + assert.equal(test['total'], 3); + assert.equal(test['passed'], 1); + assert.equal(test['failed'], 2); }); }); done(); From 43c2b6e55117643cdf5ce9df6d103eadbf888ee9 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Mon, 8 Aug 2016 12:25:27 +0530 Subject: [PATCH 102/162] Add usage to README --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/README.md b/README.md index e67a717..69098e3 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,99 @@ If you're getting an error `EACCES open ... BrowserStackLocal`, configure npm to Where `[user]` is replaced with a local user with enough permissions. +## Usage as a module + +`browserstack-runner` can also be used as a module. To run your tests, inside your project do - + +```node +var browserstackRunner = require('browserstack-runner'); + +global.logLevel = 'info'; +var config = require('./browserstack.json'); + +browserstackRunner.run(config, function(error, report) { + if(error) { + console.log("Error:" + error); + return; + } + console.log(JSON.stringify(report, null, 2)); + console.log("Test Finished"); +}); +``` + +The callback to `browserstackRunner.run` is called with two params - +1. `error`: This parameter is either `null` or an `Error` object (if test execution failed) with message as the reason of why executing the tests on `BrowserStack` failed. +2. `report`: This is an object which can be used to keep track of the `failed assertions` and the total count of `passed/failed` tests specific to a browser instance. + +The structure of the `report` object is as follows - + +```json +{ + "OS X Lion, Firefox 44.0": { + "assertions": [ + { + "actual": false, + "expected": true, + "message": "One is an odd number", + "source": "@http://localhost:8888/tests/test.js:4:1" + }, + { + "actual": false, + "expected": true, + "message": "Zero is not odd number", + "source": "@http://localhost:8888/tests/test.js:6:3" + }, + { + "actual": false, + "expected": true, + "message": "Three is an odd number", + "source": "@http://localhost:8888/tests/test.js:5:1" + } + ], + "tests": [ + { + "runtime": 3, + "total": 1, + "passed": 0, + "failed": 1, + "url": "/sample.html" + } + ] + }, + "OS X Mountain Lion, Chrome 49.0": { + "assertions": [ + { + "actual": false, + "expected": true, + "message": "Three is an odd number", + "source": " at Object. (http://localhost:8888/tests/test.js:5:10)" + }, + { + "actual": false, + "expected": true, + "message": "One is an odd number", + "source": " at Object. (http://localhost:8888/tests/test.js:4:10)" + }, + { + "actual": false, + "expected": true, + "message": "Zero is not odd number", + "source": " at Object. (http://localhost:8888/tests/test.js:6:10)" + } + ], + "tests": [ + { + "runtime": 9, + "total": 1, + "passed": 0, + "failed": 1, + "url": "/sample.html" + } + ] + } +} +``` + ## Configuration To run browser tests on BrowserStack infrastructure, you need to create a `browserstack.json` file in project's root directory (the directory from which tests are run), by running this command: From 87f43921089d26f2b05e2ea9cff5cda50bd95588 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Tue, 9 Aug 2016 14:18:38 +0530 Subject: [PATCH 103/162] Update report structure --- README.md | 166 ++++++++++++++--------- bin/cli.js | 10 +- lib/_patch/reporter.js | 30 +--- lib/config.js | 2 +- lib/server.js | 80 ++++++----- tests/behaviour/resources/qunit_test1.js | 2 +- tests/behaviour/runner.js | 116 +++++++++------- 7 files changed, 232 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index 69098e3..c4942de 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +# BrowserStack Runner + +[![Build Status](https://travis-ci.org/browserstack/browserstack-runner.svg?branch=master)](https://travis-ci.org/browserstack/browserstack-runner) + A command line interface to run browser tests over BrowserStack. ## Usage @@ -28,7 +32,6 @@ Where `[user]` is replaced with a local user with enough permissions. ```node var browserstackRunner = require('browserstack-runner'); -global.logLevel = 'info'; var config = require('./browserstack.json'); browserstackRunner.run(config, function(error, report) { @@ -42,76 +45,115 @@ browserstackRunner.run(config, function(error, report) { ``` The callback to `browserstackRunner.run` is called with two params - -1. `error`: This parameter is either `null` or an `Error` object (if test execution failed) with message as the reason of why executing the tests on `BrowserStack` failed. -2. `report`: This is an object which can be used to keep track of the `failed assertions` and the total count of `passed/failed` tests specific to a browser instance. +- `error`: This parameter is either `null` or an `Error` object (if test execution failed) with message as the reason of why executing the tests on `BrowserStack` failed. +- `report`: This is an array which can be used to keep track of the executed tests and suites in a run. Each object in the array has the following keys - + - `browser`: The name of the browser the test executed on. + - `tests`: An array of `Test` objects. The `Test` Objects are described [here](https://github.com/js-reporters/js-reporters#event-data) + - `suites`: A global Suite Object as described [here](https://github.com/js-reporters/js-reporters#event-data) The structure of the `report` object is as follows - ```json -{ - "OS X Lion, Firefox 44.0": { - "assertions": [ - { - "actual": false, - "expected": true, - "message": "One is an odd number", - "source": "@http://localhost:8888/tests/test.js:4:1" - }, - { - "actual": false, - "expected": true, - "message": "Zero is not odd number", - "source": "@http://localhost:8888/tests/test.js:6:3" - }, - { - "actual": false, - "expected": true, - "message": "Three is an odd number", - "source": "@http://localhost:8888/tests/test.js:5:1" - } - ], - "tests": [ +[ + { + "browser": "Windows 7, Firefox 47.0", + "tests": [ { - "runtime": 3, - "total": 1, - "passed": 0, - "failed": 1, - "url": "/sample.html" + "name": "isOdd()", + "suiteName": "Odd Tests", + "fullName": [ + "Odd Tests", + "isOdd()" + ], + "status": "passed", + "runtime": 2, + "errors": [], + "assertions": [ + { + "passed": true, + "actual": true, + "expected": true, + "message": "One is an odd number" + }, + { + "passed": true, + "actual": true, + "expected": true, + "message": "Three is an odd number" + }, + { + "passed": true, + "actual": true, + "expected": true, + "message": "Zero is not odd number" + } + ] } - ] - }, - "OS X Mountain Lion, Chrome 49.0": { - "assertions": [ - { - "actual": false, - "expected": true, - "message": "Three is an odd number", - "source": " at Object. (http://localhost:8888/tests/test.js:5:10)" - }, - { - "actual": false, - "expected": true, - "message": "One is an odd number", - "source": " at Object. (http://localhost:8888/tests/test.js:4:10)" - }, - { - "actual": false, - "expected": true, - "message": "Zero is not odd number", - "source": " at Object. (http://localhost:8888/tests/test.js:6:10)" - } - ], - "tests": [ + ], + "suites": { + "fullName": [], + "childSuites": [ { - "runtime": 9, - "total": 1, - "passed": 0, - "failed": 1, - "url": "/sample.html" + "name": "Odd Tests", + "fullName": [ + "Odd Tests" + ], + "childSuites": [], + "tests": [ + { + "name": "isOdd()", + "suiteName": "Odd Tests", + "fullName": [ + "Odd Tests", + "isOdd()" + ], + "status": "passed", + "runtime": 2, + "errors": [], + "assertions": [ + { + "passed": true, + "actual": true, + "expected": true, + "message": "One is an odd number" + }, + { + "passed": true, + "actual": true, + "expected": true, + "message": "Three is an odd number" + }, + { + "passed": true, + "actual": true, + "expected": true, + "message": "Zero is not odd number" + } + ] + } + ], + "status": "passed", + "testCounts": { + "passed": 1, + "failed": 0, + "skipped": 0, + "total": 1 + }, + "runtime": 2 } - ] + ], + "tests": [], + "status": "passed", + "testCounts": { + "passed": 1, + "failed": 0, + "skipped": 0, + "total": 1 + }, + "runtime": 2 } -} + } +] ``` ## Configuration diff --git a/bin/cli.js b/bin/cli.js index 498c922..49112ef 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -65,7 +65,7 @@ function terminateAllWorkers(callback) { function cleanUpAndExit(signal, error, report, callback) { ConfigParser.finalBrowsers = []; callback = callback || function() {}; - report = report || {}; + report = report || []; logger.trace('cleanUpAndExit: signal: %s', signal); try { @@ -306,8 +306,8 @@ var statusPoller = { } logger.trace('[%s] worker.activityTimeout: all tests done', worker.id, config.status && 'with failures'); - if(server && server.report) { - callback(null, server.report); + if(server && server.reports) { + callback(null, server.reports); } else { callback(null, {}); } @@ -336,8 +336,8 @@ var statusPoller = { } logger.trace('[%s] worker.testActivityTimeout: all tests done', worker.id, config.status && 'with failures'); - if(server && server.report) { - callback(null, server.report); + if(server && server.reports) { + callback(null, server.reports); } else { callback(null, {}); } diff --git a/lib/_patch/reporter.js b/lib/_patch/reporter.js index e8006ef..dce7561 100644 --- a/lib/_patch/reporter.js +++ b/lib/_patch/reporter.js @@ -1,8 +1,5 @@ (function() { var runner; - var total = 0, - passed = 0, - failed = 0; if (window.QUnit) { runner = new JsReporters.QUnitAdapter(QUnit); @@ -15,34 +12,13 @@ } runner.on('testEnd', function(test) { - total = total + 1 - - passed = passed + (test.status === 'passed' ? 1 : 0); - failed = failed + (test.status === 'failed' ? 1 : 0); - - test.errors.forEach(function(error) { - BrowserStack.post("/_progress", { - tracebacks: [{ - actual: error.actual, - expected: error.expected, - message: error.message, - source: error.source || error.stack, - testName: test.testName - }] - }, function() {}); + BrowserStack.post("/_progress", { + test: test }); }); runner.on('runEnd', function(globalSuite) { - var results = {}; - - results.runtime = globalSuite.runtime; - results.total = total; - results.passed = passed; - results.failed = failed; - results.url = window.location.pathname; - - BrowserStack.post("/_report", results, function() {}); + BrowserStack.post("/_report", globalSuite, function() {}); }); })(); diff --git a/lib/config.js b/lib/config.js index 6345b8e..25e05bf 100644 --- a/lib/config.js +++ b/lib/config.js @@ -44,7 +44,7 @@ exports.config = function(config) { fallback_project = 'Anonymous OpenSource Project'; } - this.project = process.env.TRAVIS_REPO_SLUG || package_json.name; + this.project = process.env.TRAVIS_REPO_SLUG || fallback_project || package_json.name; } var commit_id = process.env.TRAVIS_COMMIT; diff --git a/lib/server.js b/lib/server.js index a8c0178..a18f540 100644 --- a/lib/server.js +++ b/lib/server.js @@ -18,7 +18,24 @@ exports.Server = function Server(bsClient, workers, config, callback) { .map(function (path) { return path.split(/[?#]/)[0]; }), - report = {}; + reports = []; + + function getBrowserReport(browserInfo) { + var browserReport = null; + reports.forEach(function(report) { + if(report && report.browser === browserInfo) { + browserReport = report; + } + }); + if(!browserReport) { + browserReport = { + browser: browserInfo + }; + reports.push(browserReport); + } + browserReport.tests = browserReport.tests || []; + return browserReport; + } function handleFile(filename, request, response, doNotUseProxy) { var url_parts = url.parse(request.url, true); @@ -143,23 +160,21 @@ exports.Server = function Server(bsClient, workers, config, callback) { } function formatTraceback(details) { - // looks like QUnit data - if (details.testName) { - var output = '"' + details.testName + '" failed'; - if (details.message) { - output += ', ' + details.message; + var output = '"' + details.testName + '" failed'; + if(details.error) { + if (details.error.message) { + output += ', ' + details.error.message; } - if (details.actual != null && details.expected != null) { - output += '\n' + chalk.blue('Expected: ') + details.expected + - '\n' + chalk.blue(' Actual: ') + details.actual; + if (details.error.actual != null && details.error.expected != null) { + output += '\n' + chalk.blue('Expected: ') + details.error.expected + + '\n' + chalk.blue(' Actual: ') + details.error.actual; } - if (details.source) { + if (details.error.source || details.error.stack) { output += '\n' + chalk.blue(' Source: ') + ''; - output += details.source.split('\n').join('\n\t '); + output += ( details.error.source || details.error.stack ).split('\n').join('\n\t '); } - return output; } - return details; + return output; } function checkAndTerminateWorker(worker, callback) { @@ -217,6 +232,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { var handlers = { '_progress': function progressHandler(uri, body, request, response) { var uuid = getWorkerUuid(request); + if (!uuid) { sendError(response, 'worker not found', 404); return; @@ -233,15 +249,18 @@ exports.Server = function Server(bsClient, workers, config, callback) { logger.info('[%s] Log: ' + qs.parse(body).data, worker.string); } + var browserReport = getBrowserReport(browserInfo); + browserReport.tests.push(query.test || {}); - report[browserInfo] = report[browserInfo] || { assertions: [], tests: [] }; - - logger.trace('[%s] _progress', worker.id, query); + logger.trace('[%s] _progress', worker.id, JSON.stringify(query)); - if (query && query.tracebacks) { - query.tracebacks.forEach(function(traceback) { - report[browserInfo]['assertions'].push(traceback); - logger.info('[%s] ' + chalk.red('Error:'), browserInfo, formatTraceback(traceback)); + if (query && query.test && query.test.errors) { + query.test.errors.forEach(function(error) { + logger.info('[%s] ' + chalk.red('Error:'), browserInfo, formatTraceback({ + error: error, + testName: query.test.name, + suiteName: query.test.suiteName + })); }); } response.end(); @@ -254,6 +273,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { return; } + var worker = workers[uuid]; worker._worker_key = uuid; var browserInfo = worker.getTestBrowserInfo(); @@ -263,23 +283,19 @@ exports.Server = function Server(bsClient, workers, config, callback) { query = parseBody(body); } catch (e) {} - logger.trace('[%s] _report', worker.id, query); + logger.trace('[%s] _report', worker.id, JSON.stringify(query)); if (query === null) { logger.info('[%s] Null response from remote Browser', request.headers['x-browser-string']); } else { - report[browserInfo] = report[browserInfo] || { assertions: [], tests: [] }; - report[browserInfo]['tests'].push(query); + var browserReport = getBrowserReport(browserInfo); + browserReport.suites = query; - if (query.tracebacks && query.tracebacks.length > 0) { - logger.info('[%s] ' + chalk['red']('Tracebacks:'), browserInfo); - query.tracebacks.forEach(function(traceback) { - logger.info(traceback); - }); + if(query.testCounts) { + var color = query.status === 'failed' ? 'red' : 'green'; + logger.info('[%s] ' + chalk[color](query.status === 'failed' ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed, %d skipped; ran for %dms', browserInfo, query.testCounts.total, query.testCounts.passed, query.testCounts.failed, query.testCounts.skipped, query.runtime); + config.status += query.testCounts.failed; } - var color = query.failed ? 'red' : 'green'; - logger.info('[%s] ' + chalk[color](query.failed ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed; ran for %dms', browserInfo, query.total, query.passed, query.failed, query.runtime); - config.status += query.failed; } logger.trace('[%s] _report: client.takeScreenshot', worker.id); @@ -322,7 +338,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { } logger.trace('[%s] _report: checkAndTerminateWorker: all tests done', worker.id, config.status && 'with failures'); - callback(null, report); + callback(null, reports); } }); }); diff --git a/tests/behaviour/resources/qunit_test1.js b/tests/behaviour/resources/qunit_test1.js index 3f14542..aad2800 100644 --- a/tests/behaviour/resources/qunit_test1.js +++ b/tests/behaviour/resources/qunit_test1.js @@ -6,7 +6,7 @@ QUnit.module('Partial Tests', function() { // Passes assert.ok(isOdd(3), '3 is an odd number'); - assert.ok(!isOdd(4), '4 is not odd number'); + assert.ok(!isOdd(4), '4 is not an odd number'); assert.ok(isEven(6), '6 is an even number'); assert.ok(!isEven(7), '7 is not an even number'); }); diff --git a/tests/behaviour/runner.js b/tests/behaviour/runner.js index 0fa1d82..cebd9c2 100644 --- a/tests/behaviour/runner.js +++ b/tests/behaviour/runner.js @@ -99,96 +99,120 @@ describe('Pass/Fail reporting', function() { it('report keys should have browser names', function(done) { var config = getBaseConfig(); - browserstackRunner.run(config, function(err, report) { + browserstackRunner.run(config, function(err, reports) { + var shouldBePresentBrowsers = [ 'Windows 7, Chrome 52.0', 'Windows 7, Firefox 47.0' ]; assert.equal(err, null); - assert.notEqual(report['Windows 7, Chrome 52.0'], null); - assert.notEqual(report['Windows 7, Firefox 47.0'], null); + reports.forEach(function(report) { + var numMatched = 0; + shouldBePresentBrowsers.forEach(function(browser) { + if(browser === report.browser) { + numMatched++; + } + }); + if(numMatched != 1) { + done(new Error('Report didnt match the shouldBePresentBrowsers for browser: ' + report.browser + ' numMatched: ' + numMatched)); + } else { + var removeIndex = shouldBePresentBrowsers.indexOf(report.browser); + shouldBePresentBrowsers = shouldBePresentBrowsers.slice(0, removeIndex).concat(shouldBePresentBrowsers.slice(removeIndex + 1)); + } + }); + if(shouldBePresentBrowsers.length != 0) { + done(new Error('Browsers not Present in Report: ' + JSON.stringify(shouldBePresentBrowsers))); + } done(); }); }); - it('report keys should have assertions and tests', function(done) { + it('report keys should have suites and tests', function(done) { var config = getBaseConfig(); - browserstackRunner.run(config, function(err, report) { + browserstackRunner.run(config, function(err, reports) { assert.equal(err, null); - assert.notEqual(report['Windows 7, Chrome 52.0'].assertions, null); - assert.notEqual(report['Windows 7, Chrome 52.0'].tests, null); - assert.notEqual(report['Windows 7, Firefox 47.0'].assertions, null); - assert.notEqual(report['Windows 7, Firefox 47.0'].tests, null); + reports.forEach(function(report) { + assert.notEqual(report.tests, null); + assert.notEqual(report.suites, null); + }); done(); }); }); - describe('Test Assertions', function() { - it('report should have proper number of assertions for tests', function(done) { + describe('Test Tests', function() { + it('report should have proper number of tests', function(done) { var config = getBaseConfig(); - browserstackRunner.run(config, function(err, report) { + browserstackRunner.run(config, function(err, reports) { assert.equal(err, null); - // Only failed assertions are emitted - assert.equal(report['Windows 7, Chrome 52.0'].assertions.length, 8); - assert.equal(report['Windows 7, Firefox 47.0'].assertions.length, 8); + reports.forEach(function(report) { + assert.equal(report.tests.length, 3); + }); done(); }); }); - it('report should have specific keys', function(done) { + it('Each test should have specific keys', function(done) { var config = getBaseConfig(); - browserstackRunner.run(config, function(err, report) { + browserstackRunner.run(config, function(err, reports) { assert.equal(err, null); - Object.keys(report).forEach(function(reportKey) { - report[reportKey].assertions.forEach(function(assertion) { - [ 'actual', 'expected', 'message', 'source' ].forEach(function(key) { - assert.notEqual(assertion[key], null); + reports.forEach(function(report) { + Object.keys(report.tests).forEach(function(reportKey) { + [ 'name', 'suiteName', 'status', 'runtime', 'errors' ].forEach(function(key) { + assert.notEqual(report.tests[reportKey][key], null); + }); + report.tests[reportKey].assertions.forEach(function(assertion) { + [ 'passed', 'actual', 'expected', 'message' ].forEach(function(key) { + assert.notEqual(assertion[key], null); + }); }); }); }); done(); }); }); - it('report should have message in assertions', function(done) { + it('Each test should have message in assertions', function(done) { var config = getBaseConfig(); - browserstackRunner.run(config, function(err, report) { + browserstackRunner.run(config, function(err, reports) { assert.equal(err, null); - Object.keys(report).forEach(function(reportKey) { - report[reportKey].assertions.forEach(function(assertion) { - assert.notEqual(assertion['message'].match(/\d+ is .*an .* number/), null); + reports.forEach(function(report) { + Object.keys(report.tests).forEach(function(reportKey) { + report.tests[reportKey].assertions.forEach(function(assertion) { + assert.notEqual(assertion['message'].match(/\d+ is .*an .* number/), null); + }); }); }); done(); }); }); }); - describe('Test tests', function() { - it('report should have proper number of tests', function(done) { + describe('Test Suites', function() { + it('report should have Suite of tests', function(done) { var config = getBaseConfig(); - browserstackRunner.run(config, function(err, report) { + browserstackRunner.run(config, function(err, reports) { assert.equal(err, null); - assert.equal(report['Windows 7, Chrome 52.0'].tests.length, 1); - assert.equal(report['Windows 7, Firefox 47.0'].tests.length, 1); + reports.forEach(function(report) { + assert.notEqual(report.suites, null); + }); done(); }); }); - it('report should have specific keys', function(done) { + it('Each Suite should have specific keys', function(done) { var config = getBaseConfig(); - browserstackRunner.run(config, function(err, report) { + browserstackRunner.run(config, function(err, reports) { assert.equal(err, null); - Object.keys(report).forEach(function(reportKey) { - report[reportKey].tests.forEach(function(test) { - [ 'runtime', 'total', 'passed', 'failed', 'url' ].forEach(function(key) { - assert.notEqual(test[key], null); - }); + reports.forEach(function(report) { + [ 'childSuites', 'tests', 'runtime', 'status', 'testCounts' ].forEach(function(key) { + assert.notEqual(report.suites[key], null); + }); + [ 'total', 'passed', 'failed', 'skipped' ].forEach(function(key) { + assert.notEqual(report.suites.testCounts[key], null); }); }); done(); }); }); - it('report should have message in assertions', function(done) { + it('Suites should have correct passed/failed count', function(done) { var config = getBaseConfig(); - browserstackRunner.run(config, function(err, report) { + browserstackRunner.run(config, function(err, reports) { assert.equal(err, null); - Object.keys(report).forEach(function(reportKey) { - report[reportKey].tests.forEach(function(test) { - assert.equal(test['total'], 3); - assert.equal(test['passed'], 1); - assert.equal(test['failed'], 2); - }); + reports.forEach(function(report) { + assert.equal(report.suites.testCounts['total'], 3); + assert.equal(report.suites.testCounts['passed'], 1); + assert.equal(report.suites.testCounts['failed'], 2); + assert.equal(report.suites.testCounts['skipped'], 0); }); done(); }); From e5ede8b2b681ca2f2f2cb1d08df582669d795c20 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Thu, 11 Aug 2016 16:20:41 +0530 Subject: [PATCH 104/162] Bump js-reporters version and update jasmine patch --- bin/cli.js | 2 +- bin/runner.js | 14 +++++++------- lib/_patch/browserstack.js | 4 ++-- lib/_patch/reporter.js | 6 +++--- lib/server.js | 26 +++++++++++++------------- package.json | 2 +- tests/external-tests.js | 6 +++--- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 49112ef..4384a24 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -382,7 +382,7 @@ function runTests(config, callback) { logger.trace('runTests: creating tunnel'); tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, config, function (err) { if(err) { - cleanUpAndExit(null, err, {}, callback); + cleanUpAndExit(null, err, [], callback); } else { logger.trace('runTests: created tunnel'); diff --git a/bin/runner.js b/bin/runner.js index 0deb8f1..aef3b9e 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -26,12 +26,12 @@ try { config = require(config_path); } catch (e) { if (e.code === 'MODULE_NOT_FOUND') { - console.err('Configuration file `browserstack.json` is missing.'); + console.error('Configuration file `browserstack.json` is missing.'); throw new Error('Configuration file `browserstack.json` is missing.'); } else { - console.err('Invalid configuration in `browserstack.json` file'); - console.err(e.message); - console.err(e.stack); + console.error('Invalid configuration in `browserstack.json` file'); + console.error(e.message); + console.error(e.stack); throw new Error('Invalid configuration in `browserstack.json` file'); } } @@ -39,9 +39,9 @@ try { var runner = require('./cli.js'); runner.run(config, function(err) { if(err) { - console.err(err); - console.err(err.stack); - console.err('Invalid Command'); + console.error(err); + console.error(err.stack); + console.error('Invalid Command'); process.exit(1); } process.exit(0); diff --git a/lib/_patch/browserstack.js b/lib/_patch/browserstack.js index 4ea1f07..057513b 100644 --- a/lib/_patch/browserstack.js +++ b/lib/_patch/browserstack.js @@ -23,12 +23,12 @@ if (req.readyState==4) cb(req.responseText); }; - var data = "data=" + encodeURIComponent(JSON.stringify(json)); + var data = JSON.stringify(json); req.open("POST", url, true); req.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); req.setRequestHeader('X-Browser-String', BrowserStack.browser_string); req.setRequestHeader('X-Worker-UUID', BrowserStack.worker_uuid); - req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + req.setRequestHeader('Content-type', 'application/json'); req.send(data); } diff --git a/lib/_patch/reporter.js b/lib/_patch/reporter.js index dce7561..64c79c8 100644 --- a/lib/_patch/reporter.js +++ b/lib/_patch/reporter.js @@ -11,10 +11,10 @@ throw new Error('JsReporters: No testing framework was found'); } - runner.on('testEnd', function(test) { + runner.on('testEnd', function(eachTest) { BrowserStack.post("/_progress", { - test: test - }); + 'test': eachTest + }, function() {}); }); runner.on('runEnd', function(globalSuite) { diff --git a/lib/server.js b/lib/server.js index a18f540..e1df155 100644 --- a/lib/server.js +++ b/lib/server.js @@ -154,11 +154,6 @@ exports.Server = function Server(bsClient, workers, config, callback) { } } - function parseBody(body) { - // TODO: Have better implementation - return JSON.parse(qs.parse(body).data.escapeSpecialChars()); - } - function formatTraceback(details) { var output = '"' + details.testName + '" failed'; if(details.error) { @@ -243,18 +238,18 @@ exports.Server = function Server(bsClient, workers, config, callback) { var query = null; try { - query = parseBody(body); + query = JSON.parse(body); } catch(e) { logger.info('[%s] Exception in parsing log', worker.string); logger.info('[%s] Log: ' + qs.parse(body).data, worker.string); } - var browserReport = getBrowserReport(browserInfo); - browserReport.tests.push(query.test || {}); - logger.trace('[%s] _progress', worker.id, JSON.stringify(query)); if (query && query.test && query.test.errors) { + var browserReport = getBrowserReport(browserInfo); + browserReport.tests.push(query.test || {}); + query.test.errors.forEach(function(error) { logger.info('[%s] ' + chalk.red('Error:'), browserInfo, formatTraceback({ error: error, @@ -280,7 +275,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { var query = null; try { - query = parseBody(body); + query = JSON.parse(body); } catch (e) {} logger.trace('[%s] _report', worker.id, JSON.stringify(query)); @@ -291,8 +286,13 @@ exports.Server = function Server(bsClient, workers, config, callback) { var browserReport = getBrowserReport(browserInfo); browserReport.suites = query; - if(query.testCounts) { - var color = query.status === 'failed' ? 'red' : 'green'; + var color; + if(config['test_framework'] === 'jasmine') { + color = ( query.total !== query.passed ) ? 'red' : 'green'; + logger.info('[%s] ' + chalk[color](( query.total !== query.passed ) ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed; ran for %dms', browserInfo, query.total, query.passed, query.failed, query.runtime); + config.status += query.failed; + } else if(query.testCounts) { + color = query.status === 'failed' ? 'red' : 'green'; logger.info('[%s] ' + chalk[color](query.status === 'failed' ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed, %d skipped; ran for %dms', browserInfo, query.testCounts.total, query.testCounts.passed, query.testCounts.failed, query.testCounts.skipped, query.runtime); config.status += query.testCounts.failed; } @@ -347,7 +347,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { }, '_log': function logHandler(uri, body, request, response) { var uuid = getWorkerUuid(request); - var query = parseBody(body); + var query = body; logger.trace('[%s] _log', ((uuid && workers[uuid]) || {}).id, query); var logged = false; diff --git a/package.json b/package.json index 3fd741b..30d569e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dependencies": { "browserstack": "1.3.0", "chalk": "0.4.0", - "js-reporters": "^1.0.0", + "js-reporters": "^1.1.0", "mime": "1.3.4", "send": "0.13.0", "tunnel": "0.0.3" diff --git a/tests/external-tests.js b/tests/external-tests.js index 92ed62a..afc6120 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -96,9 +96,9 @@ var repositories = [ 'test/browser/opts.html' ], expected_results: { - tests: 94, - passed: 80, - failed: 14 + tests: 86, + passed: 78, + failed: 8 } }, { From 182505568efc0eb632cd5854f607efd50e6afabb Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Tue, 16 Aug 2016 12:57:44 +0530 Subject: [PATCH 105/162] Safe parse Circular JSON --- lib/_patch/browserstack.js | 15 ++++++++++----- lib/server.js | 16 +++++++++++----- package.json | 5 +++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/_patch/browserstack.js b/lib/_patch/browserstack.js index 057513b..31e98b8 100644 --- a/lib/_patch/browserstack.js +++ b/lib/_patch/browserstack.js @@ -9,7 +9,7 @@ } // Tiny Ajax Post - var post = function (url, json, cb){ + var post = function (url, json, cb) { var req; if (window.ActiveXObject) @@ -20,10 +20,15 @@ throw "Strider: No ajax" req.onreadystatechange = function () { - if (req.readyState==4) - cb(req.responseText); - }; - var data = JSON.stringify(json); + if (req.readyState==4) + cb(req.responseText); + }; + var data; + if(window.CircularJSON) { + data = window.CircularJSON.stringify(json); + } else { + data = JSON.stringify(json); + } req.open("POST", url, true); req.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); req.setRequestHeader('X-Browser-String', BrowserStack.browser_string); diff --git a/lib/server.js b/lib/server.js index e1df155..82b691e 100644 --- a/lib/server.js +++ b/lib/server.js @@ -11,7 +11,8 @@ var Log = require('./logger'), chalk = require('chalk'), mime = require('mime'), send = require('send'), - vm = require('vm'); + vm = require('vm'), + CircularJSON = require('circular-json'); exports.Server = function Server(bsClient, workers, config, callback) { var testFilePaths = (Array.isArray(config.test_path) ? config.test_path : [ config.test_path ]) @@ -64,6 +65,10 @@ exports.Server = function Server(bsClient, workers, config, callback) { var jsReportersScript = fs.readFileSync(jsReportersPath, { encoding: 'utf8' }); + var circularJSONPath = path.join(__dirname, '../node_modules/circular-json/build/circular-json.js'); + var circularJSONScript = fs.readFileSync(circularJSONPath, { + encoding: 'utf8' + }); if (pathMatches) { var framework = config['test_framework']; @@ -75,6 +80,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { }); patch += ''; + patch += ''; // adding framework scripts if (framework === 'jasmine') { @@ -238,13 +244,13 @@ exports.Server = function Server(bsClient, workers, config, callback) { var query = null; try { - query = JSON.parse(body); + query = CircularJSON.parse(body); } catch(e) { logger.info('[%s] Exception in parsing log', worker.string); logger.info('[%s] Log: ' + qs.parse(body).data, worker.string); } - logger.trace('[%s] _progress', worker.id, JSON.stringify(query)); + logger.trace('[%s] _progress', worker.id, CircularJSON.stringify(query)); if (query && query.test && query.test.errors) { var browserReport = getBrowserReport(browserInfo); @@ -275,10 +281,10 @@ exports.Server = function Server(bsClient, workers, config, callback) { var query = null; try { - query = JSON.parse(body); + query = CircularJSON.parse(body); } catch (e) {} - logger.trace('[%s] _report', worker.id, JSON.stringify(query)); + logger.trace('[%s] _report', worker.id, CircularJSON.stringify(query)); if (query === null) { logger.info('[%s] Null response from remote Browser', request.headers['x-browser-string']); diff --git a/package.json b/package.json index 30d569e..eb2d038 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "dependencies": { "browserstack": "1.3.0", "chalk": "0.4.0", - "js-reporters": "^1.1.0", + "circular-json": "0.3.1", + "js-reporters": "1.1.0", "mime": "1.3.4", "send": "0.13.0", "tunnel": "0.0.3" @@ -18,7 +19,7 @@ "devDependencies": { "jshint": "2.5.6", "mocha": "1.15.1", - "sinon": "^1.17.5" + "sinon": "1.17.5" }, "licenses": [ { From c86c5615664962ececee3d793a482b38b28b8d41 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Tue, 16 Aug 2016 18:42:03 +0530 Subject: [PATCH 106/162] use an abstract function --- lib/server.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/server.js b/lib/server.js index 82b691e..8d95c9b 100644 --- a/lib/server.js +++ b/lib/server.js @@ -61,15 +61,6 @@ exports.Server = function Server(bsClient, workers, config, callback) { var filePath = path.relative(process.cwd(), filename); var pathMatches = (testFilePaths.indexOf(filePath) !== -1); - var jsReportersPath = path.join(__dirname, '../node_modules/js-reporters/dist/js-reporters.js'); - var jsReportersScript = fs.readFileSync(jsReportersPath, { - encoding: 'utf8' - }); - var circularJSONPath = path.join(__dirname, '../node_modules/circular-json/build/circular-json.js'); - var circularJSONScript = fs.readFileSync(circularJSONPath, { - encoding: 'utf8' - }); - if (pathMatches) { var framework = config['test_framework']; var tag_name = (framework === 'mocha') ? 'head' : 'body'; @@ -79,8 +70,8 @@ exports.Server = function Server(bsClient, workers, config, callback) { patch += '\n'; }); - patch += ''; - patch += ''; + patch += externalScript('../node_modules/js-reporters/dist/js-reporters.js'); + patch += externalScript('../node_modules/circular-json/build/circular-json.js'); // adding framework scripts if (framework === 'jasmine') { @@ -203,7 +194,6 @@ exports.Server = function Server(bsClient, workers, config, callback) { return config.test_path[ ++worker.path_index ]; } - function getWorkerUuid(request) { var uuid = request.headers['x-worker-uuid']; @@ -229,6 +219,10 @@ exports.Server = function Server(bsClient, workers, config, callback) { response.end(); } + function externalScript(scriptPath) { + var scriptContents = fs.readFileSync(path.join(__dirname, scriptPath), { encoding: 'utf8' }); + return ''; + } var handlers = { '_progress': function progressHandler(uri, body, request, response) { From 90ed755802627fd47daf2870fc35cb83a1844839 Mon Sep 17 00:00:00 2001 From: Vibhaj Rajan Date: Tue, 23 Aug 2016 11:13:46 +0530 Subject: [PATCH 107/162] Updated readme for new way of running tests from wiki --- README.md | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 618e41d..0767f5e 100644 --- a/README.md +++ b/README.md @@ -171,26 +171,16 @@ These can also be provided by a build server, for example [using secure environm Check out code sample [here]. [here]:https://github.com/browserstack/browserstack-runner-sample -### Tests +### Running Tests -Testing involves pulling sample repos with test cases and running tests against them. -``` -git submodule update --init --recursive -npm test -``` +BrowserStack Runner is currently tested by running test cases defined in [QUnit](https://github.com/jquery/qunit), [Mocha](https://github.com/mochajs/mocha), and [Spine](https://github.com/spine/spine) repositories. -Sample `browserstack.json`: -``` -{ - "username": "BROWSERSTACK_USERNAME", - "key": "BROWSERSTACK_KEY", - "test_path": "tests/external-repos//test/test.html", - "debug": true, - "browsers": [ - "chrome_latest", - "firefox_latest", - "ie_11", - ... - ] -} -``` +To run tests: + + npm test + +To run a larger suite of tests ensuring compatibility with older versions of QUnit, etc.: + + npm run test-ci + +Tests are also run for every pull request, courtesy [Travis CI](https://travis-ci.org/). From 9b7011f097962d2a311778ca136d072aa6dbb758 Mon Sep 17 00:00:00 2001 From: Josh Duff Date: Sat, 3 Dec 2016 01:41:05 -0600 Subject: [PATCH 108/162] Use resolve to resolve module references instead of path.join Fixes #168 --- lib/server.js | 10 ++++++---- package.json | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/server.js b/lib/server.js index 8d95c9b..1292631 100644 --- a/lib/server.js +++ b/lib/server.js @@ -12,7 +12,8 @@ var Log = require('./logger'), mime = require('mime'), send = require('send'), vm = require('vm'), - CircularJSON = require('circular-json'); + CircularJSON = require('circular-json'), + resolve = require('resolve'); exports.Server = function Server(bsClient, workers, config, callback) { var testFilePaths = (Array.isArray(config.test_path) ? config.test_path : [ config.test_path ]) @@ -70,8 +71,8 @@ exports.Server = function Server(bsClient, workers, config, callback) { patch += '\n'; }); - patch += externalScript('../node_modules/js-reporters/dist/js-reporters.js'); - patch += externalScript('../node_modules/circular-json/build/circular-json.js'); + patch += externalScript('js-reporters/dist/js-reporters.js'); + patch += externalScript('circular-json/build/circular-json.js'); // adding framework scripts if (framework === 'jasmine') { @@ -220,7 +221,8 @@ exports.Server = function Server(bsClient, workers, config, callback) { } function externalScript(scriptPath) { - var scriptContents = fs.readFileSync(path.join(__dirname, scriptPath), { encoding: 'utf8' }); + var resolvedPath = resolve.sync(scriptPath, { basedir: __dirname }) + var scriptContents = fs.readFileSync(resolvedPath, { encoding: 'utf8' }); return ''; } diff --git a/package.json b/package.json index eb2d038..9fb8b6d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "circular-json": "0.3.1", "js-reporters": "1.1.0", "mime": "1.3.4", + "resolve": "1.1.7", "send": "0.13.0", "tunnel": "0.0.3" }, From d2b05b163542a3a27fce6f467aabd5c3796da71e Mon Sep 17 00:00:00 2001 From: Josh Duff Date: Sat, 3 Dec 2016 01:45:21 -0600 Subject: [PATCH 109/162] Semicolon for the linter --- lib/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index 1292631..0fe4b94 100644 --- a/lib/server.js +++ b/lib/server.js @@ -221,7 +221,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { } function externalScript(scriptPath) { - var resolvedPath = resolve.sync(scriptPath, { basedir: __dirname }) + var resolvedPath = resolve.sync(scriptPath, { basedir: __dirname }); var scriptContents = fs.readFileSync(resolvedPath, { encoding: 'utf8' }); return ''; } From e5537a7d9a5d73c742984fe7fa06fa62351bdb18 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Thu, 29 Dec 2016 18:50:41 +0530 Subject: [PATCH 110/162] iterate and modify an array at the same time --- lib/config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/config.js b/lib/config.js index 25e05bf..10e49c8 100644 --- a/lib/config.js +++ b/lib/config.js @@ -65,8 +65,8 @@ exports.config = function(config) { if (typeof(config['test_server']) === 'undefined') { this.test_path = config.test_path; if (Object.prototype.toString.call(this.test_path) === '[object Array]') { - this.test_path.forEach(function(path) { - path = formatPath(path); + this.test_path.forEach(function(path, index, test_path_array) { + test_path_array[index] = formatPath(path); }); } else { From 4e0c194047fe71507b13b1448165db5121190341 Mon Sep 17 00:00:00 2001 From: Ankur Goel Date: Thu, 29 Dec 2016 19:30:29 +0530 Subject: [PATCH 111/162] Version bump for new release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9fb8b6d..8b8e343 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.4.4", + "version": "0.5.0", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 81b0e2e1e44791a8b389bbcdf9a6fa35892bf90f Mon Sep 17 00:00:00 2001 From: hagaii Date: Thu, 26 Jan 2017 15:45:16 +0200 Subject: [PATCH 112/162] fix log printing in QUnit --- lib/server.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index 0fe4b94..047fec3 100644 --- a/lib/server.js +++ b/lib/server.js @@ -349,7 +349,11 @@ exports.Server = function Server(bsClient, workers, config, callback) { }, '_log': function logHandler(uri, body, request, response) { var uuid = getWorkerUuid(request); - var query = body; + var query = null; + try { + query = CircularJSON.parse(body); + } catch (e) {} + logger.trace('[%s] _log', ((uuid && workers[uuid]) || {}).id, query); var logged = false; From f194c816fbb9bcb595af2f744b50e08249ef7b6e Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Fri, 27 Jan 2017 12:24:44 +0530 Subject: [PATCH 113/162] Added specs for server logs for console.log and test progress/errors --- tests/behaviour/server.js | 148 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 tests/behaviour/server.js diff --git a/tests/behaviour/server.js b/tests/behaviour/server.js new file mode 100644 index 0000000..c96e16d --- /dev/null +++ b/tests/behaviour/server.js @@ -0,0 +1,148 @@ +'use strict'; + +var assert = require('assert'), + sinon = require('sinon'), + path = require('path'), + http = require('http'), + chalk = require('chalk'), + serverPort = 8888, + browserStackRunnerServer = require('../../lib/server.js'); + +var getBaseConfig = function() { + return { + username: 'BROWSERSTACK_USER', + key: 'BROWSERSTACK_KEY', + test_framework: 'qunit', + test_path: path.resolve(__dirname, 'resources', 'qunit_sample.html'), + build: 'BrowserStack Runner Behaviour Tests', + browsers: [ { + browser: 'firefox', + browser_version: '47.0', + os: 'Windows', + os_version: '7' + } ] + } +}; + +var requestServer = function(path, requestBody, appendHeaders, callback) { + var headers = { + 'Content-Length': Buffer.byteLength(requestBody) + } + var request = http.request({ + hostname: 'localhost', + port: serverPort, + path: path, + method: 'POST', + headers: Object.assign(headers, appendHeaders), + }, (res) => { + var responseData = ''; + + res.on('data', (data) => { + responseData += data.toString(); + }); + res.on('end', () => { + callback(null, responseData, res.statusCode); + }); + }).on('error', (e) => { + callback(e); + }); + request.write(requestBody); + request.end(); +}; + +describe('Server Assertions', function() { + describe('Assert logs from the browserstack-runner server', function() { + var sandBox, bsClient, infoLoggerStub, server, reports, workers = {}; + + beforeEach(function() { + sandBox = sinon.sandbox.create(); + bsClient = sandBox.stub(); + infoLoggerStub = sandBox.stub(browserStackRunnerServer.logger, 'info'); + + server = browserStackRunnerServer.Server(bsClient, workers, getBaseConfig(), function(error, reports) { + console.log('Dude!', reports); + }); + server.listen(serverPort); + }); + + afterEach(function() { + sandBox.restore(); + server.close(); + }); + + it('logs', function(done) { + var browserString = 'OS X Chrome 54' + requestServer('/_log', '{"arguments":["Random String"]}', { + 'x-browser-string': browserString + }, function(error) { + if(error) done(error); + assert.equal(infoLoggerStub.called, true); + assert.equal(infoLoggerStub.callCount, 1); + assert.equal(infoLoggerStub.getCalls()[0].args, '[' + browserString + '] ' + 'Random String'); + + requestServer('/_log', '{"arguments":["Invalid Random String', { + 'x-browser-string': browserString + }, function(error) { + if(error) done(error); + assert.equal(infoLoggerStub.callCount, 2); + assert.equal(infoLoggerStub.getCalls()[1].args, '[' + browserString + '] ' + '{"arguments":["Invalid Random String'); + done(); + }); + }); + }); + + it('test errors', function(done) { + this.timeout(0); + var browserUUIDString = 'abcd-efgh-1234-5678', + browserInfoString = 'browserInfo'; + + workers[browserUUIDString] = { + getTestBrowserInfo: sandBox.stub().returns(browserInfoString), + string: 'workerString' + }; + var requestBodyObject = { + test: { + errors: [{ + message: "failedTestMessage", + actual: "ActualValue", + expected: "expectedValue", + source: "LongStackTrace" + }], + name:"customTestName", + suiteName:"customSuiteName" + } + }; + + requestServer('/_progress', JSON.stringify(requestBodyObject), { + 'x-worker-uuid': browserUUIDString + }, function(error) { + if(error) done(error); + assert.equal(infoLoggerStub.called, true); + assert.equal(infoLoggerStub.callCount, 1); + assert.equal(infoLoggerStub.getCalls()[0].args.length, 3); + assert.equal(infoLoggerStub.getCalls()[0].args[0], '[%s] ' + chalk.red('Error:')); + assert.equal(infoLoggerStub.getCalls()[0].args[1], browserInfoString); + assert.equal(infoLoggerStub.getCalls()[0].args[2], + '"customTestName" failed, failedTestMessage\n' + chalk.blue('Expected: ') + 'expectedValue' + + '\n' + chalk.blue(' Actual: ') + 'ActualValue' + + '\n' + chalk.blue(' Source: ') + 'LongStackTrace' + ); + + requestServer('/_progress', '{"arguments":["Invalid Random String', { + 'x-worker-uuid': browserUUIDString + }, function(error) { + if(error) done(error); + assert.equal(infoLoggerStub.callCount, 3); + assert.equal(infoLoggerStub.getCalls()[1].args.length, 2); + assert.equal(infoLoggerStub.getCalls()[1].args[0], '[%s] Exception in parsing log'); + assert.equal(infoLoggerStub.getCalls()[1].args[1], 'workerString'); + + assert.equal(infoLoggerStub.getCalls()[2].args.length, 2); + assert.equal(infoLoggerStub.getCalls()[2].args[0], '[%s] Log: undefined'); + assert.equal(infoLoggerStub.getCalls()[2].args[1], 'workerString'); + done(); + }); + }); + }); + }); +}); From e21a4b1a8c543c5f975b49de3c953cdc539b76bb Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Fri, 27 Jan 2017 12:25:25 +0530 Subject: [PATCH 114/162] fix server logs for non-json logs --- lib/server.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index 047fec3..5ee0af7 100644 --- a/lib/server.js +++ b/lib/server.js @@ -352,7 +352,9 @@ exports.Server = function Server(bsClient, workers, config, callback) { var query = null; try { query = CircularJSON.parse(body); - } catch (e) {} + } catch (e) { + query = body; + } logger.trace('[%s] _log', ((uuid && workers[uuid]) || {}).id, query); @@ -406,3 +408,5 @@ exports.Server = function Server(bsClient, workers, config, callback) { }); }); }; + +exports.logger = logger; From 180a8eda1b44a22f3cd3452f7b2366303aef83cd Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Fri, 27 Jan 2017 13:07:00 +0530 Subject: [PATCH 115/162] Added tests for server logs in case of test reports --- tests/behaviour/server.js | 58 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/tests/behaviour/server.js b/tests/behaviour/server.js index c96e16d..6a68c44 100644 --- a/tests/behaviour/server.js +++ b/tests/behaviour/server.js @@ -56,7 +56,9 @@ describe('Server Assertions', function() { beforeEach(function() { sandBox = sinon.sandbox.create(); - bsClient = sandBox.stub(); + bsClient = { + takeScreenshot: sandBox.stub() + }, infoLoggerStub = sandBox.stub(browserStackRunnerServer.logger, 'info'); server = browserStackRunnerServer.Server(bsClient, workers, getBaseConfig(), function(error, reports) { @@ -70,7 +72,7 @@ describe('Server Assertions', function() { server.close(); }); - it('logs', function(done) { + it('logs console.log correctly', function(done) { var browserString = 'OS X Chrome 54' requestServer('/_log', '{"arguments":["Random String"]}', { 'x-browser-string': browserString @@ -91,8 +93,7 @@ describe('Server Assertions', function() { }); }); - it('test errors', function(done) { - this.timeout(0); + it('logs test errors correctly', function(done) { var browserUUIDString = 'abcd-efgh-1234-5678', browserInfoString = 'browserInfo'; @@ -144,5 +145,54 @@ describe('Server Assertions', function() { }); }); }); + + it('logs for test reports correctly', function(done) { + var browserUUIDString = 'abcd-efgh-1234-5678', + browserString = 'OS X Chrome 41', + browserInfoString = 'browserInfo'; + + workers[browserUUIDString] = { + getTestBrowserInfo: sandBox.stub().returns(browserInfoString), + string: 'workerString' + }; + var requestBodyObject = { + testCounts: { + total: 1, + passed: 1, + failed: 0, + skipped: 0 + }, + runtime: '00:01:00', + status: 'passed' + }; + + requestServer('/_report', JSON.stringify(requestBodyObject), { + 'x-worker-uuid': browserUUIDString + }, function(error) { + if(error) done(error); + assert.equal(infoLoggerStub.called, true); + assert.equal(infoLoggerStub.callCount, 1); + assert.equal(infoLoggerStub.getCalls()[0].args.length, 7); + assert.equal(infoLoggerStub.getCalls()[0].args[0], '[%s] ' + chalk['green']('Passed:') + ' %d tests, %d passed, %d failed, %d skipped; ran for %dms'); + assert.equal(infoLoggerStub.getCalls()[0].args[1], browserInfoString); + assert.equal(infoLoggerStub.getCalls()[0].args[2], 1); + assert.equal(infoLoggerStub.getCalls()[0].args[3], 1); + assert.equal(infoLoggerStub.getCalls()[0].args[4], 0); + assert.equal(infoLoggerStub.getCalls()[0].args[5], 0); + assert.equal(infoLoggerStub.getCalls()[0].args[6], '00:01:00'); + + requestServer('/_report', '{"arguments":["Invalid Random String', { + 'x-worker-uuid': browserUUIDString, + 'x-browser-string': browserString + }, function(error) { + if(error) done(error); + assert.equal(infoLoggerStub.callCount, 2); + assert.equal(infoLoggerStub.getCalls()[1].args.length, 2); + assert.equal(infoLoggerStub.getCalls()[1].args[0], '[%s] Null response from remote Browser'); + assert.equal(infoLoggerStub.getCalls()[1].args[1], browserString); + done(); + }); + }); + }); }); }); From 7f7c50bad4e4b9f8f894eb2f366fc85eebd48723 Mon Sep 17 00:00:00 2001 From: Reetesh Ranjan Date: Sun, 30 Apr 2017 18:48:30 +0530 Subject: [PATCH 116/162] Fix for Issue #174 (exception seen in Travis) The issue is documented here: https://github.com/browserstack/browserstack-runner/issues/174 The variable 'worker' being accessed at line# 279 is being obtained using a look-up into the 'workers' variable where the key to lookup is coming from '_workers'. It seems '_workers' and 'workers' are not in sync, and the key obtained from '_workers' does not exist in 'workers' causing the exception. Though any race conditions causing this being identified and eliminated should be the ideal fix; however, it was also seen that extending the check at line# 279 from 'if(worker.launched)' to 'if(!worker || worker.launched)' does not cause any side effect on the code following this if block, because all this if block does is to return (stop processing this 'worker'). Ideally, if 'worker' does not even exist, that is equally good reason to stop processing as well, and that's my logic for the statement that 'no side effects are caused'. --- bin/cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/cli.js b/bin/cli.js index 4384a24..1522a7c 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -276,7 +276,7 @@ var statusPoller = { }).forEach(function(_worker) { var workerData = workerKeys[_worker.id]; var worker = workers[workerData.key]; - if (worker.launched) { + if (!worker || worker.launched) { return; } From 7a628f34f03d482f370e2406a2bb4b94c3650311 Mon Sep 17 00:00:00 2001 From: Harish Ved Date: Tue, 9 May 2017 19:53:00 +0530 Subject: [PATCH 117/162] Version Bump for release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b8e343..6edb249 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.5.0", + "version": "0.5.1", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 56dc01af5e870919aabfa2e803ebf7c49534355d Mon Sep 17 00:00:00 2001 From: Reetesh Ranjan Date: Thu, 11 May 2017 02:36:04 +0530 Subject: [PATCH 118/162] Fix for Issue #177 The code that works with Jasmine is completely different from the one working with Mocha and QUnit. The results sent to '_report' API in case of Jasmine has very limited information resulting in the case as described in this issue. It was found that the Jasmine reports are actually as good as Mocha and it's our code that does not let it pass through in its entirety causing the limited information. - jasmine-jsreporter.js: it has the code that prepares per test suite report, and cuts out detailed information. It was modified to retain detailed information. Also some 'summarization' attributes are just duplicate state, and hence they were removed - jasmine-plugin.js: it has code that further summarizes the reports created per suite in jasmine-jsreporter.js before making a call to the '_report' API. It was modified to retain detailed information. - server.js: for Jasmine case, a 'reformatting' adapter function is added. With the changes done in above files, detailed information as good as Mocha is available; however, the structure is different. This function adapts the structure such that the final reports are in the format desired. This function is called in the handler for '_report' API. Limitations/Differences - There is no equivalent of '_progress' API here. The 'tests' attribute at the topmost level is built in the '_progress' API in case of Mocha. This API gets called as each test completes with Mocha. In Jasmine case, we build the 'tests' attribute in the handler for '_report' call itself. - In case of Mocha, whether it's an assertion failure or an uncaught exception, stack trace is available for both cases. In Jasmine case, stack trace is available only in case of uncaught exceptions. In case of assertion failures, Jasmine provides only a message, not a trace. --- lib/_patch/jasmine-jsreporter.js | 5 +- lib/_patch/jasmine-plugin.js | 9 ++- lib/server.js | 96 ++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/lib/_patch/jasmine-jsreporter.js b/lib/_patch/jasmine-jsreporter.js index 67eeea3..76ccc0e 100644 --- a/lib/_patch/jasmine-jsreporter.js +++ b/lib/_patch/jasmine-jsreporter.js @@ -75,10 +75,7 @@ description : specs[i].description, durationSec : specs[i].durationSec, passed : specs[i].results().passedCount === specs[i].results().totalCount, - skipped : specs[i].results().skipped, - passedCount : specs[i].results().passedCount, - failedCount : specs[i].results().failedCount, - totalCount : specs[i].results().totalCount + results : specs[i].results() }; suiteData.passed = !suiteData.specs[i].passed ? false : suiteData.passed; suiteData.durationSec += suiteData.specs[i].durationSec; diff --git a/lib/_patch/jasmine-plugin.js b/lib/_patch/jasmine-plugin.js index 55687f0..cd45520 100644 --- a/lib/_patch/jasmine-plugin.js +++ b/lib/_patch/jasmine-plugin.js @@ -3,8 +3,10 @@ for (var i = 0; i < suite.specs.length; ++i) { if (suite.specs[i].passed){ results.passed++; + } + else if(suite.specs[i].results.skipped) { + results.skipped++; } else { - results.tracebacks.push(suite.specs[i].description); results.failed++; } } @@ -26,7 +28,7 @@ results.total = 0; results.passed = 0; results.failed = 0; - results.tracebacks = []; + results.skipped = 0; for (var i = 0; i < report.suites.length; ++i) { if (report.suites[i]) { @@ -34,9 +36,10 @@ } } - results.total = results.passed + results.failed; + results.total = results.passed + results.failed + results.skipped; results.url = window.location.pathname; + results.report = report BrowserStack.post("/_report", results, function(){}); clearInterval(checker); } diff --git a/lib/server.js b/lib/server.js index 5ee0af7..816ee79 100644 --- a/lib/server.js +++ b/lib/server.js @@ -39,6 +39,101 @@ exports.Server = function Server(bsClient, workers, config, callback) { return browserReport; } + function reformatJasmineReport(browserReport) { + var results = browserReport.suites; + browserReport.tests = browserReport.tests || [ ]; + browserReport.suites = { + fullName : [ ], + childSuites : [ ], + tests : [ ], + status : !results.failed ? 'passed' : 'failed', + testCounts : { + passed : results.passed, + failed : results.failed, + skipped : results.skipped, + total : results.total, + }, + runtime : results.runtime + }; + function recurseThroughSuites(jasmineSuite, par) { + var suite = { + name : jasmineSuite.description, + fullName: [ ], + childSuites : [ ], + tests: [ ], + status : jasmineSuite.passed ? 'passed' : 'failed', + testCounts : { + passed : 0, + failed : 0, + skipped: 0, + total: 0 + }, + runtime: 0 + }; + if(par.name) { + suite.fullName.push(par.name); + } + suite.fullName.push(jasmineSuite.description); + jasmineSuite.specs.forEach(function(spec) { + var test = { + name : spec.description, + suiteName : suite.decription, + fullName : [ + ], + status : spec.passed ? 'passed' : (spec.results.skipped ? 'skipped' : 'failed'), + runtime : spec.durationSec, + errors : [ ], + assertions : [ ] + }; + Array.prototype.push.apply(test.fullName, suite.fullName); + test.fullName.push(spec.description); + if(!spec.passed) { + spec.results.items_.forEach(function(jasmineItem) { + if(!jasmineItem.passed_) { + var detail = { + passed : false + }; + if('message' in jasmineItem) { + detail.message = jasmineItem.message; + } + if('actual' in jasmineItem) { + detail.actual = jasmineItem.actual; + } + if('expected' in jasmineItem) { + detail.expected = jasmineItem.expected; + } + if('trace' in jasmineItem) { + detail.stack = jasmineItem.trace.message || jasmineItem.trace.stack; + } + test.errors.push(detail); + test.assertions.push(detail); + } + }); + } + suite.tests.push(test); + browserReport.tests.push(test); + if(spec.passed) { + ++suite.testCounts.passed; + } + else if(spec.skipped) { + ++suite.testCounts.skipped; + } + else { + ++suite.testCounts.failed; + } + ++suite.testCounts.total; + suite.runtime += spec.durationSec; + }); + jasmineSuite.suites.forEach(function(childSuite) { + recurseThroughSuites(childSuite, suite); + }); + par.childSuites.push(suite); + } + results.report.suites.forEach(function(jasmineSuite) { + recurseThroughSuites(jasmineSuite, browserReport.suites); + }); + } + function handleFile(filename, request, response, doNotUseProxy) { var url_parts = url.parse(request.url, true); var query = url_parts.query; @@ -293,6 +388,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { color = ( query.total !== query.passed ) ? 'red' : 'green'; logger.info('[%s] ' + chalk[color](( query.total !== query.passed ) ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed; ran for %dms', browserInfo, query.total, query.passed, query.failed, query.runtime); config.status += query.failed; + reformatJasmineReport(browserReport); } else if(query.testCounts) { color = query.status === 'failed' ? 'red' : 'green'; logger.info('[%s] ' + chalk[color](query.status === 'failed' ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed, %d skipped; ran for %dms', browserInfo, query.testCounts.total, query.testCounts.passed, query.testCounts.failed, query.testCounts.skipped, query.runtime); From 4072fa05c18892817c94627d3e69ee34d88de544 Mon Sep 17 00:00:00 2001 From: Reetesh Ranjan Date: Thu, 11 May 2017 05:15:50 +0530 Subject: [PATCH 119/162] Issues with spine-v1 browsers & changes The external test spine-v1 had 2 issues: - Bad Jasmine output data with NULL suite description which was not hanlded by the changed code - Safari 5.1 is timing out. Changing to Safari 9.0 works. --- lib/server.js | 1 + tests/external-tests.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/server.js b/lib/server.js index 816ee79..eb6d719 100644 --- a/lib/server.js +++ b/lib/server.js @@ -56,6 +56,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { runtime : results.runtime }; function recurseThroughSuites(jasmineSuite, par) { + if(!jasmineSuite) return var suite = { name : jasmineSuite.description, fullName: [ ], diff --git a/tests/external-tests.js b/tests/external-tests.js index afc6120..590ed30 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -131,9 +131,9 @@ var repositories = [ browsers: [ { 'browser': 'safari', - 'browser_version': '5.1', + 'browser_version': '9.0', 'os': 'OS X', - 'os_version': 'Snow Leopard' + 'os_version': 'El Capitan' } ], test_path: [ From fd5451d851f6d31b7a5082b0e496b5f07c2e50de Mon Sep 17 00:00:00 2001 From: Reetesh Ranjan Date: Thu, 11 May 2017 05:21:33 +0530 Subject: [PATCH 120/162] Fixed js lint failure --- lib/server.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index eb6d719..f01d7e0 100644 --- a/lib/server.js +++ b/lib/server.js @@ -56,7 +56,9 @@ exports.Server = function Server(bsClient, workers, config, callback) { runtime : results.runtime }; function recurseThroughSuites(jasmineSuite, par) { - if(!jasmineSuite) return + if(!jasmineSuite) { + return; + } var suite = { name : jasmineSuite.description, fullName: [ ], From a89e5420a9c0523e4aebd68577a14915b38b99a7 Mon Sep 17 00:00:00 2001 From: Eugene Tiutiunnyk Date: Mon, 24 Jul 2017 11:29:03 -0700 Subject: [PATCH 121/162] Add an option of exit with fail on tests fail --- README.md | 1 + bin/cli.js | 10 ++++++---- bin/runner.js | 10 +++++++--- lib/server.js | 3 ++- lib/utils.js | 10 ++++++++++ 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4ed5675..0a7e859 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,7 @@ To run browser tests on BrowserStack infrastructure, you need to create a `brows * `browsers`: A list of browsers on which tests are to be run. Find a [list of all supported browsers and platforms on browerstack.com](https://www.browserstack.com/list-of-browsers-and-platforms?product=js_testing). * `build`: A string to identify your test run in Browserstack. In `TRAVIS` setup `TRAVIS_COMMIT` will be the default identifier. * `proxy`: Specify a proxy to use for the local tunnel. Object with `host`, `port`, `username` and `password` properties. + * `exit_with_fail`: If set to true the cli process will exit with fail if any of the tests failed. Useful for automatic build systems. A sample configuration file: diff --git a/bin/cli.js b/bin/cli.js index 1522a7c..3e8ff45 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -306,10 +306,11 @@ var statusPoller = { } logger.trace('[%s] worker.activityTimeout: all tests done', worker.id, config.status && 'with failures'); + var testsFailedError = utils.createTestsFailedError(config); if(server && server.reports) { - callback(null, server.reports); + callback(testsFailedError, server.reports); } else { - callback(null, {}); + callback(testsFailedError, {}); } } } else { @@ -336,10 +337,11 @@ var statusPoller = { } logger.trace('[%s] worker.testActivityTimeout: all tests done', worker.id, config.status && 'with failures'); + var testsFailedError = utils.createTestsFailedError(config); if(server && server.reports) { - callback(null, server.reports); + callback(testsFailedError, server.reports); } else { - callback(null, {}); + callback(testsFailedError, {}); } } } else { diff --git a/bin/runner.js b/bin/runner.js index aef3b9e..484e2d7 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -39,9 +39,13 @@ try { var runner = require('./cli.js'); runner.run(config, function(err) { if(err) { - console.error(err); - console.error(err.stack); - console.error('Invalid Command'); + if (err.name === 'TestsFailedError') { + console.error('Exit with fail due to some tests failure.'); + } else { + console.error(err); + console.error(err.stack); + console.error('Invalid Command'); + } process.exit(1); } process.exit(0); diff --git a/lib/server.js b/lib/server.js index 5ee0af7..8b9300c 100644 --- a/lib/server.js +++ b/lib/server.js @@ -340,7 +340,8 @@ exports.Server = function Server(bsClient, workers, config, callback) { } logger.trace('[%s] _report: checkAndTerminateWorker: all tests done', worker.id, config.status && 'with failures'); - callback(null, reports); + var testsFailedError = utils.createTestsFailedError(config); + callback(testsFailedError, reports); } }); }); diff --git a/lib/utils.js b/lib/utils.js index dab1f82..e6b7f18 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -43,7 +43,17 @@ var objectSize = function objectSize(obj) { return size; }; +var createTestsFailedError = function createTestsFailedError(config) { + var error = null; + if (config.status && config.exit_with_fail) { + error = new Error('Some tests failed.'); + error.name = 'TestsFailedError'; + } + return error; +}; + exports.titleCase = titleCase; exports.uuid = uuid; exports.browserString = browserString; exports.objectSize = objectSize; +exports.createTestsFailedError = createTestsFailedError; From 064f0e9d5ccfc33441c46eac50cc7241e4c813ff Mon Sep 17 00:00:00 2001 From: Eugene Tiutiunnyk Date: Wed, 5 Jul 2017 13:17:52 -0700 Subject: [PATCH 122/162] Add `tunnel_pid` and `test_server_port` settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support of `config.tunnel_pid` and cli `—pid` param - It should be a string like “/dir1/dir2/file.pid” to save process id - It will allow external process to control the tunnel process - If automated jobs for browserstack hanged or shutdown, we want to kill previous tunnel process manually before starting tests again. * Add support of `config.test_server_port` param - If not set, the default 8888 port is used - List of support ports: https://www.browserstack.com/question/664 --- README.md | 3 +++ bin/cli.js | 11 +++++------ bin/runner.js | 8 +++++++- lib/config.js | 4 ++++ lib/local.js | 11 +++++++++++ lib/utils.js | 14 ++++++++++++++ 6 files changed, 44 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4ed5675..04c5053 100644 --- a/README.md +++ b/README.md @@ -168,10 +168,12 @@ To run browser tests on BrowserStack infrastructure, you need to create a `brows * `key`: BrowserStack [access key](https://www.browserstack.com/accounts/local-testing) (Or `BROWSERSTACK_KEY` environment variable) * `test_path`: Path to the test page which will run the tests when opened in a browser. * `test_framework`: Specify test framework which will run the tests. Currently supporting qunit, jasmine, jasmine2 and mocha. + * `test_server_port`: Specify test server port that will be opened from BrowserStack. If not set the default port 8888 will be used. Find a [list of all supported ports on browerstack.com](https://www.browserstack.com/question/664). * `timeout`: Specify worker timeout with BrowserStack. * `browsers`: A list of browsers on which tests are to be run. Find a [list of all supported browsers and platforms on browerstack.com](https://www.browserstack.com/list-of-browsers-and-platforms?product=js_testing). * `build`: A string to identify your test run in Browserstack. In `TRAVIS` setup `TRAVIS_COMMIT` will be the default identifier. * `proxy`: Specify a proxy to use for the local tunnel. Object with `host`, `port`, `username` and `password` properties. + * `tunnel_pid`: Specify a path to file to save the tunnel process id into. A sample configuration file: @@ -181,6 +183,7 @@ A sample configuration file: "key": "", "test_framework": "qunit|jasmine|jasmine2|mocha", "test_path": ["relative/path/to/test/page1", "relative/path/to/test/page2"], + "test_server_port": "8899", "browsers": [ { "browser": "ie", diff --git a/bin/cli.js b/bin/cli.js index 1522a7c..65429d5 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -9,7 +9,6 @@ var Log = require('../lib/logger'), tunnel = require('tunnel'), http = require('http'), ConfigParser = require('../lib/configParser').ConfigParser, - serverPort = 8888, config, server, timeout, @@ -106,7 +105,7 @@ function getTestBrowserInfo(browserString, path) { } function buildTestUrl(test_path, worker_key, browser_string) { - var url = 'http://localhost:' + serverPort + '/' + test_path; + var url = 'http://localhost:' + config.test_server_port + '/' + test_path; var querystring = qs.stringify({ _worker_key: worker_key, @@ -119,11 +118,11 @@ function buildTestUrl(test_path, worker_key, browser_string) { } function launchServer(config, callback) { - logger.trace('launchServer:', serverPort); - logger.debug('Launching server on port:', serverPort); + logger.trace('launchServer:', config.test_server_port); + logger.debug('Launching server on port:', config.test_server_port); server = new Server(client, workers, config, callback); - server.listen(parseInt(serverPort, 10)); + server.listen(parseInt(config.test_server_port, 10)); } function launchBrowser(browser, path) { @@ -380,7 +379,7 @@ function runTests(config, callback) { launchServer(config, runTestsCallback); logger.trace('runTests: creating tunnel'); - tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, config, function (err) { + tunnel = new Tunnel(config.key, config.test_server_port, config.tunnelIdentifier, config, function (err) { if(err) { cleanUpAndExit(null, err, [], callback); } else { diff --git a/bin/runner.js b/bin/runner.js index aef3b9e..c6b11f3 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -4,7 +4,7 @@ var todo = process.argv[2], path = require('path'), config; -if (todo === '--verbose') { +if (process.argv.indexOf('--verbose') !== -1) { global.logLevel = process.env.LOG_LEVEL || 'debug'; } else { global.logLevel = 'info'; @@ -36,6 +36,12 @@ try { } } +// extract a path to file to store tunnel pid +var matches, pid = process.argv.find(function (param) { return param.indexOf('--pid') !== -1; }); +if (pid && (matches = /-pid=([\w\/\.-]+)/g.exec(pid))) { + config.tunnel_pid = matches[1]; +} + var runner = require('./cli.js'); runner.run(config, function(err) { if(err) { diff --git a/lib/config.js b/lib/config.js index 10e49c8..0aa6d78 100644 --- a/lib/config.js +++ b/lib/config.js @@ -81,4 +81,8 @@ exports.config = function(config) { for (var key in config) { this[key] = config[key]; } + + if (!this.test_server_port) { + this.test_server_port = 8888; + } }; diff --git a/lib/local.js b/lib/local.js index ef46884..30307cb 100644 --- a/lib/local.js +++ b/lib/local.js @@ -2,7 +2,9 @@ var Log = require('./logger'), logger = new Log(global.logLevel || 'info'), exec = require('child_process').execFile, fs = require('fs'), + path = require('path'), https = require('https'), + utils = require('./utils'), windows = ((process.platform.match(/win32/) || process.platform.match(/win64/)) !== null), localBinary = __dirname + '/BrowserStackLocal' + (windows ? '.exe' : ''); @@ -55,6 +57,11 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { } }); + if (config.tunnel_pid) { + utils.mkdirp(path.dirname(config.tunnel_pid)); + fs.writeFile(config.tunnel_pid, subProcess.pid); + } + that.process = subProcess; } @@ -99,6 +106,10 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { subProcess = null; } catch (e) { logger.debug('[%s] failed to kill process:', new Date(), e); + } finally { + if (config.tunnel_pid) { + fs.unlink(config.tunnel_pid, function () {}); + } } } diff --git a/lib/utils.js b/lib/utils.js index dab1f82..02d630d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,3 +1,6 @@ +var fs = require('fs'), + path = require('path'); + String.prototype.escapeSpecialChars = function() { return this.replace(/\n/g, '\\n') .replace(/\r/g, '\\r') @@ -43,7 +46,18 @@ var objectSize = function objectSize(obj) { return size; }; +var mkdirp = function mkdirp(filepath) { + var dirname = path.dirname(filepath); + if (!fs.existsSync(dirname)) { + mkdirp(dirname); + } + if (!fs.existsSync(filepath)) { + fs.mkdirSync(filepath); + } +}; + exports.titleCase = titleCase; exports.uuid = uuid; exports.browserString = browserString; exports.objectSize = objectSize; +exports.mkdirp = mkdirp; From db935b09a214c1cc2940ba61ec6811cd8b2a6b68 Mon Sep 17 00:00:00 2001 From: Ankur Goel Date: Thu, 27 Jul 2017 15:00:32 +0530 Subject: [PATCH 123/162] version bump for release --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4ed5675..2102c71 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ To run browser tests on BrowserStack infrastructure, you need to create a `brows * `username`: BrowserStack username (Or `BROWSERSTACK_USERNAME` environment variable) * `key`: BrowserStack [access key](https://www.browserstack.com/accounts/local-testing) (Or `BROWSERSTACK_KEY` environment variable) * `test_path`: Path to the test page which will run the tests when opened in a browser. - * `test_framework`: Specify test framework which will run the tests. Currently supporting qunit, jasmine, jasmine2 and mocha. + * `test_framework`: Specify test framework which will run the tests. Currently supporting qunit, jasmine, jasmine1.3.1, jasmine2 and mocha. * `timeout`: Specify worker timeout with BrowserStack. * `browsers`: A list of browsers on which tests are to be run. Find a [list of all supported browsers and platforms on browerstack.com](https://www.browserstack.com/list-of-browsers-and-platforms?product=js_testing). * `build`: A string to identify your test run in Browserstack. In `TRAVIS` setup `TRAVIS_COMMIT` will be the default identifier. diff --git a/package.json b/package.json index 6edb249..19650ac 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.5.1", + "version": "0.5.2", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From befb015947b42a68c674908aa1fc67dfbb15fbd1 Mon Sep 17 00:00:00 2001 From: Punit Mittal Date: Tue, 5 Dec 2017 16:22:53 +0530 Subject: [PATCH 124/162] Added support for content-encoding --- lib/proxy.js | 6 ++--- lib/server.js | 69 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/lib/proxy.js b/lib/proxy.js index 1c352dc..020aee9 100644 --- a/lib/proxy.js +++ b/lib/proxy.js @@ -13,13 +13,13 @@ var ProxyServer = { }; var proxy = http.request(options, function (res) { - var data = ''; + var chunks = []; res.on('data', function(chunk) { - data += chunk; + chunks.push(chunk); }); res.on('end', function() { //Replace - callback(res, data); + callback(res, Buffer.concat(chunks)); }); }).on('error', function(e) { client_res.writeHead(500); diff --git a/lib/server.js b/lib/server.js index f01d7e0..4f2160f 100644 --- a/lib/server.js +++ b/lib/server.js @@ -13,7 +13,8 @@ var Log = require('./logger'), send = require('send'), vm = require('vm'), CircularJSON = require('circular-json'), - resolve = require('resolve'); + resolve = require('resolve'), + zlib = require('zlib'); exports.Server = function Server(bsClient, workers, config, callback) { var testFilePaths = (Array.isArray(config.test_path) ? config.test_path : [ config.test_path ]) @@ -205,25 +206,61 @@ exports.Server = function Server(bsClient, workers, config, callback) { response.end(); }; + var patchResponse = function (data, headers, callback) { + var mimeType = mime.lookup(filename); + var finalData = data; + if (mimeType === 'text/html') { + var matcher = /(.*)<\/head>/; + var patch = getReporterPatch(); + finalData = data.replace(matcher, patch); + headers['content-length'] = finalData.length; + } + callback && callback(finalData, headers); + }; + + var checkForEncodingAndPatch = function (responseData, headers, callback) { + var encoding = headers['content-encoding']; + if (encoding === 'gzip') { + zlib.gunzip(responseData, function (err, decoded) { + if (!err) { + patchResponse(decoded && decoded.toString(), headers, function (data, headers) { + zlib.gzip(data, function (err, encoded) { + if (!err) { + callback && callback(encoded, headers); + } + }); + }); + } + }); + } else if (encoding === 'deflate') { + zlib.inflate(responseData, function (err, decoded) { + if (!err) { + patchResponse(decoded && decoded.toString(), headers, function (data, headers) { + zlib.deflate(data, function (err, encoded) { + if (!err) { + callback && callback(encoded, headers); + } + }); + }); + } + }); + } else { + patchResponse(responseData, headers, function (data, headers) { + callback && callback(data, headers); + }); + } + }; + if (!doNotUseProxy && config.test_server) { - proxyServer.onRequest(request, response, config.test_server, function(remote_response, response_data) { - var mimeType = mime.lookup(filename); - var final_data = response_data; + proxyServer.onRequest(request, response, config.test_server, function (remote_response, response_data) { var headers = remote_response.headers; - if (mimeType === 'text/html') { - var matcher = /(.*)<\/head>/; - var patch = getReporterPatch(); - final_data = response_data.replace(matcher, patch); - headers['content-length'] = final_data.length; - } - response.writeHead(remote_response.statusCode, headers); - response.write(final_data); - response.end(); - return; + checkForEncodingAndPatch(response_data, headers, function (data, headers) { + response.writeHead(remote_response.statusCode, headers); + response.write(data); + response.end(); + }); }); - } else { - fs.exists(filename, function(exists) { if (!exists) { sendError(response,'file not found', 404); From 4d876735b13fa51401c09da844e4ee37a0b27c4a Mon Sep 17 00:00:00 2001 From: Punit Mittal Date: Tue, 5 Dec 2017 18:24:41 +0530 Subject: [PATCH 125/162] Bumped version to 0.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19650ac..a9a2844 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.5.2", + "version": "0.6.0", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 9495f60fae6de1d512d911c19b212820a5a1e9a0 Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Thu, 7 Dec 2017 16:36:43 +0530 Subject: [PATCH 126/162] Adding --pid to README.md * Renaming config.tunnel_pid to config.tunnel_pid_file * Regex match for --pid instead of -pid * Error message on invalid flag --- README.md | 2 +- bin/runner.js | 12 +++++++++--- lib/local.js | 10 +++++----- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f155136..cc8138d 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ To run browser tests on BrowserStack infrastructure, you need to create a `brows * `build`: A string to identify your test run in Browserstack. In `TRAVIS` setup `TRAVIS_COMMIT` will be the default identifier. * `proxy`: Specify a proxy to use for the local tunnel. Object with `host`, `port`, `username` and `password` properties. * `exit_with_fail`: If set to true the cli process will exit with fail if any of the tests failed. Useful for automatic build systems. - * `tunnel_pid`: Specify a path to file to save the tunnel process id into. + * `tunnel_pid_file`: Specify a path to file to save the tunnel process id into. Can also by specified using the `--pid` flag while launching browserstack-runner from the command line. A sample configuration file: diff --git a/bin/runner.js b/bin/runner.js index 629de4a..d1abf0a 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -37,9 +37,15 @@ try { } // extract a path to file to store tunnel pid -var matches, pid = process.argv.find(function (param) { return param.indexOf('--pid') !== -1; }); -if (pid && (matches = /-pid=([\w\/\.-]+)/g.exec(pid))) { - config.tunnel_pid = matches[1]; +var pid = process.argv.find(function (param) { return param.indexOf('--pid') !== -1; }); + +if (pid) { + var extracted_path = /--pid=([\w\/\.-]+)/g.exec(pid); + if (extracted_path) { + config.tunnel_pid_file = extracted_path[1]; + } else { + console.error('Error while parsing flag --pid. Usage: --pid=/path/to/file'); + } } var runner = require('./cli.js'); diff --git a/lib/local.js b/lib/local.js index 30307cb..15b147c 100644 --- a/lib/local.js +++ b/lib/local.js @@ -57,9 +57,9 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { } }); - if (config.tunnel_pid) { - utils.mkdirp(path.dirname(config.tunnel_pid)); - fs.writeFile(config.tunnel_pid, subProcess.pid); + if (config.tunnel_pid_file) { + utils.mkdirp(path.dirname(config.tunnel_pid_file)); + fs.writeFile(config.tunnel_pid_file, subProcess.pid); } that.process = subProcess; @@ -107,8 +107,8 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { } catch (e) { logger.debug('[%s] failed to kill process:', new Date(), e); } finally { - if (config.tunnel_pid) { - fs.unlink(config.tunnel_pid, function () {}); + if (config.tunnel_pid_file) { + fs.unlink(config.tunnel_pid_file, function () {}); } } } From fb11e488ecac33e10ca8564a62bfdb9969afd00f Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Thu, 7 Dec 2017 12:39:06 +0100 Subject: [PATCH 127/162] Version bump to 0.7.0 (#194) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a9a2844..94dfdcf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.6.0", + "version": "0.7.0", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From cb3420704453b16bbf040a8e0b6a57ae2b02ff4e Mon Sep 17 00:00:00 2001 From: Chris Wisecarver Date: Wed, 6 Dec 2017 12:53:29 -0500 Subject: [PATCH 128/162] Use conditional tag matching logic everywhere --- lib/server.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/server.js b/lib/server.js index a5e799a..f2f7b16 100644 --- a/lib/server.js +++ b/lib/server.js @@ -187,6 +187,11 @@ exports.Server = function Server(bsClient, workers, config, callback) { } }; + var getTestingFrameworkMatcher = function() { + var tag_name = (config['test_framework'] === 'mocha') ? 'head' : 'body'; + return new RegExp('(.*)<\/' + tag_name + '>'); ///(.*)<\/body>/; + }; + var writeResponse = function(err, data) { if (err) { @@ -197,8 +202,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); - var tag_name = (config['test_framework'] === 'mocha') ? 'head' : 'body'; - var matcher = new RegExp('(.*)<\/' + tag_name + '>'); ///(.*)<\/body>/; + var matcher = getTestingFrameworkMatcher(); var patch = getReporterPatch(); data = data.replace(matcher, patch); @@ -210,7 +214,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { var mimeType = mime.lookup(filename); var finalData = data; if (mimeType === 'text/html') { - var matcher = /(.*)<\/head>/; + var matcher = getTestingFrameworkMatcher(); var patch = getReporterPatch(); finalData = data.replace(matcher, patch); headers['content-length'] = finalData.length; From c584dd5900d7f348a0da535447094b2fc7f45b73 Mon Sep 17 00:00:00 2001 From: Chris Wisecarver Date: Tue, 12 Dec 2017 15:51:59 -0500 Subject: [PATCH 129/162] Force patched responseData to a string --- lib/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index f2f7b16..6058ca1 100644 --- a/lib/server.js +++ b/lib/server.js @@ -249,7 +249,7 @@ exports.Server = function Server(bsClient, workers, config, callback) { } }); } else { - patchResponse(responseData, headers, function (data, headers) { + patchResponse(responseData && responseData.toString(), headers, function (data, headers) { callback && callback(data, headers); }); } From dba129b2951a674d490e975a24d4ec8ca4ddff4d Mon Sep 17 00:00:00 2001 From: Mohit Munjani Date: Tue, 6 Mar 2018 15:33:58 +0530 Subject: [PATCH 130/162] change host to bs-local on ios platform --- bin/cli.js | 16 +++++++++++----- tests/behaviour/runner.js | 9 ++++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 8dda992..01d6d51 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -104,9 +104,15 @@ function getTestBrowserInfo(browserString, path) { return info; } -function buildTestUrl(test_path, worker_key, browser_string) { - var url = 'http://localhost:' + config.test_server_port + '/' + test_path; - +function buildTestUrl(test_path, worker_key, browser) { + var host; + if (browser.os.toLowerCase() === 'ios' ){ + host = 'bs-local.com'; + } else { + host = 'localhost'; + } + var url = 'http://'+host+':' + config.test_server_port + '/' + test_path; + var browser_string = utils.browserString(browser); var querystring = qs.stringify({ _worker_key: worker_key, _browser_string: browser_string @@ -131,7 +137,7 @@ function launchBrowser(browser, path) { var browserInfo = getTestBrowserInfo(browserString, path); logger.debug('[%s] Launching', browserInfo); - browser.url = buildTestUrl(path.replace(/\\/g, '/'), key, browserString); + browser.url = buildTestUrl(path.replace(/\\/g, '/'), key, browser); if (config.project) { browser.project = config.project; @@ -195,7 +201,7 @@ function attachWorkerHelpers(worker) { worker.buildUrl = function buildUrl(test_path) { var workerKey = workerKeys[this.id] ? workerKeys[this.id].key : null; - var url = buildTestUrl(test_path || this.test_path, workerKey, this.getTestBrowserInfo()); + var url = buildTestUrl(test_path || this.test_path, workerKey, this.config); logger.trace('[%s] worker.buildUrl: %s', this.id, url); return url; }; diff --git a/tests/behaviour/runner.js b/tests/behaviour/runner.js index cebd9c2..3bcc4cb 100644 --- a/tests/behaviour/runner.js +++ b/tests/behaviour/runner.js @@ -26,6 +26,13 @@ var getBaseConfig = function() { browser_version: '52.0', os: 'Windows', os_version: '7' + }, { + browser: 'iphone', + browser_version: '', + device: 'iPhone SE', + os: 'ios', + os_version: '11.2', + real_mobile: true } ] } }; @@ -100,7 +107,7 @@ describe('Pass/Fail reporting', function() { it('report keys should have browser names', function(done) { var config = getBaseConfig(); browserstackRunner.run(config, function(err, reports) { - var shouldBePresentBrowsers = [ 'Windows 7, Chrome 52.0', 'Windows 7, Firefox 47.0' ]; + var shouldBePresentBrowsers = [ 'Windows 7, Chrome 52.0', 'Windows 7, Firefox 47.0', 'ios 11.2, Iphone ']; assert.equal(err, null); reports.forEach(function(report) { var numMatched = 0; From 630945f677be5d835767b0dd283a3068d230ee44 Mon Sep 17 00:00:00 2001 From: Mohit Munjani Date: Fri, 30 Mar 2018 12:23:30 +0530 Subject: [PATCH 131/162] recommended solution for travis build timeout --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index cc8138d..98cf69e 100644 --- a/README.md +++ b/README.md @@ -323,3 +323,13 @@ To run a larger suite of tests ensuring compatibility with older versions of QUn npm run test-ci Tests are also run for every pull request, courtesy [Travis CI](https://travis-ci.org/). + +### Timeout issue with Travis CI + +You might face [build timeout issue on Travis](https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received) if runner takes more than 10 minutes to run tests. + +There are 2 possible ways to solve this problem: + 1. Run a script which does `console.log` every 1-2 minutes. This will output to console and hence avoid Travis buildtimeout + 2. Use `travis_wait` function provided by Travis-CI. prefix `browserstack-runner` by `travis-wait` + +We would recommend using `travis_wait` function. It also allows you to configure wait time (ex: `travis_wait 20 browserstack-runner` this will extend wait time to 20 minutes). Read more about `travis_wait` [here](https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received) From 4175c7d5c6211b7f225ffa7671aa454be727e018 Mon Sep 17 00:00:00 2001 From: Mohit Munjani Date: Fri, 30 Mar 2018 12:28:04 +0530 Subject: [PATCH 132/162] update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 98cf69e..2a2f811 100644 --- a/README.md +++ b/README.md @@ -329,7 +329,7 @@ Tests are also run for every pull request, courtesy [Travis CI](https://travis-c You might face [build timeout issue on Travis](https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received) if runner takes more than 10 minutes to run tests. There are 2 possible ways to solve this problem: - 1. Run a script which does `console.log` every 1-2 minutes. This will output to console and hence avoid Travis buildtimeout - 2. Use `travis_wait` function provided by Travis-CI. prefix `browserstack-runner` by `travis-wait` + 1. Run a script which does `console.log` every 1-2 minutes. This will output to console and hence avoid Travis build timeout + 2. Use `travis_wait` function provided by Travis-CI. You can prefix `browserstack-runner` command by `travis-wait` in your `travis.yml` file -We would recommend using `travis_wait` function. It also allows you to configure wait time (ex: `travis_wait 20 browserstack-runner` this will extend wait time to 20 minutes). Read more about `travis_wait` [here](https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received) +We would recommend using `travis_wait` function. It also allows you to configure wait time (ex: `travis_wait 20 browserstack-runner`, this will extend wait time to 20 minutes). Read more about `travis_wait` [here](https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received) From ed8e7c370fc27ac094541c08311208fbd4b84e9b Mon Sep 17 00:00:00 2001 From: Mohit Munjani Date: Mon, 21 May 2018 19:02:20 +0530 Subject: [PATCH 133/162] dont export env variables from .travis.yml --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f488f2..45dbde0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,6 @@ node_js: before_install: - npm install -g grunt-cli jshint gulp - - true && `base64 --decode <<< ZXhwb3J0IEJST1dTRVJTVEFDS19VU0VSTkFNRT1icm93c2Vyc3RhY2tydW5uMQo=` - - true && `base64 --decode <<< ZXhwb3J0IEJST1dTRVJTVEFDS19LRVk9NW5qc0d5emdqNkpWMmh3eUh1dHEK` script: - npm run-script test-ci From c9b43692ddbe030e5bd5842fb93f94725ec41ab0 Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Sun, 5 Aug 2018 15:33:19 +0200 Subject: [PATCH 134/162] Fix invalid markdown syntax in README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a2f811..be18496 100644 --- a/README.md +++ b/README.md @@ -307,8 +307,7 @@ These can also be provided by a build server, for example [using secure environm ### Code Sample -Check out code sample [here]. -[here]:https://github.com/browserstack/browserstack-runner-sample +Check out code sample [here](https://github.com/browserstack/browserstack-runner-sample). ### Running Tests From f146ff08419ef5757572bd7733964731a7ec710d Mon Sep 17 00:00:00 2001 From: Mohit Munjani Date: Thu, 9 Aug 2018 17:34:47 +0530 Subject: [PATCH 135/162] Version bump for release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 94dfdcf..0faa8af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.7.0", + "version": "0.8.0", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 4a8c64a6db5dd73662ba986a396181818ca2b242 Mon Sep 17 00:00:00 2001 From: Pankaj Ahuja Date: Fri, 10 Aug 2018 19:20:46 +0530 Subject: [PATCH 136/162] Added command line interface using yargs. --- bin/init.js | 5 ++-- bin/runner.js | 71 +++++++++++++++++++++++++++++++++++--------- package.json | 3 +- presets/default.json | 24 ++++++++++----- 4 files changed, 78 insertions(+), 25 deletions(-) diff --git a/bin/init.js b/bin/init.js index 57640f4..2c0febb 100755 --- a/bin/init.js +++ b/bin/init.js @@ -1,13 +1,14 @@ #! /usr/bin/env node var fs = require('fs'); -var preset = process.argv[3] || 'default'; +var preset = require('./runner').preset; +var path = require('./runner').path; var browsers = require('../presets/' + preset + '.json'); var config = { username: 'BROWSERSTACK_USERNAME', key: 'BROWSERSTACK_KEY', - test_path: 'path/to/test/runner', + test_path: path || 'path/to/test/runner', browsers: browsers }; diff --git a/bin/runner.js b/bin/runner.js index d1abf0a..ba1c6f6 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -1,21 +1,53 @@ #! /usr/bin/env node -var todo = process.argv[2], - path = require('path'), - config; +var yargs = require('yargs') + .command('init [preset] [path]', 'initialise browserstack.json with preset and test runner path', function(yargs) { + return yargs.option('preset', { + type: 'string', + default: 'default', + description: 'name of preset json file(without extension)(present in node_modules/browserstack-runner/presets to be used while initiating' + }) + .option('path', { + type: 'string', + default: '/path/to/test/runner', + description: 'path to test runner to be inserted in browserstack.json' + }) + }) + .option('browsers', { + alias: 'b', + type: 'array', + description: 'list of space separatedbrowsers keys as described in json file' + }) + .option('path', { + type: 'string', + description: 'path to test file' + }) + .option('version', { + alias: 'V', + description: 'browserstack-runner version' + }) + .option('pid', { + type: 'string', + description: 'path to pid file' + }) + .option('verbose', { + alias: 'v', + description: 'verbose logging' + }).argv; -if (process.argv.indexOf('--verbose') !== -1) { +if (yargs['verbose']) { global.logLevel = process.env.LOG_LEVEL || 'debug'; } else { global.logLevel = 'info'; } +var path = require('path'), + config; -if (todo === 'init') { +if(yargs['_'].indexOf('init') !== -1) { + module.exports.preset = yargs['preset']; + module.exports.path = yargs['path']; require('./init.js'); return; -} else if (todo === '--version') { - require('./version.js'); - return; } var config_path = process.env.BROWSERSTACK_JSON || 'browserstack.json'; @@ -37,17 +69,28 @@ try { } // extract a path to file to store tunnel pid -var pid = process.argv.find(function (param) { return param.indexOf('--pid') !== -1; }); - -if (pid) { - var extracted_path = /--pid=([\w\/\.-]+)/g.exec(pid); - if (extracted_path) { - config.tunnel_pid_file = extracted_path[1]; +if(yargs['pid']) { + if(yargs['pid'].length > 0) { + config.tunnel_pid_file = yargs['pid']; } else { console.error('Error while parsing flag --pid. Usage: --pid=/path/to/file'); } } +// filter browsers according to from command line arguments +if(yargs['browsers']) { + if(yargs['browsers'].length > 0) { + config.browsers = config.browsers.filter( function(browser) { + return yargs['browsers'].indexOf(browser['cli_key']) !== -1; + }); + } else { + console.error('No browser keys specified. Usage --browsers ...'); + } +} + +// test file path from cli arguments +config.test_path = yargs['path'] || config.test_path; + var runner = require('./cli.js'); runner.run(config, function(err) { if(err) { diff --git a/package.json b/package.json index 94dfdcf..3c13a2b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "mime": "1.3.4", "resolve": "1.1.7", "send": "0.13.0", - "tunnel": "0.0.3" + "tunnel": "0.0.3", + "yargs": "^12.0.1" }, "devDependencies": { "jshint": "2.5.6", diff --git a/presets/default.json b/presets/default.json index 5498339..dcfcf53 100644 --- a/presets/default.json +++ b/presets/default.json @@ -3,48 +3,56 @@ "browser": "firefox", "browser_version": "latest", "os": "OS X", - "os_version": "Lion" + "os_version": "Lion", + "cli_key": 1 }, { "browser": "safari", "browser_version": "latest", "os": "OS X", - "os_version": "Mountain Lion" + "os_version": "Mountain Lion", + "cli_key": 2 }, { "browser": "chrome", "browser_version": "latest", "os": "OS X", - "os_version": "Mountain Lion" + "os_version": "Mountain Lion", + "cli_key": 3 }, { "browser": "firefox", "browser_version": "latest", "os": "Windows", - "os_version": "7" + "os_version": "7", + "cli_key": 4 }, { "browser": "chrome", "browser_version": "latest", "os": "Windows", - "os_version": "7" + "os_version": "7", + "cli_key": 5 }, { "browser": "ie", "browser_version": "9.0", "os": "Windows", - "os_version": "7" + "os_version": "7", + "cli_key": 6 }, { "browser": "ie", "browser_version": "10.0", "os": "Windows", - "os_version": "8" + "os_version": "8", + "cli_key": 7 }, { "browser": "ie", "browser_version": "11.0", "os": "Windows", - "os_version": "7" + "os_version": "7", + "cli_key": 8 } ] From e026e81bfbc070bed94686b18ba396cb86101798 Mon Sep 17 00:00:00 2001 From: Pankaj Ahuja Date: Mon, 13 Aug 2018 12:50:43 +0530 Subject: [PATCH 137/162] Fixed jshint error in runner.js --- bin/runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/runner.js b/bin/runner.js index ba1c6f6..55f95c8 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -11,7 +11,7 @@ var yargs = require('yargs') type: 'string', default: '/path/to/test/runner', description: 'path to test runner to be inserted in browserstack.json' - }) + }); }) .option('browsers', { alias: 'b', From 64ea3e45d44b0cf0878262fd2a3bf514520cbb9c Mon Sep 17 00:00:00 2001 From: Trent Willis Date: Fri, 17 Aug 2018 08:34:48 -0700 Subject: [PATCH 138/162] Update dependencies to fix vulnerabilities --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0faa8af..488045e 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ "chalk": "0.4.0", "circular-json": "0.3.1", "js-reporters": "1.1.0", - "mime": "1.3.4", + "mime": "1.6.0", "resolve": "1.1.7", - "send": "0.13.0", + "send": "0.16.2", "tunnel": "0.0.3" }, "devDependencies": { From 64818bcfe3ee645e851e03b31a3294be395098c1 Mon Sep 17 00:00:00 2001 From: Trent Willis Date: Fri, 17 Aug 2018 08:36:23 -0700 Subject: [PATCH 139/162] Update devDependencies to fix vulnerabilities --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 488045e..129f652 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "tunnel": "0.0.3" }, "devDependencies": { - "jshint": "2.5.6", - "mocha": "1.15.1", + "jshint": "2.9.6", + "mocha": "5.2.0", "sinon": "1.17.5" }, "licenses": [ From 826d2c7c9edbc442c1b3b0256f4115d7962bda15 Mon Sep 17 00:00:00 2001 From: Pat O'Callaghan Date: Fri, 18 May 2018 08:01:00 +0100 Subject: [PATCH 140/162] Don't clobbber window.console --- lib/_patch/browserstack.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/_patch/browserstack.js b/lib/_patch/browserstack.js index 31e98b8..e1ff85c 100644 --- a/lib/_patch/browserstack.js +++ b/lib/_patch/browserstack.js @@ -37,18 +37,12 @@ req.send(data); } - if (typeof console !== 'object') { - var console = {}; - window.console = console; - } - - _console_log = console.log; - - console.log = function () { + var browserstack_console = console || window.console || {}; + browserstack_console.log = function () { var args = BrowserStack.util.toArray(arguments).map(BrowserStack.util.inspect); post('/_log/', { arguments: args }, function () {}); }; - console.warn = function () { + browserstack_console.warn = function () { var args = BrowserStack.util.toArray(arguments).map(BrowserStack.util.inspect); post('/_log/', { arguments: args }, function () {}); }; @@ -60,4 +54,6 @@ BrowserStack.worker_uuid = getParameterByName('_worker_key'); window.BrowserStack = BrowserStack; + window.console = browserstack_console; + console = browserstack_console; })(); From 843c2cf6ec12d49b7e39a8488249c09a1686b972 Mon Sep 17 00:00:00 2001 From: Pankaj Ahuja Date: Wed, 22 Aug 2018 13:31:44 +0530 Subject: [PATCH 141/162] Updated Readme for CLI changes. --- README.md | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2a2f811..61dbc57 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,19 @@ If you're getting an error `EACCES open ... BrowserStackLocal`, configure npm to Where `[user]` is replaced with a local user with enough permissions. +CLI options: + +`--path`: Can be used if a different test runner is needed other than the one present in the `browserstack.json` file. + +`--pid`: Custom `pid` file that stores the pid's of the BrowserStackLocal instances created. + +`--verbose` or `-v`: For verbose logging. + +`--browsers` or `-b`: Space separated list of `cli_key` as defined in the `browserstack.json` file. This will run tests on the selected browsers only. If not present tests will run on all browsers present in the configuration file. + +Sample Usage: +`browserstack_runner --browsers 1 2 3 --path 'path/to/test/runner' --pid 'path/to/pid/file' -v` + ## Usage as a module `browserstack-runner` can also be used as a module. To run your tests, inside your project do - @@ -160,7 +173,11 @@ The structure of the `report` object is as follows - To run browser tests on BrowserStack infrastructure, you need to create a `browserstack.json` file in project's root directory (the directory from which tests are run), by running this command: - browserstack-runner init +`browserstack-runner init [preset] [path]` + +`preset`: Path of a custom preset file. Default: `presets/default.json` + +`path`: Path to test file. Default: `path/to/test/runner` ### Parameters for `browserstack.json` @@ -191,17 +208,20 @@ A sample configuration file: "browser_version": "10.0", "device": null, "os": "Windows", - "os_version": "8" + "os_version": "8", + "cli_key": 1 }, { "os": "android", "os_version": "4.0", - "device": "Samsung Galaxy Nexus" + "device": "Samsung Galaxy Nexus", + "cli_key": 2 }, { "os": "ios", "os_version": "7.0", - "device": "iPhone 5S" + "device": "iPhone 5S", + "cli_key": 3 } ] } @@ -209,7 +229,7 @@ A sample configuration file: #### `browsers` parameter -`browsers` parameter is a list of objects, where each object contains the details of the browsers on which you want to run your tests. This object differs for browsers on desktop platforms and browsers on mobile platforms. Browsers on desktop platform should contain `browser`, `browser_version`, `os `, `os_version` parameters set as required. +`browsers` parameter is a list of objects, where each object contains the details of the browsers on which you want to run your tests. This object differs for browsers on desktop platforms and browsers on mobile platforms. Browsers on desktop platform should contain `browser`, `browser_version`, `os`, `os_version` parameters set as required and the `cli_key` parameter is optional and can be used in the command line when tests need to be run on a set of browsers from the `browserstack.json` file. Example: ```json @@ -217,7 +237,8 @@ Example: "browser": "ie", "browser_version": "10.0", "os": "Windows", - "os_version": "8" + "os_version": "8", + "cli_key": 1 } ``` @@ -228,12 +249,14 @@ Example: [{ "os": "ios", "os_version": "8.3", - "device": "iPhone 6 Plus" + "device": "iPhone 6 Plus", + "cli_key": 1 }, { "os": "android", "os_version": "4.0", - "device": "Google Nexus" + "device": "Google Nexus", + "cli_key": 2 } ] ``` @@ -266,7 +289,8 @@ Example: "browser_version": "10.0", "device": null, "os": "Windows", - "os_version": "8" + "os_version": "8", + "cli_key": 1 } ] } From ad0a2e6cd506f62501926aae72474c6e0c474fbd Mon Sep 17 00:00:00 2001 From: Pat O'Callaghan Date: Fri, 17 Aug 2018 12:24:59 +0100 Subject: [PATCH 142/162] Add asserts for console --- tests/behaviour/resources/qunit_test1.js | 9 +++++++++ tests/behaviour/runner.js | 8 ++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/behaviour/resources/qunit_test1.js b/tests/behaviour/resources/qunit_test1.js index aad2800..f596e55 100644 --- a/tests/behaviour/resources/qunit_test1.js +++ b/tests/behaviour/resources/qunit_test1.js @@ -1,4 +1,13 @@ QUnit.module('Partial Tests', function() { + QUnit.test('console Tests', function(assert) { + // console functions should exist + assert.ok(typeof console.info === 'function', 'console.info exists'); + assert.ok(typeof console.warn === 'function', 'console.warn exists'); + assert.ok(typeof console.log === 'function', 'console.log exists'); + assert.ok(typeof console.error === 'function', 'console.error exists'); + assert.ok(typeof console.debug === 'function', 'console.debug exists'); + }); + QUnit.test('Partial Tests', function(assert) { // Fails assert.ok(isOdd(2), '2 is an odd number'); diff --git a/tests/behaviour/runner.js b/tests/behaviour/runner.js index 3bcc4cb..03316bf 100644 --- a/tests/behaviour/runner.js +++ b/tests/behaviour/runner.js @@ -146,7 +146,7 @@ describe('Pass/Fail reporting', function() { browserstackRunner.run(config, function(err, reports) { assert.equal(err, null); reports.forEach(function(report) { - assert.equal(report.tests.length, 3); + assert.equal(report.tests.length, 4); }); done(); }); @@ -177,7 +177,7 @@ describe('Pass/Fail reporting', function() { reports.forEach(function(report) { Object.keys(report.tests).forEach(function(reportKey) { report.tests[reportKey].assertions.forEach(function(assertion) { - assert.notEqual(assertion['message'].match(/\d+ is .*an .* number/), null); + assert.notEqual(assertion['message'].match(/(\d+ is .*an .* number|console\..*? exists)/), null); }); }); }); @@ -216,8 +216,8 @@ describe('Pass/Fail reporting', function() { browserstackRunner.run(config, function(err, reports) { assert.equal(err, null); reports.forEach(function(report) { - assert.equal(report.suites.testCounts['total'], 3); - assert.equal(report.suites.testCounts['passed'], 1); + assert.equal(report.suites.testCounts['total'], 4); + assert.equal(report.suites.testCounts['passed'], 2); assert.equal(report.suites.testCounts['failed'], 2); assert.equal(report.suites.testCounts['skipped'], 0); }); From 6c1b658760de670f8de4521eff41d928f85d163d Mon Sep 17 00:00:00 2001 From: Pankaj Ahuja Date: Tue, 28 Aug 2018 16:04:25 +0530 Subject: [PATCH 143/162] Tests added for CLI --- bin/runner.js | 16 +++++++++++----- tests/behaviour/runner.js | 40 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/bin/runner.js b/bin/runner.js index 55f95c8..94fdec0 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -69,11 +69,11 @@ try { } // extract a path to file to store tunnel pid -if(yargs['pid']) { - if(yargs['pid'].length > 0) { - config.tunnel_pid_file = yargs['pid']; +if(yargs.hasOwnProperty('pid')) { + if(yargs['pid'].trim().length > 0) { + config.tunnel_pid_file = yargs['pid'].trim(); } else { - console.error('Error while parsing flag --pid. Usage: --pid=/path/to/file'); + throw new Error('Empty pid file path'); } } @@ -84,7 +84,13 @@ if(yargs['browsers']) { return yargs['browsers'].indexOf(browser['cli_key']) !== -1; }); } else { - console.error('No browser keys specified. Usage --browsers ...'); + throw new Error('No browser keys specified. Usage --browsers ...'); + } + if(config.browsers.length === 0) { + throw new Error('Invalid browser keys'); + } + if(config.browsers.length < yargs['browsers'].length) { + console.warn('Some browser keys not present in config file.'); } } diff --git a/tests/behaviour/runner.js b/tests/behaviour/runner.js index cebd9c2..9ae32b4 100644 --- a/tests/behaviour/runner.js +++ b/tests/behaviour/runner.js @@ -7,7 +7,9 @@ var assert = require('assert'), path = require('path'), http = require('http'), browserstackRunner = require('../../bin/cli.js'), - Tunnel = require('../../lib/local.js').Tunnel; + Tunnel = require('../../lib/local.js').Tunnel, + exec = require('child_process').exec, + execSync = require('child_process').execSync; var getBaseConfig = function() { return { @@ -219,3 +221,39 @@ describe('Pass/Fail reporting', function() { }); }); }); + +describe('Command Line Interface Tests', function() { + this.timeout(0); + it('Should run with valid CLI arguments', function(done) { + execSync('bin/runner.js init'); + exec('bin/runner.js --browsers 1 --path tests/behaviour/resources/qunit_sample.html', null, function(error, stdout, stderr) { + assert.equal(error, null); + done(); + }); + }); + it('Should raise errors if all invalid browser keys.', function(done) { + exec('bin/runner.js --browsers 10 --path tests/behaviour/resources/qunit_sample.html', null, function(error, stdout, stderr) { + assert.notEqual(error.message.match('Invalid'), null); + done(); + }); + }); + it('Should raise error if invalid test path', function(done) { + exec('bin/runner.js --browsers 1 --path invalid/path', function(error, stdout, stderr) { + assert.notEqual(error, null); + assert.notEqual(error.message.match('Invalid'), null); + done(); + }); + }); + it('Should run tests on browsers present if some keys not present', function(done) { + exec('bin/runner.js --browsers 1 10 --path tests/behaviour/resources/qunit_sample.html', null, function(error, stdout, stderr) { + assert.equal(error, null); + done(); + }); + }); + it('Should raise error if empty pid path with pid parameter', function(done) { + exec('bin/runner.js --browsers 1 --path tests/behaviour/resources/qunit_sample.html --pid', null, function(error, stdout, stderr) { + assert.notEqual(error, null); + done(); + }); + }); +}); From a53fb31cc0f60ff09d4a9e5087d2b23558d3c202 Mon Sep 17 00:00:00 2001 From: Mohit Munjani Date: Thu, 30 Aug 2018 15:54:03 +0530 Subject: [PATCH 144/162] Version bump for release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1cbbfb0..66b9d46 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.8.0", + "version": "0.9.0", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 307e0647746bf1499c2a3d9ae2a78fee98fde3e4 Mon Sep 17 00:00:00 2001 From: Hitesh Raghuvanshi Date: Tue, 4 Sep 2018 18:05:42 +0530 Subject: [PATCH 145/162] Adding issue template --- .github/issue_template.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/issue_template.md diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..be63a00 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,19 @@ + + +## Expected Behavior + + +## Actual Behavior + + +## Steps to Reproduce the Problem + + 1. + 2. + 3. + +## browserstack.json + + +## Details + From 41986ac4c99643256e5a47c86c0d6b5181c9cd6a Mon Sep 17 00:00:00 2001 From: Hitesh Raghuvanshi Date: Tue, 25 Sep 2018 13:31:49 +0530 Subject: [PATCH 146/162] adding version info to issue template --- .github/issue_template.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/issue_template.md b/.github/issue_template.md index be63a00..c1c8f68 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -15,5 +15,11 @@ ## browserstack.json +## Platform details + + 1. browserstack-runner version: + 2. node version: + 3. os type and version: + ## Details From a11592b3f68e722ca3cecbcde12dbc183c4c2646 Mon Sep 17 00:00:00 2001 From: Yash Ladha Date: Thu, 30 Jan 2020 00:43:52 +0530 Subject: [PATCH 147/162] Version bumped up --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 66b9d46..584c4ab 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.9.0", + "version": "0.9.1", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 078de1f5e76d26c10239b2cdf1ab03b43a6d53a5 Mon Sep 17 00:00:00 2001 From: Rohan Immanuel Date: Mon, 22 Jun 2020 15:17:18 +0530 Subject: [PATCH 148/162] updated yargs version to 15.3.1 --- package-lock.json | 1903 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 1904 insertions(+), 1 deletion(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..444fa13 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1903 @@ +{ + "name": "browserstack-runner", + "version": "0.9.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "dev": true, + "optional": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=" + }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + }, + "async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true, + "optional": true + }, + "available-typed-arrays": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", + "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "dev": true, + "requires": { + "array-filter": "^1.0.0" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserstack": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.3.0.tgz", + "integrity": "sha1-hDgFPvasu4RNxrKRUQwZQznrUN8=" + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "optional": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true, + "optional": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true, + "optional": true + }, + "chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "requires": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + } + }, + "circular-json": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz", + "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=" + }, + "cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "dev": true, + "requires": { + "exit": "0.1.2", + "glob": "^7.1.1" + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true, + "optional": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "optional": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "optional": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=", + "dev": true, + "optional": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "requires": { + "xregexp": "4.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "optional": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + }, + "entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", + "dev": true + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true, + "optional": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "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 + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "optional": true + }, + "extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "dev": true, + "optional": true, + "requires": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "optional": true + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", + "dev": true, + "optional": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "optional": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "optional": true + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "optional": true, + "requires": { + "pend": "~1.2.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "optional": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formatio": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", + "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", + "dev": true, + "requires": { + "samsam": "~1.1" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true, + "optional": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, + "optional": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "optional": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "hasha": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", + "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", + "dev": true, + "optional": true, + "requires": { + "is-stream": "^1.0.1", + "pinkie-promise": "^2.0.0" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typed-array": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", + "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.0", + "es-abstract": "^1.17.4", + "foreach": "^2.0.5", + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true, + "optional": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true, + "optional": true + }, + "js-reporters": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/js-reporters/-/js-reporters-1.1.0.tgz", + "integrity": "sha1-yDwA/g1Mn2f5RLTt1fOylXSXzWI=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "jshint": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.6.tgz", + "integrity": "sha512-KO9SIAKTlJQOM4lE64GQUtGBRpTOuvbrRrSZw3AhUxMNG266nX9hK2cKA4SBhXOj0irJGyNyGSLT62HGOVDEOA==", + "dev": true, + "requires": { + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.10", + "minimatch": "~3.0.2", + "phantom": "~4.0.1", + "phantomjs-prebuilt": "~2.1.7", + "shelljs": "0.3.x", + "strip-json-comments": "1.0.x", + "unicode-5.2.0": "^0.7.5" + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true, + "optional": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "optional": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, + "optional": true + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kew": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", + "dev": true, + "optional": true + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "lolex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", + "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true, + "optional": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "optional": true, + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true, + "optional": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "optional": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "optional": true + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true, + "optional": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true, + "optional": true + }, + "phantom": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/phantom/-/phantom-4.0.12.tgz", + "integrity": "sha512-Tz82XhtPmwCk1FFPmecy7yRGZG2btpzY2KI9fcoPT7zT9det0CcMyfBFPp1S8DqzsnQnm8ZYEfdy528mwVtksA==", + "dev": true, + "optional": true, + "requires": { + "phantomjs-prebuilt": "^2.1.16", + "split": "^1.0.1", + "winston": "^2.4.0" + } + }, + "phantomjs-prebuilt": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", + "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", + "dev": true, + "optional": true, + "requires": { + "es6-promise": "^4.0.3", + "extract-zip": "^1.6.5", + "fs-extra": "^1.0.0", + "hasha": "^2.2.0", + "kew": "^0.7.0", + "progress": "^1.1.8", + "request": "^2.81.0", + "request-progress": "^2.0.1", + "which": "^1.2.10" + } + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "optional": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "optional": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "optional": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true, + "optional": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true, + "optional": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "optional": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true, + "optional": true + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "request-progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", + "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", + "dev": true, + "optional": true, + "requires": { + "throttleit": "^1.0.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + }, + "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==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "optional": true + }, + "samsam": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", + "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", + "dev": true + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + } + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "sinon": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.5.tgz", + "integrity": "sha1-EDjLqDDjcBLpmmSDfs07ZyAMBYw=", + "dev": true, + "requires": { + "formatio": "1.1.1", + "lolex": "1.3.2", + "samsam": "1.1.2", + "util": ">=0.10.3 <1" + } + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "optional": true, + "requires": { + "through": "2" + } + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "optional": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true, + "optional": true + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true, + "optional": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true, + "optional": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "optional": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.3.tgz", + "integrity": "sha1-6PmIEVynvp0HbHofrkeIvnCPDPE=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true, + "optional": true + }, + "unicode-5.2.0": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/unicode-5.2.0/-/unicode-5.2.0-0.7.5.tgz", + "integrity": "sha512-KVGLW1Bri30x00yv4HNM8kBxoqFXr0Sbo55735nvrlsx4PYBZol3UtoWgO492fSwmsetzPEZzy73rbU8OGXJcA==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "optional": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", + "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "safe-buffer": "^5.1.2", + "which-typed-array": "^1.1.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true, + "optional": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true, + "optional": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "which-typed-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", + "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.2", + "es-abstract": "^1.17.5", + "foreach": "^2.0.5", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-typed-array": "^1.1.3" + } + }, + "winston": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.5.tgz", + "integrity": "sha512-TWoamHt5yYvsMarGlGEQE59SbJHqGsZV8/lwC+iCcGeAe0vUaOh+Lv6SYM17ouzC/a/LB1/hz/7sxFBtlu1l4A==", + "dev": true, + "optional": true, + "requires": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yargs": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz", + "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==", + "requires": { + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "requires": { + "camelcase": "^4.1.0" + } + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "optional": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/package.json b/package.json index 584c4ab..3b4e92c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "resolve": "1.1.7", "send": "0.16.2", "tunnel": "0.0.3", - "yargs": "12.0.1" + "yargs": "15.3.1" }, "devDependencies": { "jshint": "2.9.6", From 4b54c3a6baeb6536a874b063807709bfa262056b Mon Sep 17 00:00:00 2001 From: Yash Ladha Date: Mon, 6 Jul 2020 13:25:48 +0530 Subject: [PATCH 149/162] 0.9.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 444fa13..dc18ac0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "browserstack-runner", - "version": "0.9.1", + "version": "0.9.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3b4e92c..45d0a96 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.9.1", + "version": "0.9.2", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From 4b897c334ae20a387e645d95682bca646b721c72 Mon Sep 17 00:00:00 2001 From: Yash Ladha Date: Thu, 30 Jul 2020 22:00:44 +0530 Subject: [PATCH 150/162] add: LOCAL_BINARY_PATH variable to the config User can use the already present local binary on their file system for setting up the local tunnel on their system. As a fallback it will use the legacy path so that the build doesn't break for already running users. --- README.md | 7 ++++--- bin/cli.js | 4 ++-- lib/local.js | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0f81e86..54b8771 100644 --- a/README.md +++ b/README.md @@ -320,6 +320,7 @@ To avoid duplication of system or user specific information across several confi * `BROWSERSTACK_KEY`: BrowserStack key. * `TUNNEL_ID`: Identifier for the current instance of the tunnel process. In `TRAVIS` setup `TRAVIS_JOB_ID` will be the default identifier. * `BROWSERSTACK_JSON`: Path to the browserstack.json file. If null, `browserstack.json` in the root directory will be used. +* `BROWSERSTACK_LOCAL_BINARY_PATH`: Path to the browserstack local binary present on the system. If null, `BrowserStackLocal` in the `lib/` directory will be used. ### Secure Information @@ -348,10 +349,10 @@ To run a larger suite of tests ensuring compatibility with older versions of QUn Tests are also run for every pull request, courtesy [Travis CI](https://travis-ci.org/). ### Timeout issue with Travis CI - -You might face [build timeout issue on Travis](https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received) if runner takes more than 10 minutes to run tests. -There are 2 possible ways to solve this problem: +You might face [build timeout issue on Travis](https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received) if runner takes more than 10 minutes to run tests. + +There are 2 possible ways to solve this problem: 1. Run a script which does `console.log` every 1-2 minutes. This will output to console and hence avoid Travis build timeout 2. Use `travis_wait` function provided by Travis-CI. You can prefix `browserstack-runner` command by `travis-wait` in your `travis.yml` file diff --git a/bin/cli.js b/bin/cli.js index 01d6d51..5262736 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -421,11 +421,11 @@ function runTests(config, callback) { } } -exports.run = function(user_config, callback) { +exports.run = function(userConfig, callback) { callback = callback || function() {}; try { - config = new (require('../lib/config').config)(user_config); + config = new (require('../lib/config').config)(userConfig); client = BrowserStack.createClient({ username: config.username, diff --git a/lib/local.js b/lib/local.js index 15b147c..9e05877 100644 --- a/lib/local.js +++ b/lib/local.js @@ -11,6 +11,8 @@ var Log = require('./logger'), var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { var that = {}; + localBinary = process.env.BROWSERSTACK_LOCAL_BINARY_PATH || localBinary; + function tunnelLauncher() { var tunnelOptions = getTunnelOptions(key, uniqueIdentifier); From d3ee2ba65638265ae117c042b5a41625203dbfb4 Mon Sep 17 00:00:00 2001 From: Yash Ladha Date: Thu, 30 Jul 2020 22:05:15 +0530 Subject: [PATCH 151/162] chore: lint markdown in README --- README.md | 147 +++++++++++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 54b8771..269106c 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,12 @@ Sample Usage: `browserstack-runner` can also be used as a module. To run your tests, inside your project do - ```node -var browserstackRunner = require('browserstack-runner'); +var browserstackRunner = require("browserstack-runner"); -var config = require('./browserstack.json'); +var config = require("./browserstack.json"); browserstackRunner.run(config, function(error, report) { - if(error) { + if (error) { console.log("Error:" + error); return; } @@ -58,6 +58,7 @@ browserstackRunner.run(config, function(error, report) { ``` The callback to `browserstackRunner.run` is called with two params - + - `error`: This parameter is either `null` or an `Error` object (if test execution failed) with message as the reason of why executing the tests on `BrowserStack` failed. - `report`: This is an array which can be used to keep track of the executed tests and suites in a run. Each object in the array has the following keys - - `browser`: The name of the browser the test executed on. @@ -74,10 +75,7 @@ The structure of the `report` object is as follows - { "name": "isOdd()", "suiteName": "Odd Tests", - "fullName": [ - "Odd Tests", - "isOdd()" - ], + "fullName": ["Odd Tests", "isOdd()"], "status": "passed", "runtime": 2, "errors": [], @@ -108,18 +106,13 @@ The structure of the `report` object is as follows - "childSuites": [ { "name": "Odd Tests", - "fullName": [ - "Odd Tests" - ], + "fullName": ["Odd Tests"], "childSuites": [], "tests": [ { "name": "isOdd()", "suiteName": "Odd Tests", - "fullName": [ - "Odd Tests", - "isOdd()" - ], + "fullName": ["Odd Tests", "isOdd()"], "status": "passed", "runtime": 2, "errors": [], @@ -181,17 +174,17 @@ To run browser tests on BrowserStack infrastructure, you need to create a `brows ### Parameters for `browserstack.json` - * `username`: BrowserStack username (Or `BROWSERSTACK_USERNAME` environment variable) - * `key`: BrowserStack [access key](https://www.browserstack.com/accounts/local-testing) (Or `BROWSERSTACK_KEY` environment variable) - * `test_path`: Path to the test page which will run the tests when opened in a browser. - * `test_framework`: Specify test framework which will run the tests. Currently supporting qunit, jasmine, jasmine1.3.1, jasmine2 and mocha. - * `test_server_port`: Specify test server port that will be opened from BrowserStack. If not set the default port 8888 will be used. Find a [list of all supported ports on browerstack.com](https://www.browserstack.com/question/664). - * `timeout`: Specify worker timeout with BrowserStack. - * `browsers`: A list of browsers on which tests are to be run. Find a [list of all supported browsers and platforms on browerstack.com](https://www.browserstack.com/list-of-browsers-and-platforms?product=js_testing). - * `build`: A string to identify your test run in Browserstack. In `TRAVIS` setup `TRAVIS_COMMIT` will be the default identifier. - * `proxy`: Specify a proxy to use for the local tunnel. Object with `host`, `port`, `username` and `password` properties. - * `exit_with_fail`: If set to true the cli process will exit with fail if any of the tests failed. Useful for automatic build systems. - * `tunnel_pid_file`: Specify a path to file to save the tunnel process id into. Can also by specified using the `--pid` flag while launching browserstack-runner from the command line. +- `username`: BrowserStack username (Or `BROWSERSTACK_USERNAME` environment variable) +- `key`: BrowserStack [access key](https://www.browserstack.com/accounts/local-testing) (Or `BROWSERSTACK_KEY` environment variable) +- `test_path`: Path to the test page which will run the tests when opened in a browser. +- `test_framework`: Specify test framework which will run the tests. Currently supporting qunit, jasmine, jasmine1.3.1, jasmine2 and mocha. +- `test_server_port`: Specify test server port that will be opened from BrowserStack. If not set the default port 8888 will be used. Find a [list of all supported ports on browerstack.com](https://www.browserstack.com/question/664). +- `timeout`: Specify worker timeout with BrowserStack. +- `browsers`: A list of browsers on which tests are to be run. Find a [list of all supported browsers and platforms on browerstack.com](https://www.browserstack.com/list-of-browsers-and-platforms?product=js_testing). +- `build`: A string to identify your test run in Browserstack. In `TRAVIS` setup `TRAVIS_COMMIT` will be the default identifier. +- `proxy`: Specify a proxy to use for the local tunnel. Object with `host`, `port`, `username` and `password` properties. +- `exit_with_fail`: If set to true the cli process will exit with fail if any of the tests failed. Useful for automatic build systems. +- `tunnel_pid_file`: Specify a path to file to save the tunnel process id into. Can also by specified using the `--pid` flag while launching browserstack-runner from the command line. A sample configuration file: @@ -232,32 +225,35 @@ A sample configuration file: `browsers` parameter is a list of objects, where each object contains the details of the browsers on which you want to run your tests. This object differs for browsers on desktop platforms and browsers on mobile platforms. Browsers on desktop platform should contain `browser`, `browser_version`, `os`, `os_version` parameters set as required and the `cli_key` parameter is optional and can be used in the command line when tests need to be run on a set of browsers from the `browserstack.json` file. Example: + ```json { - "browser": "ie", - "browser_version": "10.0", - "os": "Windows", - "os_version": "8", - "cli_key": 1 + "browser": "ie", + "browser_version": "10.0", + "os": "Windows", + "os_version": "8", + "cli_key": 1 } ``` For mobile platforms, `os`, `os_version` and `device` parameters are required. Example: + ```json -[{ - "os": "ios", - "os_version": "8.3", - "device": "iPhone 6 Plus", - "cli_key": 1 -}, -{ - "os": "android", - "os_version": "4.0", - "device": "Google Nexus", - "cli_key": 2 -} +[ + { + "os": "ios", + "os_version": "8.3", + "device": "iPhone 6 Plus", + "cli_key": 1 + }, + { + "os": "android", + "os_version": "4.0", + "device": "Google Nexus", + "cli_key": 2 + } ] ``` @@ -266,48 +262,52 @@ For a full list of supported browsers, platforms and other details, [visit the B #### Compact `browsers` configuration When `os` and `os_version` granularity is not desired, following configuration can be used: - * `[browser]_current` or *browser*_latest: will assign the latest version of the *browser*. - * `[browser]_previous`: will assign the previous version of the *browser*. - * `[browser]_[version]`: will assign the *version* specified of the *browser*. Minor versions can be concatenated with underscores. + +- `[browser]_current` or _browser_\_latest: will assign the latest version of the _browser_. +- `[browser]_previous`: will assign the previous version of the _browser_. +- `[browser]_[version]`: will assign the _version_ specified of the _browser_. Minor versions can be concatenated with underscores. This can also be mixed with fine-grained configuration. Example: + ```json { "browsers": [ - "chrome_previous", - "chrome_latest", - "firefox_previous", - "firefox_latest", - "ie_6", - "ie_11", - "opera_12_1", - "safari_5_1", - { - "browser": "ie", - "browser_version": "10.0", - "device": null, - "os": "Windows", - "os_version": "8", - "cli_key": 1 - } + "chrome_previous", + "chrome_latest", + "firefox_previous", + "firefox_latest", + "ie_6", + "ie_11", + "opera_12_1", + "safari_5_1", + { + "browser": "ie", + "browser_version": "10.0", + "device": null, + "os": "Windows", + "os_version": "8", + "cli_key": 1 + } ] } ``` + **Note:** These shortcuts work only for browsers on desktop platforms supported by BrowserStack. ### Proxy support for BrowserStack local Add the following in `browserstack.json` + ```json { "proxy": { - "host": "localhost", - "port": 3128, - "username": "foo", - "password": "bar" + "host": "localhost", + "port": 3128, + "username": "foo", + "password": "bar" } } ``` @@ -316,12 +316,11 @@ Add the following in `browserstack.json` To avoid duplication of system or user specific information across several configuration files, use these environment variables: -* `BROWSERSTACK_USERNAME`: BrowserStack user name. -* `BROWSERSTACK_KEY`: BrowserStack key. -* `TUNNEL_ID`: Identifier for the current instance of the tunnel process. In `TRAVIS` setup `TRAVIS_JOB_ID` will be the default identifier. -* `BROWSERSTACK_JSON`: Path to the browserstack.json file. If null, `browserstack.json` in the root directory will be used. -* `BROWSERSTACK_LOCAL_BINARY_PATH`: Path to the browserstack local binary present on the system. If null, `BrowserStackLocal` in the `lib/` directory will be used. - +- `BROWSERSTACK_USERNAME`: BrowserStack user name. +- `BROWSERSTACK_KEY`: BrowserStack key. +- `TUNNEL_ID`: Identifier for the current instance of the tunnel process. In `TRAVIS` setup `TRAVIS_JOB_ID` will be the default identifier. +- `BROWSERSTACK_JSON`: Path to the browserstack.json file. If null, `browserstack.json` in the root directory will be used. +- `BROWSERSTACK_LOCAL_BINARY_PATH`: Path to the browserstack local binary present on the system. If null, `BrowserStackLocal` in the `lib/` directory will be used. ### Secure Information @@ -329,7 +328,6 @@ To avoid checking in the BrowserStack `username` and `key` in your source contro These can also be provided by a build server, for example [using secure environment variables on Travis CI](http://about.travis-ci.org/docs/user/build-configuration/#Secure-environment-variables). - ### Code Sample Check out code sample [here](https://github.com/browserstack/browserstack-runner-sample). @@ -353,7 +351,8 @@ Tests are also run for every pull request, courtesy [Travis CI](https://travis-c You might face [build timeout issue on Travis](https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received) if runner takes more than 10 minutes to run tests. There are 2 possible ways to solve this problem: - 1. Run a script which does `console.log` every 1-2 minutes. This will output to console and hence avoid Travis build timeout - 2. Use `travis_wait` function provided by Travis-CI. You can prefix `browserstack-runner` command by `travis-wait` in your `travis.yml` file + +1. Run a script which does `console.log` every 1-2 minutes. This will output to console and hence avoid Travis build timeout +2. Use `travis_wait` function provided by Travis-CI. You can prefix `browserstack-runner` command by `travis-wait` in your `travis.yml` file We would recommend using `travis_wait` function. It also allows you to configure wait time (ex: `travis_wait 20 browserstack-runner`, this will extend wait time to 20 minutes). Read more about `travis_wait` [here](https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received) From 46b1c4e929815fd4d6aa3a79500c83737c478a0f Mon Sep 17 00:00:00 2001 From: francisf Date: Tue, 4 Aug 2020 14:36:42 +0530 Subject: [PATCH 152/162] 0.9.3 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc18ac0..9a2552f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "browserstack-runner", - "version": "0.9.2", + "version": "0.9.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 45d0a96..313046c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.9.2", + "version": "0.9.3", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From e1494bdd1c93ee13c89a91999c7d8a01cd895dab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Sep 2020 02:18:18 +0000 Subject: [PATCH 153/162] Bump yargs-parser from 10.1.0 to 18.1.3 Bumps [yargs-parser](https://github.com/yargs/yargs-parser) from 10.1.0 to 18.1.3. - [Release notes](https://github.com/yargs/yargs-parser/releases) - [Changelog](https://github.com/yargs/yargs-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/yargs-parser/compare/v10.1.0...v18.1.3) Signed-off-by: dependabot[bot] --- package-lock.json | 366 +++++++++++++++------------------------------- 1 file changed, 121 insertions(+), 245 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a2552f..626d3c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,11 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, "ajv": { "version": "6.12.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", @@ -18,9 +23,9 @@ } }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { "version": "1.0.0", @@ -138,11 +143,6 @@ "dev": true, "optional": true }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -176,29 +176,37 @@ } }, "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" }, "dependencies": { "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.0" } } } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "colors": { "version": "1.0.3", @@ -292,16 +300,6 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, "cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -334,12 +332,9 @@ } }, "decamelize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", - "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", - "requires": { - "xregexp": "4.0.0" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "define-properties": { "version": "1.1.3", @@ -438,6 +433,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -502,20 +502,6 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -581,11 +567,12 @@ } }, "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "requires": { - "locate-path": "^3.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "foreach": { @@ -652,14 +639,9 @@ "dev": true }, "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "getpass": { "version": "0.1.7", @@ -810,11 +792,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, "is-arguments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", @@ -834,9 +811,9 @@ "dev": true }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-generator-function": { "version": "1.0.7", @@ -856,7 +833,9 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "optional": true }, "is-symbol": { "version": "1.0.3", @@ -895,7 +874,9 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true, + "optional": true }, "isstream": { "version": "0.1.2", @@ -996,21 +977,12 @@ "graceful-fs": "^4.1.9" } }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" - } - }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, "lodash": { @@ -1025,23 +997,6 @@ "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", "dev": true }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "requires": { - "mimic-fn": "^1.0.0" - } - }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -1064,11 +1019,6 @@ "mime-db": "1.44.0" } }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1159,19 +1109,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -1220,21 +1157,6 @@ "wrappy": "1" } }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -1244,11 +1166,11 @@ } }, "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "requires": { - "p-limit": "^2.0.0" + "p-limit": "^2.2.0" } }, "p-try": { @@ -1257,9 +1179,9 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "path-is-absolute": { "version": "1.0.1", @@ -1267,11 +1189,6 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -1347,11 +1264,6 @@ "dev": true, "optional": true }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -1435,9 +1347,9 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "resolve": { "version": "1.1.7", @@ -1500,30 +1412,12 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, "shelljs": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", "dev": true }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" - }, "sinon": { "version": "1.17.5", "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.5.tgz", @@ -1577,20 +1471,21 @@ "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" }, "dependencies": { "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.0" } } } @@ -1626,11 +1521,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=" }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, "strip-json-comments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", @@ -1760,6 +1650,8 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "optional": true, "requires": { "isexe": "^2.0.0" } @@ -1799,43 +1691,30 @@ } }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^5.0.0" } } } @@ -1846,46 +1725,43 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "xregexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", - "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==" - }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, "yargs": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz", - "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==", - "requires": { - "cliui": "^4.0.0", - "decamelize": "^2.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^4.2.0", "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^10.1.0" + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" } }, "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + } } }, "yauzl": { From 177883a2e1ec82f08ceed9b14512c39fabd17e41 Mon Sep 17 00:00:00 2001 From: Saransh Date: Tue, 15 Dec 2020 11:55:12 +0530 Subject: [PATCH 154/162] using last stable version when using compact config --- lib/configParser.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/configParser.js b/lib/configParser.js index 6afdb1b..7ef7e1d 100644 --- a/lib/configParser.js +++ b/lib/configParser.js @@ -36,13 +36,17 @@ var ConfigParser = { return parseFloat(a) - parseFloat(b); }); if (verStr === 'current' || verStr === 'latest') { - return filteredBrowsers[filteredBrowsers.length - 1]; + return ConfigParser.checkIfLatestFlagApplicable(browserObject) ? 'latest' : filteredBrowsers[filteredBrowsers.length - 1]; } else if (verStr === 'previous') { - return filteredBrowsers[filteredBrowsers.length - 2]; + return ConfigParser.checkIfLatestFlagApplicable(browserObject) ? 'latest-1' : filteredBrowsers[filteredBrowsers.length - 2]; } }, + checkIfLatestFlagApplicable: function(browserObject) { + return !browserObject.mobile && ['chrome', 'firefox', 'edge'].includes(browserObject.browser) + }, + populateOsAndOsVersion: function(browserObject) { if (!(browserObject.os && browserObject.os_version)) { if (browserObject.mobile) { @@ -56,13 +60,18 @@ var ConfigParser = { else { var windowsFiltered = ConfigParser.bsBrowsers.filter(function(currentValue) { - return currentValue.os === 'Windows' && currentValue.browser_version.match(/metro/i) == null && currentValue.browser === browserObject.browser && parseFloat(currentValue.browser_version).toPrecision(4) === parseFloat(browserObject.browser_version).toPrecision(4); + return currentValue.os === 'Windows' && currentValue.browser_version.match(/metro/i) == null && currentValue.browser === browserObject.browser && ((browserObject.browser_version && browserObject.browser_version.indexOf('latest') > -1) || parseFloat(currentValue.browser_version).toPrecision(4) === parseFloat(browserObject.browser_version).toPrecision(4)); }); var osxFiltered = ConfigParser.bsBrowsers.filter(function(currentValue) { - return currentValue.os === 'OS X' && currentValue.browser === browserObject.browser && parseFloat(currentValue.browser_version).toPrecision(4) === parseFloat(browserObject.browser_version).toPrecision(4); + return currentValue.os === 'OS X' && currentValue.browser === browserObject.browser && ((browserObject.browser_version && browserObject.browser_version.indexOf('latest')) > -1 || parseFloat(currentValue.browser_version).toPrecision(4) === parseFloat(browserObject.browser_version).toPrecision(4)); }); - browserObject = windowsFiltered.length > 0 ? windowsFiltered[Math.floor(Math.random() * windowsFiltered.length)] : osxFiltered[Math.floor(Math.random() * osxFiltered.length)]; + + let filteredObject = windowsFiltered.length > 0 ? windowsFiltered[Math.floor(Math.random() * windowsFiltered.length)] : osxFiltered[Math.floor(Math.random() * osxFiltered.length)]; + if (browserObject.browser_version.indexOf('latest') > -1) { + filteredObject.browser_version = browserObject.browser_version + } + browserObject = filteredObject } } From d1356675a61cf1e965733e2bb2362fc43258f4a9 Mon Sep 17 00:00:00 2001 From: Saransh Date: Tue, 15 Dec 2020 14:24:53 +0530 Subject: [PATCH 155/162] sanity --- lib/configParser.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/configParser.js b/lib/configParser.js index 7ef7e1d..7e440f7 100644 --- a/lib/configParser.js +++ b/lib/configParser.js @@ -44,7 +44,7 @@ var ConfigParser = { }, checkIfLatestFlagApplicable: function(browserObject) { - return !browserObject.mobile && ['chrome', 'firefox', 'edge'].includes(browserObject.browser) + return !browserObject.mobile && browserObject.browser && ['chrome', 'firefox', 'edge'].includes(browserObject.browser.toLowerCase()); }, populateOsAndOsVersion: function(browserObject) { @@ -69,9 +69,9 @@ var ConfigParser = { let filteredObject = windowsFiltered.length > 0 ? windowsFiltered[Math.floor(Math.random() * windowsFiltered.length)] : osxFiltered[Math.floor(Math.random() * osxFiltered.length)]; if (browserObject.browser_version.indexOf('latest') > -1) { - filteredObject.browser_version = browserObject.browser_version + filteredObject.browser_version = browserObject.browser_version; } - browserObject = filteredObject + browserObject = filteredObject; } } From 536788ee69c94eb98f4fba5fa9a5e7968ec5b467 Mon Sep 17 00:00:00 2001 From: Yash Ladha Date: Wed, 16 Dec 2020 16:11:08 +0530 Subject: [PATCH 156/162] 0.9.4 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 626d3c7..5d97221 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "browserstack-runner", - "version": "0.9.3", + "version": "0.9.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 313046c..e84a44e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.9.3", + "version": "0.9.4", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From d032d595841c5bde8dbec0e1be35eef8d7623292 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Mon, 12 Apr 2021 02:00:19 +0100 Subject: [PATCH 157/162] Fix crash from property 'browser_version' undefined When no match is found, the process would just crash. Instead, recognise this as a normal end-user scenario and point them to the list of available workers. In my experience the most common way to encounter this issue is when additional decimal places are required by the API, e.g. "safari" "9" results in a crash, because the available workers are described as "9.1" instead. Given that most browers are described without a decimal place, this is an easy mistake to make. Another way to get this is when there is a misspelled browser name. Before: ``` TypeError: Cannot read property 'browser_version' of undefined at browserstack-runner/bin/cli.js ``` After: ``` Error: No desktop match found for {"browser":"safari","browser_version":"9"} Check https://www.browserstack.com/list-of-browsers-and-platforms/js_testing at populateOsAndOsVersion (browserstack-runner/lib/configParser.js) ``` Fixes https://github.com/browserstack/browserstack-runner/issues/243. --- lib/configParser.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/configParser.js b/lib/configParser.js index 7e440f7..b88995c 100644 --- a/lib/configParser.js +++ b/lib/configParser.js @@ -3,6 +3,8 @@ var Log = require('./logger'), logger = new Log(global.logLevel || 'info'); +var BROWSER_LIST_URL = 'https://www.browserstack.com/list-of-browsers-and-platforms/js_testing'; + var ConfigParser = { finalBrowsers: [], @@ -54,10 +56,12 @@ var ConfigParser = { var mobileFiltered = ConfigParser.bsBrowsers.filter(function(currentValue) { return currentValue.browser.toLowerCase() === browserObject.browser && parseFloat(currentValue.os_version).toPrecision(4) === parseFloat(browserObject.os_version).toPrecision(4); }); + if (!mobileFiltered.length) { + throw new Error('No mobile match found for ' + JSON.stringify(browserObject) + '\nCheck ' + BROWSER_LIST_URL); + } browserObject = mobileFiltered[Math.floor(Math.random() * mobileFiltered.length)]; - } - else { + } else { var windowsFiltered = ConfigParser.bsBrowsers.filter(function(currentValue) { return currentValue.os === 'Windows' && currentValue.browser_version.match(/metro/i) == null && currentValue.browser === browserObject.browser && ((browserObject.browser_version && browserObject.browser_version.indexOf('latest') > -1) || parseFloat(currentValue.browser_version).toPrecision(4) === parseFloat(browserObject.browser_version).toPrecision(4)); @@ -66,8 +70,13 @@ var ConfigParser = { var osxFiltered = ConfigParser.bsBrowsers.filter(function(currentValue) { return currentValue.os === 'OS X' && currentValue.browser === browserObject.browser && ((browserObject.browser_version && browserObject.browser_version.indexOf('latest')) > -1 || parseFloat(currentValue.browser_version).toPrecision(4) === parseFloat(browserObject.browser_version).toPrecision(4)); }); + // Use Windows VMs if no OS specified + var desktopFiltered = windowsFiltered.length > 0 ? windowsFiltered : osxFiltered; - let filteredObject = windowsFiltered.length > 0 ? windowsFiltered[Math.floor(Math.random() * windowsFiltered.length)] : osxFiltered[Math.floor(Math.random() * osxFiltered.length)]; + if (!desktopFiltered.length) { + throw new Error('No desktop match found for ' + JSON.stringify(browserObject) + '\nCheck ' + BROWSER_LIST_URL); + } + var filteredObject = desktopFiltered[Math.floor(Math.random() * desktopFiltered.length)]; if (browserObject.browser_version.indexOf('latest') > -1) { filteredObject.browser_version = browserObject.browser_version; } @@ -82,15 +91,14 @@ var ConfigParser = { var browserObject = {}; var version = null; var sliceStart = 1; - if (typeof(entry) === 'string') { + if (typeof entry === 'string') { var browserData = entry.split('_'); var lindex = browserData.length - 1; if (browserData[0] === 'mobile' || browserData[0] === 'android' || (browserData[0] === 'opera' && browserData[1] === 'browser')) { browserObject.browser = browserData[0] + ' ' + browserData[1]; browserObject.mobile = true; sliceStart = 2; - } - else { + } else { browserObject.browser = browserData[0]; } if (browserData[lindex] && browserData[lindex].indexOf('+') === -1) { @@ -100,15 +108,13 @@ var ConfigParser = { else { version = browserData.slice(sliceStart, lindex + 1).join('.'); } - } - else { + } else { version = browserData.slice(sliceStart, lindex + 1).join('.'); } if (browserObject.mobile) { browserObject.os_version = version; browserObject.browser_version = null; - } - else { + } else { browserObject.browser_version = version; } } else { From d75cbb482b0e717cbed7bb3d756ace747ba44263 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Sun, 11 Apr 2021 22:21:46 +0100 Subject: [PATCH 158/162] Fix broken BrowserStackLocal binary after first test Currently after installing browserstack-runner you can run one test. After that, the command is broken. There are numerous symptoms from this: * BrowserStackLocal gets re-downloaded every single time. * an ETXTBSY error appears because the previous file is still busy. Here is what happens on later runs: * `fs.exists(localBinary)` finds the binary. * `runTunnelCmd(['-version'])` gets the following output: ``` $ node_modules/browserstack-runner/lib/BrowserStackLocal -version Sun Apr 11 2021 20:59:41 GMT+0000 (UTC) -- BrowserStackLocal v8.1 Sun Apr 11 2021 20:59:41 GMT+0000 (UTC) -- Container runtime environment detected Sun Apr 11 2021 20:59:41 GMT+0000 (UTC) -- Attaching services to public interface BrowserStack Local version 8.1 ``` * Only the first line of data is checked by `subProcess.stdout.on('data')`. * The line does not match `/version\s+(\d)/`. It reports: `Tunnel binary: found version null`. * The script invokes `callbackOnce()` and will try to kill the process. This may succeed, but it is not immediate as there are additional output lines still in the data buffer which must be processed first, which is not possible since we are still indirectly in the call stack of the first `data` stdout callback. * The re-download begins, but the operating system does not permit writing to a currently executing binary, since it will not shutdown until a few milliseconds later. * We fail with `Error: ETXTBSY: text file is busy`. All of this happens only because `-version` is interpreted as both `-v` for verbose and `--version` at the same time, the verbose output comes first and does not match the regex. Changing it to `--version` solves the problem. This code should also be changed so that it actually waits for this subprocess to exit. Perhaps falling back to unlinking the file if we can't close it so that at least the redownload will work. But, that is another issue and will not be a problem in most cases. Fixes https://github.com/browserstack/browserstack-runner/issues/224. --- lib/local.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/local.js b/lib/local.js index 9e05877..f5fad2d 100644 --- a/lib/local.js +++ b/lib/local.js @@ -151,7 +151,7 @@ var Tunnel = function Tunnel(key, port, uniqueIdentifier, config, callback) { function getTunnelBinaryVersion(callback) { var subProcessTimeout = 3000; - runTunnelCmd([ '-version' ], subProcessTimeout, function (data, done) { + runTunnelCmd([ '--version' ], subProcessTimeout, function (data, done) { var matches = /version\s+(\d+(\.\d+)*)/.exec(data); var version = (matches && matches.length > 2) && matches[1]; logger.debug('[%s] Tunnel binary: found version', new Date(), version); From d76b3dfb678aea9aa6d8e9175d5e2855d8102bf4 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Sun, 11 Apr 2021 21:56:47 +0100 Subject: [PATCH 159/162] Fix bad console assignment Follows-up 826d2c7c9edbc4. Before that commit, the reason global "console" was globbered is that the `var console` part of the inner `var console = {}` assignment was hosted by the JavaScript engine. Thus the variable always existed in the local scope as type "undefined", and so the conditional check never saw the real console, and always created a custom one. Thus it always deleted the original console reference, as well as any other it contained. In commit 826d2c7c9edbc4, this was incorrectly fixed by assigning the unchecked expression referring to `console`, thus no longer having a fallback to `{}` in older browsers, because an unchecked reference like that throws an Uncaught ReferenceError. As a result, browserstack-runner was unable to run in IE 9 or older. Fixes https://github.com/browserstack/browserstack-runner/issues/161. Fixes https://github.com/browserstack/browserstack-runner/issues/164. Fixes https://github.com/browserstack/browserstack-runner/issues/212. --- lib/_patch/browserstack.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/_patch/browserstack.js b/lib/_patch/browserstack.js index e1ff85c..a1fd54e 100644 --- a/lib/_patch/browserstack.js +++ b/lib/_patch/browserstack.js @@ -37,7 +37,11 @@ req.send(data); } - var browserstack_console = console || window.console || {}; + // Change some console method to capture the logs. + // This must not replace the console object itself so that other console methods + // from the browser or added by the tested application remain unaffected. + // https://github.com/browserstack/browserstack-runner/pull/199 + var browserstack_console = window.console || {}; browserstack_console.log = function () { var args = BrowserStack.util.toArray(arguments).map(BrowserStack.util.inspect); post('/_log/', { arguments: args }, function () {}); @@ -54,6 +58,7 @@ BrowserStack.worker_uuid = getParameterByName('_worker_key'); window.BrowserStack = BrowserStack; + // If the browser didn't have a console object (old IE), then this will create it. + // Otherwise this is a no-op as it will assign the same object it already held. window.console = browserstack_console; - console = browserstack_console; })(); From 1e85e559951bdf97ffe2a7c744ee67ca83589fde Mon Sep 17 00:00:00 2001 From: Yash Ladha Date: Wed, 21 Apr 2021 16:01:30 +0530 Subject: [PATCH 160/162] 0.9.5 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5d97221..ecd9f4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "browserstack-runner", - "version": "0.9.4", + "version": "0.9.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e84a44e..276f7f2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "browserstack-runner", "description": "A command line interface to run browser tests over BrowserStack", - "version": "0.9.4", + "version": "0.9.5", "homepage": "https://github.com/browserstack/browserstack-runner", "repository": { "type": "git", From af361c509baa8a06d98bc4ec19241d45c8a4eb4a Mon Sep 17 00:00:00 2001 From: bstack-security-github <116066275+bstack-security-github@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:20:42 +0530 Subject: [PATCH 161/162] Adding Code Scanner Semgrep.yml workflow file --- .github/workflows/Semgrep.yml | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/Semgrep.yml diff --git a/.github/workflows/Semgrep.yml b/.github/workflows/Semgrep.yml new file mode 100644 index 0000000..0347afd --- /dev/null +++ b/.github/workflows/Semgrep.yml @@ -0,0 +1,48 @@ +# Name of this GitHub Actions workflow. +name: Semgrep + +on: + # Scan changed files in PRs (diff-aware scanning): + # The branches below must be a subset of the branches above + pull_request: + branches: ["master", "main"] + push: + branches: ["master", "main"] + schedule: + - cron: '0 6 * * *' + + +permissions: + contents: read + +jobs: + semgrep: + # User definable name of this GitHub Actions job. + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + name: semgrep/ci + # If you are self-hosting, change the following `runs-on` value: + runs-on: ubuntu-latest + + container: + # A Docker image with Semgrep installed. Do not change this. + image: returntocorp/semgrep + + # Skip any PR created by dependabot to avoid permission issues: + if: (github.actor != 'dependabot[bot]') + + steps: + # Fetch project source with GitHub Actions Checkout. + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + # Run the "semgrep ci" command on the command line of the docker image. + - run: semgrep ci --sarif --output=semgrep.sarif + env: + # Add the rules that Semgrep uses by setting the SEMGREP_RULES environment variable. + SEMGREP_RULES: p/default # more at semgrep.dev/explore + + - name: Upload SARIF file for GitHub Advanced Security Dashboard + uses: github/codeql-action/upload-sarif@6c089f53dd51dc3fc7e599c3cb5356453a52ca9e # v2.20.0 + with: + sarif_file: semgrep.sarif + if: always() \ No newline at end of file From 5ee5ad0eb28c23275972592997a7605da70fcfa1 Mon Sep 17 00:00:00 2001 From: bstack-security-github <116066275+bstack-security-github@users.noreply.github.com> Date: Mon, 3 Jul 2023 20:24:53 +0530 Subject: [PATCH 162/162] Adding CODEOWNERS file --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..c9eea17 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @browserstack/automate-public-repos