diff --git a/.gitignore b/.gitignore index bace69973f..96810b174d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ gmon.out v8.log node_modules +test/worker-bundle.js diff --git a/lib/jsdom.js b/lib/jsdom.js index 52ee71f498..84b05809d8 100644 --- a/lib/jsdom.js +++ b/lib/jsdom.js @@ -34,6 +34,13 @@ exports.debugMode = false; }); }); +exports.debugMode = false; + +defineGetter(exports, 'version', function() { + return pkg.version; +}); + +var level2Html = require('./jsdom/level2/html'); exports.level = function (level, feature) { if(!feature) { feature = 'core'; diff --git a/lib/jsdom/contextify-shim.js b/lib/jsdom/contextify-shim.js new file mode 100644 index 0000000000..feaea5621e --- /dev/null +++ b/lib/jsdom/contextify-shim.js @@ -0,0 +1,5 @@ +module.exports = function (o) { + o.getGlobal = function () { + return o; + }; +}; diff --git a/package.json b/package.json index 1da8ae7f49..9ad8af8930 100644 --- a/package.json +++ b/package.json @@ -67,9 +67,24 @@ "contextify": "~0.1.5" }, "devDependencies" : { + "browser-request": "~0.3.1", + "cssstyle-browserify": "git://github.com/TreehouseJS/CSSStyleDeclaration.git", "nodeunit": "~0.8.0", "optimist": "*", - "urlmaster": ">=0.2.15" + "urlmaster": ">=0.2.15", + "http-browserify": "git://github.com/kumavis/http-browserify.git#dc84f15eb15c58505c0dea29de7ee64ff56dfe4f", + "browserify": "~3.24.1", + "q": "^1.0.1", + "wd": "^0.2.21", + "selenium-standalone": "^2.42.0-2.9.0", + "http-server": "^0.6.1" + }, + "browser": { + "canvas": false, + "contextify": "./lib/jsdom/contextify-shim.js", + "cssstyle": "./node_modules/cssstyle-browserify/lib/CSSStyleDeclaration.js", + "http": "./node_modules/http-browserify/index.js", + "request": "./node_modules/browser-request/index.js" }, "scripts": { "test": "node ./test/runner" diff --git a/test/browser-main.js b/test/browser-main.js new file mode 100644 index 0000000000..ce662740fc --- /dev/null +++ b/test/browser-main.js @@ -0,0 +1,34 @@ +window._browserRunner = { + events: [] +}; + +var worker = new Worker('./worker-bundle.js'); +var consoleEl = document.querySelector('.console'); + +function fire(event, detail) { + window._browserRunner.events.push({ + event: event, + detail: detail + }); +} + +worker.onmessage = function (e) { + if (e.data.method) { + switch (e.data.method) { + case 'fire': + fire(e.data.params.event, e.data.params.data); + break; + case 'console': + fire('console', e.data.params); + console[e.data.params.level].apply(console, e.data.params.message); + break; + case 'ready': + fire('ready'); + worker.postMessage(location.search.slice(1)); + break; + default: + console.error('Unknown method', e.data.method); + } + } +}; + diff --git a/test/browser-runner.js b/test/browser-runner.js new file mode 100644 index 0000000000..d8b0242c77 --- /dev/null +++ b/test/browser-runner.js @@ -0,0 +1,183 @@ +require('colors'); +var EventEmitter = require('events').EventEmitter; +var wd = require('wd'); +var Q = require('q'); +var browser; + +var optimist = require('./runner-options'); + +optimist. + usage('Run the jsdom test suite in a browser via WebDriver'). + describe('http-port', 'port to run test server on (defaults to pid + 20000)'). + describe('web-driver-port', 'port to run Selenium on (defaults to pid + 20000)'). + describe('verbose-web-driver', 'print verbose output from wd to stdout'). + describe('verbose-browser-console', 'print browser console to stdout'); + +var argv = optimist.argv; + +if (argv.help) { + optimist.showHelp(); + process.exit(); +} + +var httpPort = argv['http-port'] || process.pid + 20000; +var wdPort = argv['web-driver-port'] || httpPort + 10000; + +/** + * Return the body of a function as a string + * + * wd should do this for us, but it doesn't + */ +function getFnBody(fn) { + var src = fn.toString(); + return src.slice(src.indexOf('{') + 1, src.lastIndexOf('}')); +} + +function run() { + var passed = false; + browser.init({ browserName: 'chrome' }). + then(function () { + return browser.setAsyncScriptTimeout(5000); + }). + then(function () { + return browser.get([ + 'http://localhost:', + httpPort, + '/test?', + require('querystring').stringify(argv) + ].join('')); + }). + then(function (result) { + function browserPoll() { + var events = window._browserRunner.events; + + return events.splice(0, events.length); + } + + var deferred = Q.defer(); + + var runner = new EventEmitter(); + require('./runner-display')(runner, argv, function (err) { + passed = !err; + deferred.resolve(); + }); + var nodeunitTypes = require('nodeunit').types; + + function poll() { + browser. + execute(getFnBody(browserPoll)). + then(function (events) { + var done = false; + + events.forEach(function (event) { + switch (event.event) { + case 'testDone': + case 'moduleDone': + runner.emit(event.event, + event.detail[0], + nodeunitTypes.assertionList( + event.detail[1].map(nodeunitTypes.assertion))); + break; + case 'log': + runner.emit(event.event, + nodeunitTypes.assertion(event.detail[0])); + break; + case 'done': + runner.emit(event.event, + nodeunitTypes.assertionList( + event.detail[0].map(nodeunitTypes.assertion))); + break; + case 'console': + case 'http': + case 'status': + case 'command': + browser. + emit.apply(browser, [event.event].concat(event.detail)); + break; + default: + runner.emit.apply(runner, [event.event].concat(event.detail)); + } + + if (event.detail && event.event === 'done') { + done = true; + } + }); + + if (!done) { + setTimeout(poll, 50); + } + }); + } + + poll(); + return deferred.promise; + }). + fin(function () { + return browser.quit(); + }). + fin(function () { + process.exit(passed ? 0 : 1); + }). + done(); +} + +// browserify and run the tests +require('child_process').exec( + 'node_modules/browserify/bin/cmd.js test/worker.js -o test/worker-bundle.js', + function (err, stdout, stderr) { + if (err) { + console.log(stdout.toString()); + console.log('Failed to browserify test/worker'); + console.log(err); + process.exit(1); + return; + } + + // start web server + var httpServer = require('http-server').createServer().listen(httpPort); + + // set up webdriver + browser = wd.promiseRemote({ + port: wdPort + }); + + if (argv['verbose-web-driver']) { + // really verbose wd logging + browser.on('status', function (info) { + console.log(info.cyan); + }); + browser.on('command', function (eventType, command, response) { + console.log(' > ' + eventType.cyan, command, (response || '').grey); + }); + browser.on('http', function (method, path, data) { + console.log(' > ' + method.magenta, path, (data || '').grey); + }); + } + + if (argv['verbose-browser-console']) { + browser.on('console', function (detail) { + console[detail.level].apply(console, detail.message); + }); + } + + // start selenium + var selenium = require('selenium-standalone'); + var wdServer = selenium({ + stdio: 'pipe' + }, ['-port', wdPort]); + + // time out after a default of 30 seconds + var h = setTimeout(function () { + console.log('Timed out waiting for selenium server to start'); + wdServer.kill(); + process.exit(1); + }, argv.wdTimeout || 30 * 1000); + + // Wait for selenium server to start. + wdServer.stdout.on('data', function (output) { + if (output.toString().indexOf('Started org.openqa.jetty.jetty.Server') >= 0) { + clearTimeout(h); + run(); + } + }); + }); diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000000..1aaf5e52de --- /dev/null +++ b/test/index.html @@ -0,0 +1,10 @@ + + +
+