diff --git a/.gitignore b/.gitignore index 58708fd..aee4c81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .* !.gitignore node_modules +coverage diff --git a/README.md b/README.md index 8e60c21..fed5ce5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[Fiveby](http://en.wikipedia.org/wiki/Five_by_five) +[Fiveby](http://en.wikipedia.org/wiki/Five_by_five) [![Build Status](http://djin-jenkins01.dowjones.net/job/fiveby/badge/icon)](http://djin-jenkins01.dowjones.net/job/fiveby/) ======== makes it easier to write automated tests/suites. Here's the idea: don't worry about selenium (or it's config), don't worry about selenium JS api oddities, don't worry about mocha, just use fiveby: @@ -19,3 +19,19 @@ new fiveby(function (browser) { //browser is driver if you are looking at seleni }); ``` See [docs](https://github.dowjones.net/institutional/fiveby/docs) for more details and use [gulp-fiveby](https://github.dowjones.net/institutional/gulp-fiveby) as a scaffold project. [Live Help](https://dowjones.slack.com/messages/fiveby/) + +###Configuration - fiveby-config.json + +```json +{ + "implicitWait": 5000, + "hubUrl": null, + "browsers": { + "chrome": 1 + }, + "disableBrowsers": false +} +``` +disableBrowsers is optional, defaults to false + +hubUrl is optional, if not provided (and disableBrowsers = false) it will spin up a selenium server *requires java* diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..dd2f0a6 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,14 @@ +var gulp = require('gulp'); +var istanbul = require('gulp-istanbul'); +var mocha = require('gulp-mocha'); + +gulp.task('test', function (cb) { + gulp.src(['lib/fiveby.js', 'index.js']) + .pipe(istanbul()) + .on('finish', function () { + gulp.src(['test/*.js']) + .pipe(mocha()) + .pipe(istanbul.writeReports()) + .on('end', cb); + }); +}); diff --git a/index.js b/index.js index 85e8ac4..2b28dab 100644 --- a/index.js +++ b/index.js @@ -1,33 +1,23 @@ -var webdriver = require('selenium-webdriver'); -var Hook = require('mocha').Hook; +var fiveby = require('./lib/fiveby'); var path = require('path'); var fs = require('fs'); var _ = require('lodash'); -var tb = require('traceback'); var Properties = require('./lib/properties'); require('should'); -module.exports = fiveby; - -//simplify webdriver usage -global.by = webdriver.By; -global.key = webdriver.Key; -global.promise = webdriver.promise; -global.bot = webdriver.error; - //get project configuration if one exists if (!global.fivebyConfig) { - if (process.env.fivebyopts) { - global.fivebyConfig = JSON.parse(process.env.fivebyopts); - } else { - var configPath = path.resolve('fiveby-config.json'); - try { + try { + if (process.env.fivebyopts) { + global.fivebyConfig = JSON.parse(process.env.fivebyopts); + } else { + var configPath = path.resolve('fiveby-config.json'); global.fivebyConfig = JSON.parse(fs.readFileSync(configPath, {encoding: 'utf-8'})); - } catch (e) { - console.error('No global config loaded %s', e); - process.exit(1); } + } catch (e) { + console.error('No global config loaded %s', e); + return process.exit(1); } //prep properties @@ -35,108 +25,24 @@ if (!global.fivebyConfig) { var props = global.propertyService.getProperties('default'); props.setMany(global.fivebyConfig.properties||{}); - global.testPromise = webdriver.promise.fulfilled(); - console.info('Configuration complete'); - -} - -//spin up local selenium server if none provided -if (!global.fivebyConfig.hubUrl) { - console.info("No server defined, spinning one up ..."); - SeleniumServer = require('selenium-webdriver/remote').SeleniumServer; - var server = new SeleniumServer('./node_modules/fiveby/selenium-server-standalone-2.42.2.jar', { port: 4444 }); - server.start(); - global.fivebyConfig.hubUrl = server.address(); } //main test driver -function fiveby(params, test) { +module.exports = function (params, test) { - var file = tb()[1].path; + var config = _.cloneDeep(global.fivebyConfig); //seperate config for merge if (arguments.length === 1) {//switch params for 1 arg test = params; } else { - _.merge(global.fivebyConfig, params); //merge test params with global + _.merge(config, params); //merge test params with config } - //ensure minimal configuration is provided - if (!global.fivebyConfig.browsers) { - return console.warn('No browsers provided, must provide at least one'); - } - - //for each browser in the configuration - Object.keys(global.fivebyConfig.browsers).forEach(function (elem) { - - //check if specific browser is valid in selenium - if (!webdriver.Capabilities[elem]) { - return console.warn('No such browser: %s', elem); - } - - var lastPromise = global.testPromise; - var testComplete = webdriver.promise.defer(); - global.testPromise = testComplete.promise; - - //create a control flow and driver per test file - lastPromise.then(function() { - - // set options for current browser - var capabilities = webdriver.Capabilities[elem](); - - if (elem === 'chrome') { - capabilities.set('chromeOptions', { - args: ['--disable-extensions'] - }); - } - - //build driver - var driver = new webdriver.Builder() - .usingServer(global.fivebyConfig.hubUrl) - .withCapabilities(capabilities) - .build(); - driver.name = elem; - driver.manage().timeouts().implicitlyWait(global.fivebyConfig.implicitWait); - - //register tests with mocha - var describe = test(driver); - - //register hooks with mocha - registerHook('fiveby error handling', describe, "beforeEach", function () { - this.currentTest.parent.file = this.currentTest.file = file; - webdriver.promise.controlFlow().on('uncaughtException', function (e) { - if(this.currentTest) { - this.currentTest.callback(e); - } else { - console.error("Failed in setup or teardown, test result may not be valid for this file"); - throw(e); - } - }); - }); - - registerHook('fiveby cleanup', describe, "afterAll", function () { - testComplete.fulfill(); - if (driver.session_) { //in case the tests already killed the session - return driver.quit(); - } - }); - - }); - }); -} - -function registerHook(name, suite, hookarr, func) { - var hook = new Hook(name, func); - hook.parent = suite; - if (suite && suite.ctx) { - hook.ctx = suite.ctx; - } else { - console.error("Please return test suite (describe) in the fiveby constructor callback."); - process.exit(2); - } - hook.timeout(5000); - if(hookarr.indexOf("before") > -1){ - suite["_" + hookarr].unshift(hook); + if(global.fivebyConfig.disableBrowsers){ + test(); } else { - suite["_" + hookarr].push(hook); + var fb = new fiveby(config); + fb.runSuiteInBrowsers(test); } -} + +}; diff --git a/lib/fiveby.js b/lib/fiveby.js new file mode 100644 index 0000000..c75a020 --- /dev/null +++ b/lib/fiveby.js @@ -0,0 +1,117 @@ +var webdriver = require('selenium-webdriver'); +var Hook = require('mocha').Hook; +var tb = require('traceback'); + +//simplify webdriver usage +global.by = webdriver.By; +global.key = webdriver.Key; +global.promise = webdriver.promise; +global.bot = webdriver.error; + +if(!global.testPromise){ + global.testPromise = webdriver.promise.fulfilled(); +} + +module.exports = fiveby; + +function fiveby(config) { + this.config = config; + //spin up local selenium server if none provided + if (!global.fivebyConfig.hubUrl && !config.hubUrl) { + console.info("No server defined, spinning one up ..."); + SeleniumServer = require('selenium-webdriver/remote').SeleniumServer; + var server = new SeleniumServer('./node_modules/fiveby/selenium-server-standalone-2.44.0.jar', { port: 4444 }); + server.start(); + global.fivebyConfig.hubUrl = config.hubUrl = server.address(); + } +} + +fiveby.prototype.runSuiteInBrowsers = function (test) { + + if(test.length === 0){ //bail if they don't want a browser + return test(); + } + + var self = this; + //ensure minimal configuration is provided + if (!this.config.browsers) { + return console.warn('No browsers provided, must provide at least one'); + } + + var file = tb()[2].path; + + //for each browser in the configuration + Object.keys(this.config.browsers).forEach(function (elem) { + + //check if specific browser is valid in selenium + if (!webdriver.Capabilities[elem]) { + return console.warn('No such browser: %s', elem); + } + + var lastPromise = global.testPromise; + var testComplete = webdriver.promise.defer(); + global.testPromise = testComplete.promise; + + //create a control flow and driver per test file + lastPromise.then(function() { + + // set options for current browser + var capabilities = webdriver.Capabilities[elem](); + + if (elem === 'chrome') { + capabilities.set('chromeOptions', { + args: ['--disable-extensions'] + }); + } + + //build driver + var driver = new webdriver.Builder() + .usingServer(self.config.hubUrl) + .withCapabilities(capabilities) + .build(); + driver.name = elem; + driver.manage().timeouts().implicitlyWait(self.config.implicitWait); + + //register tests with mocha + var describe = test(driver); + + //register hooks with mocha + self.registerHook('fiveby error handling', describe, "beforeEach", function () { + this.currentTest.parent.file = this.currentTest.file = file; + webdriver.promise.controlFlow().on('uncaughtException', function (e) { + if(this.currentTest) { + this.currentTest.callback(e); + } else { + console.error("Failed in setup or teardown, test result may not be valid for this file"); + throw(e); + } + }); + }); + + self.registerHook('fiveby cleanup', describe, "afterAll", function () { + testComplete.fulfill(); + if (driver.session_) { //in case the tests already killed the session + return driver.quit(); + } + }); + + }); + }); +}; + +fiveby.prototype.registerHook = function (name, suite, hookarr, func) { + var hook = new Hook(name, func); + hook.parent = suite; + if (suite && suite.ctx) { + hook.ctx = suite.ctx; + } else { + console.error("Please return test suite (describe) in the fiveby constructor callback."); + return process.exit(2); + } + hook.timeout(5000); + if(hookarr.indexOf("before") > -1){ + suite["_" + hookarr].unshift(hook); + } else { + suite["_" + hookarr].push(hook); + } +}; diff --git a/package.json b/package.json index 6202a62..43c10b3 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "fiveby", - "version": "0.8.0", + "version": "0.9.0", "description": "Package up testing options, levels, apis, and dependencies into one simple lib", "scripts": { - "test": "mocha" + "test": "gulp test" }, "author": "Scott Rahner", "repository": { @@ -11,11 +11,17 @@ "url": "https://github.dowjones.net/institutional/fiveby.git" }, "dependencies": { - "selenium-webdriver": "2.42.0", - "should": "^4.0.4", - "mocha": "^1.21.4", "lodash": "^2.4.1", - "traceback": "^0.3.1", - "tesla.lib.cache": "^0.1.1" + "mocha": "^1.21.4", + "selenium-webdriver": "2.44.0", + "tesla.lib.cache": "^0.1.1", + "traceback": "^0.3.1" + }, + "devDependencies": { + "gulp": "^3.8.9", + "gulp-istanbul": "^0.3.1", + "gulp-mocha": "^1.1.1", + "should": "^4.0.4", + "proxyquire": "^1.0.1" } } diff --git a/releasenotes.md b/releasenotes.md index 44f03c5..6b0a43d 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -1,4 +1,13 @@ -Version 0.8.0 +Version 0.9.0 - *Dacosta'd* +----------------- + +- fiveby now has a disableBrowsers option that will disable selenium completely and remove the need for browser config. This is for folks that have no need of browsers but love fiveby +- the presence of the browser argument in the fiveby callback will determine if a browser is spawn regardless of the disableBrowsers flag. This feature is for those that want to mix browser and non-browser testing in the same project +- lots more unit tests and CI goodness +- index split into index and lib/fiveby +- small bug fixes + +Version 0.8.0 - *Daypartying* ----------------- - fiveby hooks run at the appropriate time @@ -10,33 +19,33 @@ Version 0.8.0 - webdriver.error now accessible as bot - better error handling -Version 0.7.1 +Version 0.7.1 - *Italiano'd* ----------------- - extensions are now disabled for the chrome browser -Version 0.7.0 +Version 0.7.0 - *Nemtsov'd* ----------------- - introduction of [properties](/docs/properties.md) -Version 0.6.5 +Version 0.6.5 - *Gruber'd* ----------------- - compensating for mocha bug with filenames -Version 0.6.4 +Version 0.6.4 - *Derp* ----------------- - bug fixes - improved docs -Version 0.6.2/0.6.3 +Version 0.6.2/0.6.3 - *Blunders* ----------------- - slightly improved error handling, working on it -Version 0.6.1 +Version 0.6.1 - *Threads* ----------------- - reluctantly giving up on single process parallel testing, mocha and selenium only play nice if the drivers are created within the tests. Will take a stab in future again.. or try Jasmine, or multiprocess @@ -46,7 +55,7 @@ Version 0.6.1 - bug fixes -Version 0.6.0 +Version 0.6.0 - *Flip-Flop* ----------------- - added ability to use any selenium supported browser @@ -59,7 +68,7 @@ Version 0.6.0 - better logging / test output - java / selenium jar no longer default. Easier to just setup chrome/phantom/ie driver stand alone. If ff is really required use hubUrl to point to local server. -Version 0.5.0 +Version 0.5.0 - *Crucible* ----------------- - first draft diff --git a/test/fiveby.js b/test/fiveby.js new file mode 100644 index 0000000..082b48d --- /dev/null +++ b/test/fiveby.js @@ -0,0 +1,198 @@ +var proxyquire = require('proxyquire').noPreserveCache(); + +var fsStub = { + + readFileSync : function(){ + return '{"alpha": "omega", "disableBrowsers": true}'; + } + +}; + +describe('fiveby config', function(){ + var fb; + + beforeEach(function(){ + global.fivebyConfig = null; + delete process.env.fivebyopts; + }); + + it("global config present", function(){ + global.fivebyConfig = {"browsers":{}, "hubUrl":"garbage"}; + var fb = proxyquire('../index', { 'fs': fsStub }); + var callCount = 0; + fb({}, function(browser){ + callCount++; + }); + callCount.should.equal(0); + }); + + it("browsers enabled", function(){ + process.env.fivebyopts = '{"browsers":{}, "hubUrl":"garbage"}'; + var fb = proxyquire('../index', { 'fs': fsStub }); + var callCount = 0; + fb({}, function(browser){ + callCount++; + }); + callCount.should.equal(0); + }); + + it('error from json', function(done){ + process.env.fivebyopts = "//}}}"; + console.error = function(){}; + process.exit = function(code){ + code.should.equal(1); + done(); + }; + proxyquire('../index', { 'fs': fsStub }); + }); + + it('configuration set by file', function(){ + proxyquire('../index', { 'fs': fsStub }); + global.fivebyConfig.alpha.should.equal("omega"); + }); + + it("constructor argument variations", function(){ + var fb = proxyquire('../index', { 'fs': fsStub }); + var callCount = 0; + fb({}, function(browser){ + callCount++; + }); + fb(function(){ + callCount++; + }); + callCount.should.equal(2); + }); +}); + +describe('fiveby hooks', function(){ + var fb; + + before(function(){ + var fiveby = require('../lib/fiveby'); + fb = new fiveby({hubUrl: true}); + }); + + it('before', function(){ + var arr = ['mark']; + fb.registerHook('derper', {ctx:'yup', "_before": arr}, 'before', function(){}); + arr.length.should.equal(2); + arr[1].should.equal('mark'); + }); + + it('after', function(){ + var arr = ['mark']; + fb.registerHook('derper', {ctx:'yup', "_after": arr}, 'after', function(){}); + arr.length.should.equal(2); + arr[0].should.equal('mark'); + }); + + it('no describe', function(done){ + process.exit = function(code){ + code.should.equal(2); + done(); + }; + fb.registerHook('derper', {"_after": []}, 'after', function(){}); + + }); + +}); + +describe('fiveby local server', function(){ + it('works', function(){ + var count = 0; + console.info = function(){}; + var webDriverStub = { + + SeleniumServer: function() { + return { + start: function(){count++;}, + address: function(){} + }; + } + + }; + var fiveby = proxyquire('../lib/fiveby', { 'selenium-webdriver/remote': webDriverStub}); + fb = new fiveby({browsers:{}}); + count.should.equal(1); + }); +}); + +describe('runSuiteInBrowsers', function(){ + + var webDriverStub = { + + SeleniumServer: function() { + return { + start: function(){}, + address: function(){return "nowhere";} + }; + } + + }; + + it('bad browser name', function(done){ + var fiveby = proxyquire('../lib/fiveby', {'selenium-webdriver/remote': webDriverStub}); + var fb = new fiveby({browsers:{shmul:1}}); + console.warn = function(msg, browser){ + msg.should.equal("No such browser: %s"); + browser.should.equal("shmul"); + done(); + }; + fb.runSuiteInBrowsers(function(browser){}); + }); + + it('no browsers provided', function(done){ + var fiveby = proxyquire('../lib/fiveby', {'selenium-webdriver/remote': webDriverStub}); + var fb = new fiveby({}); + console.warn = function(msg){ + msg.should.equal("No browsers provided, must provide at least one"); + done(); + }; + fb.runSuiteInBrowsers(function(browser){}); + }); + + it('browser 0 arg bail', function(){ + var fiveby = proxyquire('../lib/fiveby', {'selenium-webdriver/remote': webDriverStub}); + var fb = new fiveby({browsers:{chrome:1}}); + fb.runSuiteInBrowsers(function(){}); + }); + + it('exercise', function(){ + var fiveby = proxyquire('../lib/fiveby', { 'selenium-webdriver/remote': webDriverStub, 'selenium-webdriver': { + Builder: function(){ + return { + usingServer: function(){ + return { + withCapabilities: function(){ + return { + build: function(){ + return { + manage: function(){ + return { + timeouts: function(){ + return { + implicitlyWait: function(){ + return { + + }; + } + }; + } + }; + } + }; + } + }; + } + }; + } + }; + } + }}); + var fb = new fiveby({browsers:{chrome:1, ie: 1}}); + fb.registerHook = function(name, suite, hookarr, func){ + func.apply({currentTest:{parent:{}}}); + }; + fb.runSuiteInBrowsers(function(browser){}); + }); +}); diff --git a/test/prep.js b/test/properties.js similarity index 82% rename from test/prep.js rename to test/properties.js index 973f829..a3a407d 100644 --- a/test/prep.js +++ b/test/properties.js @@ -1,6 +1,7 @@ +var proxyquire = require('proxyquire').noPreserveCache(); var should = require('should'); -describe('Fiveby Tests', function(){ +describe('fiveby utils', function(){ before(function(){ var config = { @@ -10,7 +11,6 @@ describe('Fiveby Tests', function(){ chrome: 1 }, environment: "integration", - quiet: true, properties: { user: { "local,development": "frank", @@ -19,11 +19,12 @@ describe('Fiveby Tests', function(){ } } }; + global.fivebyConfig = null; process.env.fivebyopts = JSON.stringify(config); var fb = require('../index.js'); }); - - it('environment specific properties', function(){ + + it('environment specific', function(){ var props = propertyService.getProperties('default'); 'sue'.should.equal(props.get('user')); }); @@ -34,4 +35,5 @@ describe('Fiveby Tests', function(){ 'derper'.should.equal(props.get('user')); }); + });