diff --git a/tests/run_from_console.casper.js b/tests/run_from_console.casper.js new file mode 100644 index 00000000..60b0926d --- /dev/null +++ b/tests/run_from_console.casper.js @@ -0,0 +1,90 @@ +var Spooky = require('spooky'); +var path = require('path'); + +var phantom_path = require('phantomjs').path; +var casper_path = path.resolve(__dirname, 'node_modules/casperjs/bin/casperjs'); +process.env['PHANTOMJS_EXECUTABLE'] = phantom_path; +var casper_opts = { + child: { + transport: 'http', + command: casper_path + }, + casper: { + logLevel: 'debug', + verbose: true + } +} + +var provide_emitter = function(file_paths) { + var spooky = new Spooky(casper_opts, function(err) { + if (err) { + if (err.stack) console.warn(err.stack); + else console.warn(err); + return; + } + spooky.start('about:blank'); + + file_paths.forEach(function(file_path, path_ind) { + spooky.thenOpen('file://'+file_path); + spooky.then([{ path_ind: path_ind }, function() { + var res_json = { + file_ind: path_ind + } + + res_json.num_tests = this.evaluate(function() { return document.querySelectorAll('li.test').length }); + res_json.num_passes = this.evaluate(function() { return document.querySelectorAll('li.test.pass').length }); + res_json.num_fails = this.evaluate(function() { return document.querySelectorAll('li.test.fail').length }); + res_json.num_slow = this.evaluate(function() { return document.querySelectorAll('li.test.pass:not(.fast)').length }); + res_json.duration = this.evaluate(function() { return document.querySelector('li.duration em').textContent }); + + res_json.suites = this.evaluate(function() { + var traverse_node = function(elem) { + if (elem.classList.contains('suite')) { + var res = { + type: 'suite', + name: elem.querySelector('h1').textContent, + has_subfailures: elem.querySelectorAll('li.test.fail').length > 0, + } + + var child_elems = elem.querySelector('ul').children; + res.children = Array.prototype.map.call(child_elems, traverse_node); + return res; + } + else { + var h2_content = elem.querySelector('h2').childNodes; + var res = { + type: 'test', + text: h2_content[0].textContent, + } + + if (elem.classList.contains('pass')) { + res.pass = true; + res.slow = !elem.classList.contains('fast'); + res.duration = h2_content[1].textContent; + } + else { + res.error = elem.querySelector('pre.error').textContent; + } + + return res; + } + } + var top_suites = document.querySelectorAll('#mocha-report > li.suite'); + return Array.prototype.map.call(top_suites, traverse_node); + }); + + res_json.replay = this.evaluate(function() { return document.querySelector('a.replay').textContent }); + + this.emit('test_ready', res_json); + }]); + }); + spooky.run(); + }); + + return spooky; +} + +module.exports = { + provide_emitter: provide_emitter, + name: 'SpookyJS (CapserJS on PhantomJS)' +} diff --git a/tests/run_from_console.js b/tests/run_from_console.js new file mode 100755 index 00000000..9d79ebe4 --- /dev/null +++ b/tests/run_from_console.js @@ -0,0 +1,196 @@ +#!/usr/bin/env node +var ansi = require('ansi'); +var program = require('commander'); +var path = require('path'); + +var make_list = function(val) { + return val.split(','); +} + +program + .option('-t, --tests ', 'Run the specified html-file-based test(s). \'testlist\' should be a comma-separated list', make_list, []) + .option('-a, --print-all', 'Print all tests, not just the failures') + .option('--disable-color', 'Explicitly disable color') + .option('-c, --color', 'Explicitly enable color (default is to use color when not outputting to a pipe)') + .option('-i, --auto-inject ', 'Treat the test list as a set of mocha JS files, and automatically generate HTML files with which to test test. \'includefiles\' should be a comma-separated list of paths to javascript files to include in each of the generated HTML files', make_list, null) + .option('-p, --provider ', 'Use the given provider (defaults to "casper"). Currently, may be "casper" or "zombie"', 'casper') + .parse(process.argv); + +var file_paths = []; + +if (program.autoInject) { + var temp = require('temp'); + var fs = require('fs'); + temp.track(); + + var template = { + header: "\n\n\\n\n
", + script_tag: function(p) { return "" }, + footer: "\n\n" + }; + + template.header += "\n" + template.script_tag(path.resolve(__dirname, 'node_modules/chai/chai.js')); + template.header += "\n" + template.script_tag(path.resolve(__dirname, 'node_modules/mocha/mocha.js')); + template.header += "\n"; + + + template.header = program.autoInject.reduce(function(acc, sn) { + return acc + "\n" + template.script_tag(path.resolve(process.cwd(), sn)); + }, template.header); + + file_paths = program.tests.map(function(jsn, ind) { + var templ = template.header; + templ += "\n"; + templ += template.script_tag(path.resolve(process.cwd(), jsn)); + templ += template.footer; + + var tempfile = temp.openSync({ prefix: 'novnc-zombie-inject-', suffix: '-file_num-'+ind+'.html' }); + fs.writeSync(tempfile.fd, templ); + fs.closeSync(tempfile.fd); + return tempfile.path; + }); + +} +else { + file_paths = program.tests.map(function(fn) { + return path.resolve(process.cwd(), fn); + }); +} + +var failure_count = 0; + +var use_ansi = false; +if (program.color) use_ansi = true; +else if (program.disableColor) use_ansi = false; +else if (process.stdout.isTTY) use_ansi = true; + +var cursor = ansi(process.stdout, { enabled: use_ansi }); + +var prov = require(path.resolve(__dirname, 'run_from_console.'+program.provider+'.js')); + +cursor + .write("Running tests ") + .bold() + .write(program.tests.join(', ')) + .reset() + .grey() + .write(' using provider '+prov.name) + .reset() + .write("\n"); +//console.log("Running tests %s using provider %s", program.tests.join(', '), prov.name); + +var provider = prov.provide_emitter(file_paths); +provider.on('test_ready', function(test_json) { + console.log(''); + + filename = program.tests[test_json.file_ind]; + + cursor.bold(); + console.log('Results for %s:', filename); + console.log(Array('Results for :'.length+filename.length+1).join('=')); + cursor.reset(); + + console.log(''); + + cursor.write(''+test_json.num_tests+' tests run, ') + cursor + .green() + .write(''+test_json.num_passes+' passed'); + if (test_json.num_slow > 0) { + cursor + .reset() + .write(' ('); + cursor + .yellow() + .write(''+test_json.num_slow+' slow') + .reset() + .write(')'); + } + cursor + .reset() + .write(', '); + cursor + .red() + .write(''+test_json.num_fails+' failed'); + cursor + .reset() + .write(' -- duration: '+test_json.duration+"\n"); + + console.log(''); + + if (test_json.num_fails > 0 || program.printAll) { + var traverse_tree = function(indentation, node) { + if (node.type == 'suite') { + if (!node.has_subfailures && !program.printAll) return; + + if (indentation == 0) { + cursor.bold(); + console.log(node.name); + console.log(Array(node.name.length+1).join('-')); + cursor.reset(); + } + else { + cursor + .write(Array(indentation+3).join('#')) + .bold() + .write(' '+node.name+' ') + .reset() + .write(Array(indentation+3).join('#')) + .write("\n"); + } + + console.log(''); + + for (var i = 0; i < node.children.length; i++) { + traverse_tree(indentation+1, node.children[i]); + } + } + else { + if (!node.pass) { + cursor.magenta(); + console.log('- failed: '+node.text+test_json.replay); + cursor.red(); + console.log(' '+node.error.split("\n")[0]); // the split is to avoid a weird thing where in PhantomJS, we get a stack trace too + cursor.reset(); + console.log(''); + } + else if (program.printAll) { + if (node.slow) cursor.yellow(); + else cursor.green(); + cursor + .write('- pass: '+node.text) + .grey() + .write(' ('+node.duration+') '); + /*if (node.slow) cursor.yellow(); + else cursor.green();*/ + cursor + //.write(test_json.replay) + .reset() + .write("\n"); + console.log(''); + } + } + } + + for (var i = 0; i < test_json.suites.length; i++) { + traverse_tree(0, test_json.suites[i]); + } + } + + if (test_json.num_fails == 0) { + cursor.fg.green(); + console.log('all tests passed :-)'); + cursor.reset(); + } +}); + +/*provider.on('console', function(line) { + //console.log(line); +});*/ + +/*gprom.finally(function(ph) { + ph.exit(); + // exit with a status code that actually gives information + if (program.exitWithFailureCount) process.exit(failure_count); +});*/ + diff --git a/tests/run_from_console.zombie.js b/tests/run_from_console.zombie.js new file mode 100644 index 00000000..5ae51846 --- /dev/null +++ b/tests/run_from_console.zombie.js @@ -0,0 +1,73 @@ +var Browser = require('zombie'); +var path = require('path'); +var EventEmitter = require('events').EventEmitter; +var Q = require('q'); + +var provide_emitter = function(file_paths) { + var emitter = new EventEmitter(); + + file_paths.reduce(function(prom, file_path, path_ind) { + return prom.then(function(browser) { + browser.visit('file://'+file_path, function() { + if (browser.error) throw new Error(browser.errors); + + var res_json = {}; + res_json.file_ind = path_ind; + + res_json.num_tests = browser.querySelectorAll('li.test').length; + res_json.num_fails = browser.querySelectorAll('li.test.fail').length; + res_json.num_passes = browser.querySelectorAll('li.test.pass').length; + res_json.num_slow = browser.querySelectorAll('li.test.pass:not(.fast)').length; + res_json.duration = browser.text('li.duration em'); + + var traverse_node = function(elem) { + var classList = elem.className.split(' '); + if (classList.indexOf('suite') > -1) { + var res = { + type: 'suite', + name: elem.querySelector('h1').textContent, + has_subfailures: elem.querySelectorAll('li.test.fail').length > 0 + } + + var child_elems = elem.querySelector('ul').children; + res.children = Array.prototype.map.call(child_elems, traverse_node); + return res; + } + else { + var h2_content = elem.querySelector('h2').childNodes; + var res = { + type: 'test', + text: h2_content[0].textContent + } + + if (classList.indexOf('pass') > -1) { + res.pass = true; + res.slow = classList.indexOf('fast') < 0; + res.duration = h2_content[1].textContent; + } + else { + res.error = elem.querySelector('pre.error').textContent; + } + + return res; + } + } + + var top_suites = browser.querySelectorAll('#mocha-report > li.suite'); + res_json.suites = Array.prototype.map.call(top_suites, traverse_node); + res_json.replay = browser.querySelector('a.replay').textContent; + + emitter.emit('test_ready', res_json); + }); + + return new Browser(); + }); + }, Q(new Browser())); + + return emitter; +} + +module.exports = { + provide_emitter: provide_emitter, + name: 'ZombieJS' +}