From c1b1b2ac9680ab586fd79d09a598614093b9c869 Mon Sep 17 00:00:00 2001 From: Jason Leyba Date: Sun, 6 Jul 2014 16:19:51 -0400 Subject: [PATCH] Include unit tests for Closure-based js code with selenium-webdriver package. --- javascript/node/build.desc | 1 + javascript/node/deploy.js | 58 +++++++---- javascript/node/selenium-webdriver/_base.js | 90 +++++++++-------- .../selenium-webdriver/test/_base_test.js | 97 +++++++++++++++++++ javascript/webdriver/build.desc | 15 ++- 5 files changed, 203 insertions(+), 58 deletions(-) create mode 100644 javascript/node/selenium-webdriver/test/_base_test.js diff --git a/javascript/node/build.desc b/javascript/node/build.desc index 0e2a41764cf50..5eec6836ccab2 100644 --- a/javascript/node/build.desc +++ b/javascript/node/build.desc @@ -4,6 +4,7 @@ node_module( deps = [ "//javascript/webdriver:asserts_lib", "//javascript/webdriver:webdriver_lib", + "//javascript/webdriver:unit_test_lib", ], content_roots = [ "javascript", diff --git a/javascript/node/deploy.js b/javascript/node/deploy.js index 8bcbd78fbe4aa..62f1fac3c3d4c 100644 --- a/javascript/node/deploy.js +++ b/javascript/node/deploy.js @@ -199,12 +199,17 @@ function copyLibraries(outputDirPath, filePaths) { var seenFiles = {}; var symbols = []; var providedSymbols = []; + var rootFiles = []; filePaths.filter(function(path) { return !fs.statSync(path).isDirectory(); }).forEach(function(path) { + if (!FILE_INFO[path].provides.length) { + rootFiles.push(path); + } providedSymbols = providedSymbols.concat(FILE_INFO[path].provides); symbols = symbols.concat(FILE_INFO[path].requires); }); + rootFiles.forEach(processFile); providedSymbols.forEach(resolveDeps); symbols.forEach(resolveDeps); @@ -220,7 +225,10 @@ function copyLibraries(outputDirPath, filePaths) { '; required in\n ' + UNPROVIDED[symbol].join('\n ')); } - var file = PROVIDERS[symbol]; + processFile(PROVIDERS[symbol]); + } + + function processFile(file) { if (seenFiles[file]) return; seenFiles[file] = true; @@ -321,40 +329,55 @@ function copyResources(outputDirPath, resources, exclusions) { } -function generateDocs(outputDir) { +function generateDocs(outputDir, callback) { var libDir = path.join(outputDir, 'lib'); var excludedDirs = [ path.join(outputDir, 'example'), path.join(libDir, 'test'), + path.join(libDir, 'webdriver/test'), path.join(outputDir, 'test') ]; + var excludedFiles = [ + path.join(libDir, 'webdriver/testing/client.js'), + path.join(libDir, 'webdriver/testing/flowtester.js'), + path.join(libDir, 'webdriver/testing/jsunit.js'), + path.join(libDir, 'webdriver/testing/window.js'), + ]; + var endsWith = function(str, suffix) { var l = str.length - suffix.length; return l >= 0 && str.indexOf(suffix, l) == l; }; var getFiles = function(dir) { - return fs.readdirSync(dir).map(function(file) { - return path.join(dir, file); - }).filter(function(file) { - if (fs.statSync(file).isDirectory()) { - return excludedDirs.indexOf(file) == -1; + var files = []; + fs.readdirSync(dir).forEach(function(file) { + file = path.join(dir, file); + if (fs.statSync(file).isDirectory() && + excludedDirs.indexOf(file) == -1) { + files = files.concat(getFiles(file)); + } else if (endsWith(path.basename(file), '.js') && + excludedFiles.indexOf(file) == -1) { + files.push(file); } - return endsWith(path.basename(file), '.js'); }); + return files; }; + var sourceFiles = getFiles(libDir); + var moduleFiles = getFiles(outputDir).filter(function(file) { + return sourceFiles.indexOf(file) == -1; + }); + var config = { 'output': path.join(outputDir, 'docs'), 'closureLibraryDir': path.join(outputDir, 'lib', 'goog'), 'license': path.join(outputDir, 'COPYING'), 'readme': path.join(outputDir, 'README.md'), 'language': 'ES5', - 'sources': getFiles(libDir), - 'modules': getFiles(outputDir).filter(function(file) { - return file != libDir; - }) + 'sources': sourceFiles, + 'modules': moduleFiles }; var configFile = outputDir + '-docs.json'; @@ -365,9 +388,7 @@ function generateDocs(outputDir) { __dirname, '../../third_party/java/dossier/dossier-0.3.0.jar'), '-c', configFile ].join(' '); - child_process.exec(command, function(error) { - if (error) throw error; - }); + child_process.exec(command, callback); } @@ -423,9 +444,10 @@ function main() { console.log('Copying resource files...'); copyResources(options.output, options.resource, options.exclude_resource); console.log('Generating documentation...'); - generateDocs(options.output); - - console.log('ALL DONE'); + generateDocs(options.output, function(e) { + if (e) throw e; + console.log('ALL DONE'); + }); } diff --git a/javascript/node/selenium-webdriver/_base.js b/javascript/node/selenium-webdriver/_base.js index 6048179665b85..22b62a0868376 100644 --- a/javascript/node/selenium-webdriver/_base.js +++ b/javascript/node/selenium-webdriver/_base.js @@ -74,46 +74,59 @@ var DEPS_FILE_PATH = (function() { })(); - /** - * Synchronously loads a script into the protected Closure context. - * @param {string} src Path to the file to load. + * Maintains a unique context for Closure library-based code. + * @param {boolean=} opt_configureForTesting Whether to configure a fake DOM + * for Closure-testing code that (incorrectly) assumes a DOM is always + * present. + * @constructor */ -function loadScript(src) { - src = path.normalize(src); - var contents = fs.readFileSync(src, 'utf8'); - vm.runInContext(contents, closure, src); +function Context(opt_configureForTesting) { + var closure = this.closure = vm.createContext({ + console: console, + setTimeout: setTimeout, + setInterval: setInterval, + clearTimeout: clearTimeout, + clearInterval: clearInterval, + process: process, + require: require, + Buffer: Buffer, + Error: Error, + CLOSURE_BASE_PATH: path.dirname(CLOSURE_BASE_FILE_PATH) + '/', + CLOSURE_IMPORT_SCRIPT: function(src) { + loadScript(src); + return true; + }, + CLOSURE_NO_DEPS: !isDevMode(), + goog: {} + }); + closure.window = closure.top = closure; + + if (opt_configureForTesting) { + closure.document = { + body: {}, + createElement: function() { return {}; }, + getElementsByTagName: function() { return []; } + }; + closure.document.body.ownerDocument = closure.document; + } + + loadScript(CLOSURE_BASE_FILE_PATH); + loadScript(DEPS_FILE_PATH); + + /** + * Synchronously loads a script into the protected Closure context. + * @param {string} src Path to the file to load. + */ + function loadScript(src) { + src = path.normalize(src); + var contents = fs.readFileSync(src, 'utf8'); + vm.runInContext(contents, closure, src); + } } -/** - * The protected context to host the Closure library. - * @type {!Object} - * @const - */ -var closure = vm.createContext({ - console: console, - setTimeout: setTimeout, - setInterval: setInterval, - clearTimeout: clearTimeout, - clearInterval: clearInterval, - process: process, - require: require, - Buffer: Buffer, - Error: Error, - CLOSURE_BASE_PATH: path.dirname(CLOSURE_BASE_FILE_PATH) + '/', - CLOSURE_IMPORT_SCRIPT: function(src) { - loadScript(src); - return true; - }, - CLOSURE_NO_DEPS: !isDevMode(), - goog: {} -}); -closure.window = closure; - - -loadScript(CLOSURE_BASE_FILE_PATH); -loadScript(DEPS_FILE_PATH); +var context = new Context(); /** @@ -123,8 +136,8 @@ loadScript(DEPS_FILE_PATH); * @throws {Error} If the symbol has not been defined. */ function closureRequire(symbol) { - closure.goog.require(symbol); - return closure.goog.getObjectByName(symbol); + context.closure.goog.require(symbol); + return context.closure.goog.getObjectByName(symbol); } @@ -159,7 +172,8 @@ exports.exportPublicApi = function(symbol) { if (isDevMode()) { - exports.closure = closure; + exports.closure = context.closure; } +exports.Context = Context; exports.isDevMode = isDevMode; exports.require = closureRequire; diff --git a/javascript/node/selenium-webdriver/test/_base_test.js b/javascript/node/selenium-webdriver/test/_base_test.js new file mode 100644 index 0000000000000..7e10e8c304261 --- /dev/null +++ b/javascript/node/selenium-webdriver/test/_base_test.js @@ -0,0 +1,97 @@ +// Copyright 2014 Software Freedom Conservancy. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var assert = require('assert'), + fs = require('fs'), + path = require('path'); + +var base = require('../_base'); + +describe('Context', function() { + it('does not pollute the global scope', function() { + assert.equal('undefined', typeof goog); + + var context = new base.Context(); + assert.equal('undefined', typeof goog); + assert.equal('object', typeof context.closure.goog); + + context.closure.goog.require('goog.array'); + assert.equal('undefined', typeof goog); + assert.equal('object', typeof context.closure.goog.array); + }); +}); + + +function runClosureTest(file) { + var name = path.basename(file); + name = name.substring(0, name.length - '.js'.length); + + describe(name, function() { + var context = new base.Context(true); + context.closure.document.title = name; + // Null out console so everything loads silently. + context.closure.console = null; + context.closure.CLOSURE_IMPORT_SCRIPT(file); + + var tc = context.closure.G_testRunner.testCase; + if (!tc) { + tc = new context.closure.goog.testing.TestCase(name); + tc.autoDiscoverTests(); + } + // Reset console for running tests. + context.closure.console = console; + + var allTests = tc.getTests(); + allTests.forEach(function(test) { + it(test.name, function(done) { + tc.setTests([test]); + tc.setCompletedCallback(function() { + if (tc.isSuccess()) { + return done(); + } + var results = tc.getTestResults(); + done(Error('\n' + Object.keys(results).map(function(name) { + var msg = [name + ': ' + (results[name].length ? 'FAILED' : 'PASSED')]; + if (results[name].length) { + msg = msg.concat(results[name]); + } + return msg.join('\n'); + }).join('\n'))); + }); + tc.runTests(); + }); + }); + }); +} + + +function findTests(dir) { + fs.readdirSync(dir).forEach(function(name) { + var file = path.join(dir, name); + + var stat = fs.statSync(file); + if (stat.isDirectory() && name !== 'atoms' && name !== 'e2e') { + findTests(file); + return; + } + + var l = file.length - '_test.js'.length; + if (l >= 0 && file.indexOf('_test.js', l) == l) { + runClosureTest(file); + } + }); +} + +findTests(path.join( + __dirname, base.isDevMode() ? '../../..' : '../lib', 'webdriver/test')); diff --git a/javascript/webdriver/build.desc b/javascript/webdriver/build.desc index e94f741334523..90c90a7854c6c 100644 --- a/javascript/webdriver/build.desc +++ b/javascript/webdriver/build.desc @@ -32,7 +32,7 @@ js_library(name = "asserts_lib", srcs = ["testing/asserts.js"], deps = [":webdriver_lib"]) -js_library(name = "test_lib", +js_library(name = "test_support_lib", srcs = [ "testing/*.js", ], @@ -40,6 +40,17 @@ js_library(name = "test_lib", ":webdriver_lib" ]) +js_library(name = "unit_test_lib", + srcs = [ + "test/*.js", + "test/http/*.js", + "test/testing/*.js", + ], + deps = [ + ":webdriver_lib", + ":test_support_lib", + ]) + js_deps(name = "deps", srcs = [ "*.js", @@ -96,7 +107,7 @@ js_test(name = "test", "test/**/*_test.html", ], deps = [ - ":test_lib", + ":test_support_lib", "//java/client/test/org/openqa/selenium/javascript", "//java/server/test/org/openqa/selenium:server-with-tests:uber", ])