UNPKG

15.2 kBPlain TextView Raw
1#!/usr/bin/env node
2
3/**********************************
4 * Usage
5 *
6 * browser-stack realtime-host=<host>
7 * realtime-port=<port>
8 * realtime-tls-port=<tls_port>
9 * --manual-browser-launch (use if you don't want a browser to be launched so that tests can be run manually in browsers \from browserstack.com)
10 * [--all | browser-ids comma/space separated] (defaults to reduced browser list unless -all is specified)
11 *
12 * args: realtime_host, realtime_port and realtime_tls_port are optional, however they need to be provided unless
13 * you wish to run the tests against sandbox-realtime.ably.io:80 and sandbox-realtime.ably.io:443
14 *
15 * Note there is a shortcut arg: realtime=<host>,<port>,<tls-port>
16 *
17 * REALTIME_HOST, REALTIME_PORT, REALTIME_TLS_PORT shell environment variables will be used as the default if set
18 *
19 **********************************/
20
21var fs = require('fs'),
22 server = require('./browser-srv/lib/server'),
23 testvars = require('./browser-srv/framework/testvars'),
24 browserStack = require('browserstack'),
25 childProcess = require('child_process'),
26 path = require('path'),
27 inspect = require('util').inspect,
28 async = require('async'),
29 config,
30 browserClient,
31 currentBrowserClientId,
32 tunnelProcess,
33 tunnelOutput = '',
34 browsers,
35 browserQueue = [],
36 results = [],
37 testsCompleteCallback,
38 cleanedUp = false;
39
40var TEST_TIMEOUT = 120, // time we allow for the tests to run in an external browser
41 TEST_GRACE = 10, // time we allow for browser start up to get tests running
42 SERVER_HOST = 'localhost',
43 SERVER_PORT = 8092,
44 REALTIME_HOST = process.env.REALTIME_HOST || 'sandbox-realtime.ably.io',
45 REALTIME_PORT = process.env.REALTIME_PORT || 80,
46 REALTIME_TLS_PORT = process.env.REALTIME_TLS_PORT || 443,
47 DEFAULT_BROWSERS = ['ie_xp_6','chrome_win7_25','firefox_win8_18','firefox_xp_3.6','safari_leopard_4','chrome_mountain_lion_26','android_lg_nexus_4','ios_5_safari_6','android_htc_wildfire'],
48 doNotLaunchBrowsersAutomatically = false,
49 browserStackTestKey = 'automated_testing_tunnel_key',
50 useFlashPolicyServer = false;
51
52// gracefully handle termination of worker and report error to the console if there was a problem killing the worker at BrowserStack
53function terminateWorker(workerId, callback) {
54 callback = callback || function() {};
55 if (workerId) {
56 try {
57 browserClient.getWorker(workerId, function(err, worker) {
58 if (err) {
59 console.warn('Warning: Browser with id ' + workerId + ' failed on getWorker call. ' + inspect(err));
60 callback(err);
61 } else {
62 if ( (typeof(worker) == 'object') && worker['status'] ) {
63 browserClient.terminateWorker(workerId, function(err, data) {
64 if (err) {
65 console.warn('Warning: Browser with id ' + workerId + ' termination failed. ' + inspect(err));
66 } else {
67 console.log('Running browser with id ' + workerId + ' terminated successfully');
68 }
69 callback(err);
70 });
71 } else {
72 callback();
73 }
74 }
75 });
76 } catch (e) {
77 console.warn('Warning: Could not kill browser with id ' + workerId);
78 console.warn(inspect(e));
79 callback(e);
80 }
81 } else {
82 callback();
83 }
84}
85
86var cleanedUp = false;
87function cleanUpOnExit(exitCode) {
88 if (!cleanedUp) {
89 cleanedUp = true;
90 console.log('browser-stack: exiting and cleaning up');
91 if (currentBrowserClientId) browserClient.terminateWorker(currentBrowserClientId, function() {
92 if ((exitCode === 0) || (exitCode)) { process.exit(exitCode); }
93 });
94 if (tunnelProcess) tunnelProcess.kill();
95 }
96}
97function cleanUpOnExitAndLeaveExitCode() {
98 cleanUpOnExit();
99}
100function cleanUpOnExitWithCode() {
101 cleanUpOnExit(0);
102}
103process.on('exit', cleanUpOnExitAndLeaveExitCode);
104process.on('SIGINT', cleanUpOnExitWithCode);
105process.on('SIGTERM', cleanUpOnExitWithCode);
106
107process.on('uncaughtException', function (err) {
108 console.error('browser-stack: exception caught by event loop: ', err + '; ' + err.stack);
109 process.exit(1);
110});
111
112try {
113 config = JSON.parse(fs.readFileSync(path.normalize(__dirname + '/browser-stack.json')));
114} catch (e) {
115 console.error('Could not load browser-stack.json configuration. Please ensure this exists and follows the format in browser-stack.json.example');
116 console.error('Error: ' + e.message);
117 process.exit(1);
118}
119
120browserClient = browserStack.createClient({
121 username: config['credentials']['username'],
122 password: config['credentials']['password']
123});
124
125browsers = JSON.parse(fs.readFileSync(path.normalize(__dirname + '/supported-browsers.json')));
126
127function incorrectBrowserParams() {
128 console.error('You cannot specify an individual browser and --all, please specify a list of browsers or all');
129 process.exit(7);
130}
131function startsWith(string, substr) {
132 return string.substr(0, substr.length) == substr;
133}
134
135var chosenBrowsers = [];
136for(var i = 2; i < process.argv.length; i++) {
137 if(process.argv[i] == '--skip-setup') {
138 // do nothing, skip set up is used in CI tests and gets passed through
139 } else if(process.argv[i] == '--all') {
140 if (browserQueue.length) incorrectBrowserParams();
141 browserQueue = browsers;
142 } else if(startsWith(process.argv[i], 'realtime-host=')) {
143 REALTIME_HOST = process.argv[i].substr('realtime-host='.length);
144 } else if(startsWith(process.argv[i], 'realtime-port=')) {
145 REALTIME_PORT = process.argv[i].substr('realtime-port='.length);
146 } else if(startsWith(process.argv[i], 'realtime-tls-port=')) {
147 REALTIME_TLS_PORT = process.argv[i].substr('realtime-tls-port='.length);
148 } else if(startsWith(process.argv[i], 'realtime=')) {
149 var realtimeParams = process.argv[i].substr('realtime='.length).split(',');
150 REALTIME_HOST = realtimeParams[0];
151 REALTIME_PORT = realtimeParams[1];
152 REALTIME_TLS_PORT = realtimeParams[2];
153 } else if(startsWith(process.argv[i], '--manual-browser-launch')) {
154 doNotLaunchBrowsersAutomatically = true;
155 browserStackTestKey = 'manual_testing_tunnel_key';
156 } else if(startsWith(process.argv[i], '--use-flash-policy-server')) {
157 useFlashPolicyServer = true;
158 } else {
159 chosenBrowsers.push(process.argv[i]);
160 }
161}
162
163if (!doNotLaunchBrowsersAutomatically) {
164 if (chosenBrowsers.length === 0) chosenBrowsers = DEFAULT_BROWSERS;
165 for (var i = 0; i < chosenBrowsers.length; i++) {
166 var args = chosenBrowsers[i].split(',');
167 for (var argI in args) {
168 var browser, arg = args[argI];
169 for (var browserObj in browsers) {
170 if (browsers[browserObj].id == arg) {
171 browser = browsers[browserObj];
172 break;
173 }
174 }
175 if (!browser) {
176 console.error('A browser with id `' + arg + '` could not be found. Aborting browser-stack test.');
177 process.exit(3);
178 } else {
179 if (browserQueue == browsers) incorrectBrowserParams();
180 browserQueue.push(browser);
181 }
182 }
183 }
184}
185
186function launchTunnel() {
187 var tunnelPorts = [[SERVER_HOST, SERVER_PORT, 0]]; // tunnel for test server
188
189 // tests are running against a local farm so set up tunnel to map to local farm
190 if (['127.0.0.1','localhost','localhost.ably.io'].indexOf(REALTIME_HOST) !== -1) {
191 if(useFlashPolicyServer)
192 tunnelPorts.push([REALTIME_HOST, 843, 0]);
193 tunnelPorts.push([REALTIME_HOST, REALTIME_PORT, 0]); // plain text
194 tunnelPorts.push([REALTIME_HOST, REALTIME_TLS_PORT, 1]); // turn on SSL
195 }
196
197 var tunnelHosts = tunnelPorts.map(function (e) { return e.join(','); }),
198 tunnelArgs = ['-jar', path.normalize(__dirname + '/bin/BrowserStackTunnel.jar'), config['credentials'][browserStackTestKey], tunnelHosts.join(',')];
199
200 tunnelProcess = childProcess.spawn('java', tunnelArgs);
201 tunnelProcess.stdout.on('data', function(data) {
202 if (tunnelOutput == '') { console.log('Tunnel opened');}
203 tunnelOutput += data
204 });
205 tunnelProcess.stderr.on('data', function() {
206 console.log ('Tunnelling error! - ' + data);
207 setTimeout(function() {
208 console.log('Exiting because tunnel is broken');
209 process.exit(3);
210 }, 1000);
211 });
212 tunnelProcess.on('exit', function(code, signal) {
213 if (browserQueue.length) {
214 console.error('Tunnel closed prematurely with exit code: ' + code);
215 if (tunnelOutput) console.error('Tunnel log:\n' + tunnelOutput);
216 process.exit(2);
217 }
218 });
219 console.log('\nTunnel to BrowserStack for ' + tunnelPorts.map(function (e) {
220 return (e[2] ? 'https' : 'http') + '://' + e[0] + ':' + e[1];
221 }).join(', ') + ' being initialized');
222}
223
224// module async runner for setup & tear down
225function runModule(module, moduleCallback) {
226 moduleCallback = moduleCallback || function() {};
227 var tasks = Object.keys(module).map(function(item) {
228 return function(itemCb) {
229 module[item](testvars, itemCb);
230 };
231 });
232 async.series(tasks, moduleCallback);
233}
234
235function launchServer(opts, callback) {
236 server.start(opts, function(err, srv) {
237 if(err) console.error('Unexpected error in server start: ' + inspect(err));
238 callback(err);
239 });
240}
241
242function dequeue() {
243 if (browserQueue.length) {
244 var browser = browserQueue.pop(),
245 testTimeout;
246
247 var launchBrowser = function(err) {
248 if (err) {
249 testsComplete({ tests: 0, failed: 1, errors: ['Could not execute the test setup so have had to abort', inspect(err)], consoleErrors: [] });
250 } else {
251 console.log(' .. launching browser `' + browser.id + '`');
252 var browserOpts = {
253 os: browser.os,
254 os_version: browser.os_version,
255 browser: browser.browser,
256 browser_version: browser.browser_version,
257 device: browser.device,
258 url: 'http://' + SERVER_HOST + ':' + SERVER_PORT,
259 timeout: TEST_TIMEOUT,
260 version: 3
261 };
262 browserClient.createWorker(browserOpts, function(err, worker) {
263 if (err) {
264 console.log('Error: Could not launch browser ' + browser.id);
265 console.log('Error message: ' + inspect(err));
266 testsComplete({ tests: 0, failed: 1, errors: ['Could not launch browser', inspect(err)], consoleErrors: [] });
267 return;
268 }
269
270 currentBrowserClientId = worker.id;
271 console.log(' .. launched browser with worker id `' + worker.id + '`');
272
273 testTimeout = setTimeout(function() {
274 testsComplete({ tests: 0, failed: 1, errors: ['Timeout - No response from browser tests received'], consoleErrors: [] });
275 }, (TEST_TIMEOUT + TEST_GRACE) * 1000); // allow 30 seconds more than timeout for browser tests allowing for start up and launch time
276 });
277 }
278 };
279
280 var testsCompleteFn = function(result) {
281 console.log(' .. ' + (result.failed ? 'FAILED' : 'passing') + ' browser tests received for `' + browser.id + '`');
282 clearTimeout(testTimeout); // we have results, don't let the timeout
283
284 var logResultAndProcessQueue = function() {
285 currentBrowserClientId = null;
286 // payload from AJAX post changes errors to errors[] for some reason, and a single array item becomes a string
287 ['errors[]', 'consoleErrors[]'].forEach(function(val) {
288 if (result[val]) {
289 if (typeof(result[val]) == 'string') {
290 result.errors = [result[val]];
291 } else {
292 result.errors = result[val];
293 }
294 }
295 });
296 results.push({
297 browser: browser,
298 result: result
299 });
300 dequeue();
301 };
302
303 if (currentBrowserClientId) {
304 terminateWorker(currentBrowserClientId, function(err) {
305 if (!err) console.log('Closed browser ' + browser.id + ' with worker ID ' + currentBrowserClientId);
306 logResultAndProcessQueue();
307 });
308 } else {
309 logResultAndProcessQueue();
310 }
311 };
312
313 console.log('Starting test server for browser `' + browser.id + '`...');
314 testsCompleteCallback = testsCompleteFn;
315 launchBrowser();
316 } else {
317 presentResults();
318 }
319}
320
321// callback needs to be in global space so that same callback is used for each request to the test server which in turn maps to the particular callback for each dequeue step
322function testsComplete(result) {
323 testsCompleteCallback(result);
324}
325
326function presentResults() {
327 var failedBrowsers = 0, stepErrors = 0, totalSteps = 0;
328 console.log('\n--- Browser-stack tests complete ---\n');
329
330 for (var i = 0; i < results.length; i++) {
331 var outcome = results[i],
332 result = outcome.result;
333
334 totalSteps += result.steps;
335
336 console.log('Browser: ' + outcome.browser.id + ' - ' + (result.failed ? 'FAILED (' + result.failed + ' out of ' + result.tests + ')' : 'passed ' + result.tests + ' steps'));
337 if (result.failed) {
338 failedBrowsers++;
339 stepErrors += result.failed;
340 if (result.errors) {
341 for (var errorIndex = 0; errorIndex < result.errors.length; errorIndex++) {
342 var err = result.errors[errorIndex];
343 console.log(' - ' + err);
344 }
345 } else {
346 console.log(' - missing any error information');
347 }
348 console.log('\n');
349 }
350 }
351
352 if (failedBrowsers === 0) {
353 console.log('\nAll ' + results.length + ' browser test(s) passed\n');
354 process.exit(0);
355 } else {
356 console.log('FAILURE: ' + failedBrowsers + ' out of ' + results.length + ' browser tests failed to succeed');
357 console.log(' There were a total of ' + stepErrors + ' failed steps\n');
358 process.exit(10);
359 }
360}
361
362var serverOpts = {
363 host: SERVER_HOST,
364 port: SERVER_PORT
365};
366
367// if automated testing capture results in callback, else results will be sent to console
368if (!doNotLaunchBrowsersAutomatically) {
369 serverOpts.onTestResult = testsComplete;
370}
371
372// ensure we are testing against the correct realtime service, defaults to sandbox
373testvars.realtimeHost = REALTIME_HOST;
374testvars.restHost = REALTIME_HOST;
375testvars.realtimePort = REALTIME_PORT;
376testvars.realtimeTlsPort = REALTIME_TLS_PORT;
377
378console.log('Realtime host and port settings used:');
379console.log(inspect(testvars));
380
381launchServer(serverOpts, function(err) {
382 if (err) {
383 console.error('Fatal error. Could not launch local test web server');
384 console.error(inspect(err));
385 process.exit(6);
386 } else {
387 console.log('Test web server started at http://' + SERVER_HOST + ':' + SERVER_PORT);
388 console.log('Configured to test against realtime service at ws://' + REALTIME_HOST + ':' + REALTIME_PORT);
389 console.log(' and wss://' + REALTIME_HOST + ':' + REALTIME_TLS_PORT);
390 }
391});
392
393launchTunnel();
394
395console.log('Waiting 3 seconds for tunnel to be initialized');
396setTimeout(function() {
397 if (doNotLaunchBrowsersAutomatically) {
398 console.log('\n\nLog in to http://browserstack.com now and visit http://' + SERVER_HOST + ':' + SERVER_PORT);
399 } else {
400 console.log('\n-- Browser-stack testing against ' + browserQueue.length + ' browser(s) starting --');
401 dequeue();
402 }
403}, 3000);
\No newline at end of file