|
1 | 1 | #! /usr/bin/env node
|
2 | 2 |
|
3 |
| -var executable = process.argv[2] || 'runner'; |
| 3 | +if (process.argv[2] == '--verbose') |
| 4 | + global.logLevel = "debug"; |
| 5 | +else |
| 6 | + global.logLevel = "info"; |
| 7 | + |
| 8 | +var Log = require('../lib/logger'), |
| 9 | + logger = new Log(global.logLevel), |
| 10 | + BrowserStack = require('browserstack'), |
| 11 | + fs = require('fs'), |
| 12 | + chalk = require('chalk'), |
| 13 | + config = require('../lib/config'), |
| 14 | + utils = require('../lib/utils'), |
| 15 | + Server = require('../lib/server').Server, |
| 16 | + Tunnel = require('../lib/local').Tunnel, |
| 17 | + ConfigParser = require('../lib/configParser').ConfigParser, |
| 18 | + serverPort = 8888, |
| 19 | + timeout, |
| 20 | + activityTimeout, |
| 21 | + workers = {}, |
| 22 | + workerKeys = {}, |
| 23 | + logLevel, |
| 24 | + tunnel; |
| 25 | + |
| 26 | +function cleanUp(signal) { |
| 27 | + try { |
| 28 | + server.close(); |
| 29 | + } catch (e) { |
| 30 | + logger.debug("Server already closed"); |
| 31 | + } |
| 32 | + |
| 33 | + logger.info("Exiting"); |
| 34 | + |
| 35 | + for (var key in workers) { |
| 36 | + var worker = workers[key]; |
| 37 | + if (workers.hasOwnProperty(key)) { |
| 38 | + client.terminateWorker(worker.id, function () { |
| 39 | + if (!workers[key]) { |
| 40 | + return; |
| 41 | + } |
| 42 | + |
| 43 | + logger.debug('[%s] Terminated', worker.string); |
| 44 | + clearTimeout(worker.activityTimeout); |
| 45 | + delete workers[key]; |
| 46 | + delete workerKeys[worker.id]; |
| 47 | + }); |
| 48 | + } |
| 49 | + } |
| 50 | + if (statusPoller) statusPoller.stop(); |
| 51 | + |
| 52 | + try { |
| 53 | + process.kill(tunnel.process.pid, 'SIGKILL'); |
| 54 | + } catch (e) { |
| 55 | + logger.debug("Non existent tunnel"); |
| 56 | + } |
| 57 | + try { |
| 58 | + fs.unlinkSync(pid_file); |
| 59 | + } catch (e) { |
| 60 | + logger.debug("Non existent pid file."); |
| 61 | + } |
| 62 | + if (signal) { |
| 63 | + process.kill(process.pid, 'SIGTERM'); |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +function launchServer() { |
| 68 | + logger.debug("Launching server on port:", serverPort); |
| 69 | + |
| 70 | + var server = new Server(client, workers); |
| 71 | + server.listen(parseInt(serverPort, 10)); |
| 72 | +} |
| 73 | + |
| 74 | +function launchBrowser(browser, url) { |
| 75 | + var browserString = utils.browserString(browser); |
| 76 | + logger.debug("[%s] Launching", browserString); |
| 77 | + |
| 78 | + var key = utils.uuid(); |
| 79 | + |
| 80 | + if (url.indexOf('?') > 0) { |
| 81 | + url += '&'; |
| 82 | + } else { |
| 83 | + url += '?'; |
| 84 | + } |
| 85 | + |
| 86 | + url += '_worker_key=' + key + '&_browser_string=' + browserString; |
| 87 | + browser['url'] = url; |
| 88 | + |
| 89 | + if (config.project) { |
| 90 | + browser.project = config.project; |
| 91 | + } |
| 92 | + if (config.build) { |
| 93 | + browser.build = config.build; |
| 94 | + } |
| 95 | + |
| 96 | + if(config.tunnelIdentifier) { |
| 97 | + browser["tunnel_identifier"] = config.tunnelIdentifier; |
| 98 | + } |
| 99 | + |
| 100 | + timeout = parseInt(config.timeout); |
| 101 | + if(! isNaN(timeout)) { |
| 102 | + browser.timeout = timeout; |
| 103 | + } else { |
| 104 | + timeout = 300; |
| 105 | + } |
| 106 | + activityTimeout = timeout - 10; |
| 107 | + |
| 108 | + client.createWorker(browser, function (err, worker) { |
| 109 | + if (err || typeof worker !== 'object') { |
| 110 | + logger.info("Error from BrowserStack: ", err); |
| 111 | + utils.alertBrowserStack("Failed to launch worker", |
| 112 | + "Arguments: " + JSON.stringify({ |
| 113 | + err: err, |
| 114 | + worker: worker |
| 115 | + }, null, 4)); |
| 116 | + return; |
| 117 | + } |
| 118 | + |
| 119 | + worker.config = browser; |
| 120 | + worker.string = browserString; |
| 121 | + workers[key] = worker; |
| 122 | + workerKeys[worker.id] = {key: key, marked: false}; |
| 123 | + }); |
| 124 | + |
| 125 | +} |
| 126 | + |
| 127 | +function launchBrowsers(config, browser) { |
| 128 | + setTimeout(function () { |
| 129 | + if(Object.prototype.toString.call(config.test_path) === '[object Array]'){ |
| 130 | + config.test_path.forEach(function(path){ |
| 131 | + var url = 'http://localhost:' + serverPort.toString() + '/' + path; |
| 132 | + launchBrowser(browser,url); |
| 133 | + }); |
| 134 | + } else { |
| 135 | + var url = 'http://localhost:' + serverPort.toString() + '/' + config.test_path; |
| 136 | + launchBrowser(browser,url); |
| 137 | + } |
| 138 | + }, 100); |
| 139 | +} |
| 140 | + |
| 141 | +var statusPoller = { |
| 142 | + poller: null, |
| 143 | + |
| 144 | + start: function() { |
| 145 | + statusPoller.poller = setInterval(function () { |
| 146 | + client.getWorkers(function (err, _workers) { |
| 147 | + _workers = _workers.filter(function(currentValue, index, array) { |
| 148 | + return currentValue.status == 'running' && workerKeys[currentValue.id] && !workerKeys[currentValue.id].marked; |
| 149 | + }); |
| 150 | + for (var i in _workers) { |
| 151 | + var _worker = _workers[i]; |
| 152 | + var workerData = workerKeys[_worker.id]; |
| 153 | + var worker = workers[workerData.key]; |
| 154 | + if (worker.launched) { |
| 155 | + return; |
| 156 | + } |
| 157 | + |
| 158 | + if (_worker.status === 'running') { |
| 159 | + //clearInterval(statusPoller); |
| 160 | + logger.debug('[%s] Launched', worker.string); |
| 161 | + worker.launched = true; |
| 162 | + workerData.marked = true; |
| 163 | + |
| 164 | + worker.activityTimeout = setTimeout(function () { |
| 165 | + if (!worker.acknowledged) { |
| 166 | + var subject = "Worker inactive for too long: " + worker.string; |
| 167 | + var content = "Worker details:\n" + JSON.stringify(worker.config, null, 4); |
| 168 | + utils.alertBrowserStack(subject, content, null, function(){}); |
| 169 | + delete workers[workerData.key]; |
| 170 | + delete workerKeys[worker.id]; |
| 171 | + config.status += 1; |
| 172 | + if (utils.objectSize(workers) === 0) { |
| 173 | + var color = config.status > 0 ? "red" : "green"; |
| 174 | + logger.info(chalk[color]("All tests done, failures: %d."), config.status); |
| 175 | + |
| 176 | + if (config.status > 0) { |
| 177 | + config.status = 1; |
| 178 | + } |
| 179 | + |
| 180 | + process.exit(config.status); |
| 181 | + } |
| 182 | + } |
| 183 | + }, activityTimeout * 1000); |
| 184 | + |
| 185 | + setTimeout(function () { |
| 186 | + if (worker.acknowledged) { |
| 187 | + var subject = "Tests timed out on: " + worker.string; |
| 188 | + var content = "Worker details:\n" + JSON.stringify(worker.config, null, 4); |
| 189 | + utils.alertBrowserStack(subject, content, null, function(){}); |
| 190 | + delete workers[workerData.key]; |
| 191 | + delete workerKeys[worker.id]; |
| 192 | + config.status += 1; |
| 193 | + if (utils.objectSize(workers) === 0) { |
| 194 | + var color = config.status > 0 ? "red" : "green"; |
| 195 | + logger.info(chalk[color]("All tests done, failures: %d."), config.status); |
| 196 | + |
| 197 | + if (config.status > 0) { |
| 198 | + config.status = 1; |
| 199 | + } |
| 200 | + |
| 201 | + process.exit(config.status); |
| 202 | + } |
| 203 | + } |
| 204 | + }, (activityTimeout * 1000)); |
| 205 | + } |
| 206 | + } |
| 207 | + }); |
| 208 | + }, 2000); |
| 209 | + }, |
| 210 | + |
| 211 | + stop: function() { |
| 212 | + clearInterval(statusPoller.poller); |
| 213 | + } |
| 214 | +}; |
| 215 | + |
| 216 | +function runTests() { |
| 217 | + if (config.browsers && config.browsers.length > 0) { |
| 218 | + ConfigParser.parse(client, config.browsers, function(browsers){ |
| 219 | + launchServer(); |
| 220 | + tunnel = new Tunnel(config.key, serverPort, config.tunnelIdentifier, function () { |
| 221 | + statusPoller.start(); |
| 222 | + logger.info("Launching " + browsers.length + " workers"); |
| 223 | + browsers.forEach(function(browser) { |
| 224 | + if (browser.browser_version === "latest") { |
| 225 | + logger.debug("[%s] Finding version.", utils.browserString(browser)); |
| 226 | + |
| 227 | + client.getLatest(browser, function(err, version) { |
| 228 | + logger.debug("[%s] Version is %s.", |
| 229 | + utils.browserString(browser), version); |
| 230 | + browser.browser_version = version; |
| 231 | + // So that all latest logs come in together |
| 232 | + launchBrowsers(config, browser); |
| 233 | + }); |
| 234 | + } else { |
| 235 | + launchBrowsers(config, browser); |
| 236 | + } |
| 237 | + }); |
| 238 | + }); |
| 239 | + }); |
| 240 | + } |
| 241 | +} |
4 | 242 |
|
5 | 243 | try {
|
6 |
| - require('./' + executable + '.js'); |
| 244 | + if (process.argv[2] == 'init') { |
| 245 | + require('./init.js'); |
| 246 | + } else { |
| 247 | + var client = BrowserStack.createClient({ |
| 248 | + username: config.username, |
| 249 | + password: config.key |
| 250 | + }); |
| 251 | + runTests(); |
| 252 | + var pid_file = process.cwd() + '/browserstack-run.pid'; |
| 253 | + fs.writeFileSync(pid_file, process.pid, 'utf-8') |
| 254 | + process.on('exit', function() {cleanUp(false)}); |
| 255 | + process.on('SIGINT', function() {cleanUp(true)}); |
| 256 | + } |
7 | 257 | } catch (e) {
|
8 | 258 | console.log(e);
|
9 | 259 | console.log('Invalid command.');
|
10 | 260 | }
|
| 261 | + |
0 commit comments