diff --git a/README.md b/README.md index fed5ce5..6994c44 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ [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: +All the things you expect from a robust testing framework by neatly packaging: [WebDriverJS](https://code.google.com/p/selenium/wiki/WebDriverJs), [mocha](http://mochajs.org/), and [should](https://github.com/shouldjs/should.js) with a little glue and zero magic: + ```javascript var fiveby = require('fiveby'); -new fiveby(function (browser) { //browser is driver if you are looking at selenium docs +fiveby(function (browser) { return describe('Google Search in ' + browser.name, function () { it('should work', function () { browser.get('http://www.google.com'); - var searchBox = browser.findElement(by.name('q')); //notice webdriver.By convenience method + var searchBox = browser.findElement(by.name('q')); searchBox.sendKeys('awesome'); return searchBox.getAttribute('value').then(function (value) { 'awesome'.should.equal(value); @@ -18,7 +19,14 @@ 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/) +Add [gulp](http://gulpjs.com/) and some convention to make it even more powerful: [slush-fiveby](https://github.com/dowjones/slush-fiveby) + +###What's unique about fiveby? + +- Cleanly allows mocha and webdriverjs to coexist +- MUCH simpler configuration and less boilerplate code +- [environment properties](/docs/properties.md) +- conveniences: api cleanup, spins up a selenium server if not provided, closes the browser for you, etc ... ###Configuration - fiveby-config.json @@ -27,11 +35,30 @@ See [docs](https://github.dowjones.net/institutional/fiveby/docs) for more detai "implicitWait": 5000, "hubUrl": null, "browsers": { - "chrome": 1 + "firefox": true, + "chrome": { + "version": "37.0.2062.103", + "chromeOptions": { + "args": ["--disable-extensions"] + } + } }, "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* +disableBrowsers and hubUrl are optional, disableBrowsers defaults to false + +###English? + +#####Have little to no experience with end to end testing? + +Ok, this tool will allow you to write a bit of javascript that will open any browser (even mobile), emulate user behavior via a few simple commands, and then verify what's displayed onscreen is correct. You can compile large suites of these tests and easily run them against many different browsers at once and get nice reports. It can be run with something like [jenkins](http://jenkins-ci.org/) to automate further. + +###Pre-reqs + +- [node.js](http://nodejs.org/) +- [mocha cli](http://mochajs.org/) +- [java](https://www.java.com/en/download/help/download_options.xml) + +See [docs folder](/docs) for even more details! diff --git a/docs/README.md b/docs/README.md index b816414..7140f93 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,8 @@ ####1. Introduction - Automated browser testing is writing code to simulation a user: click element x, wait for event y, click again, assert results, repeat. + Automated browser testing is writing code to simulate a user: click element x, wait for event y, click again, assert results, repeat. -####2. Project Structure - the important part to note here is the distinction between tests and components. Components abstract the dom details from the tests using the [page objects pattern](pop.md). You can see some real projects [here](https://github.dowjones.net/factivaautomation). Keep in mind other projects are immature as well and probably DO NOT follow many of the best practices discussed here... and suffered for it! +####2. Project Structure - the important part to note here is the distinction between tests and components. Components abstract the dom details from the tests using the [page objects pattern](pop.md). Services are where you perform actions that have nothing to do with the browser, like rest calls used by setup/teardown. ``` └── tests @@ -21,6 +21,9 @@ │    └── smoke │     └── ... │ + └── services + │ └── LoginService.js + │ └── components    ├── BaseComponent.js    ├── CommonConfig.js @@ -45,13 +48,13 @@ ####5. [Code Style & API](api.md) -####6.[External Dependencies](/docs/external-dependencies.md) +####6.[External Dependencies](external-dependencies.md) ####7. Based on: *Selenium Javascript* api: -> http://selenium.googlecode.com/git/docs/api/javascript/index.html +> http://selenium.googlecode.com/git/docs/api/javascript/module_selenium-webdriver.html > (webdriver.By and webdriver.promise surfaced as globals for convience) *Mocha BDD* api: @@ -63,9 +66,4 @@ > https://github.com/shouldjs/should.js - -####8. Ecosystem - -![Ecosystem](ecosystem.png) - -####9. [FAQ](/docs/faq.md) +####8. [FAQ](/docs/faq.md) diff --git a/docs/api.md b/docs/api.md index ebc6168..0ad8cb3 100644 --- a/docs/api.md +++ b/docs/api.md @@ -13,7 +13,7 @@ something.sendKeys('else'); **Find multiple elements** -[Working example leveraging the page objects pattern](https://github.dowjones.net/institutional/gulp-fiveby/blob/master/tests/smoke/bootstrap.js) +[Working example leveraging the page objects pattern](https://github.com/dowjones/slush-fiveby/blob/master/tests/smoke/bootstrap.js) **Complex mouses operations** using action chains: diff --git a/docs/clean-promises.md b/docs/clean-promises.md index 115cf30..05f9c0a 100644 --- a/docs/clean-promises.md +++ b/docs/clean-promises.md @@ -3,7 +3,7 @@ There are many cases where you need to wait for multiple promises before doing something. Sometimes you need to run them in series, sometimes in parallel, sometimes you need all the results at the end, and sometimes you need each function to know about the results of the previous function. -Following are all the examples we could think of =D +Following are some of the examples we could think of =D *** ####Sequence (Waterfall) output: diff --git a/docs/ecosystem.png b/docs/ecosystem.png deleted file mode 100644 index be8b62a..0000000 Binary files a/docs/ecosystem.png and /dev/null differ diff --git a/docs/external-dependencies.md b/docs/external-dependencies.md index 22fd8e0..de16aef 100644 --- a/docs/external-dependencies.md +++ b/docs/external-dependencies.md @@ -1,5 +1,5 @@ #### Things to consider about your application before testing: -1. **Performance.** If your application's performance is inconsistent, your tests will be brittle or long running. You will either set short timeouts and fail often, or set long timeouts and take forever. This is not a problem with Automated Testing. If your experience is that inconsistent, it's going to be a problem with your customers! +1. **Performance.** If your application's performance is inconsistent, your tests will be brittle or long running. You will either set short timeouts and fail often, or set long timeouts and take forever. This is not a problem with Automated Testing. If your experience is that inconsistent, it's going to be a problem with your users! 2. **Setup / Teardown.** You should be able to programmatically set up and tear down everything related to a test. You should have APIs to create and delete users as well as populate and delete user data in bulk. These will make tests faster and more stable. They will also be more reliable as the state is pristine and therefore 100% known. -3. **ARM limits!** Most institutional transactions have limits to prevent a single user from damaging the experience of others by hogging the system resources. Your testing WILL trigger these limits. Plan accordingly. +3. **Resource limits!** Like all things automated, make sure you are not going to strain the system you are testing (or lock out a user, engage rate limiting, etc). No use testing if you bring the system down by overloading it :grin: diff --git a/docs/faq.md b/docs/faq.md index 83a2698..7008d98 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,6 +1,6 @@ -**I know what promises are but mine look terrible, is there a better way?** +**I know what promises are ... but mine look terrible, is there a better way?** -1. [Our Suggestions](/docs/clean-promises.md) +1. [Our Suggestions](clean-promises.md) 2. Good practices for promises http://solutionoptimist.com/2013/12/27/javascript-promise-chains-2/ **What is implicit wait and do my tests use it?** @@ -9,7 +9,7 @@ **Can I change the timeout on individual Mocha tests?** - Test timeout is 30 seconds, this is per "it". This can be edited via gulpfile.js + Test timeout is 30 seconds, this is per "it". This can be edited via mocha cli or something like gulpfile.js **How can I check on the state of things while my tests run?** @@ -20,20 +20,20 @@ helpful to debug selectors (making sure you have what you think you have): console.info(html); }); ``` - -if you want to debug the actual code just run + +if you want to debug the actual code just run mocha debug *yourfile* //runs the cli debugger or mocha --debug *yourfile //runs the debugger on a port for attach -**Can I run or exclude specific tests?** +**Can I run or exclude specific tests?** describe.only it.only describe.skip it.skip for excluding and including tests, see http://visionmedia.github.io/mocha/ - -**Help, I keep getting NoSuchElementError** - + +**Help, I keep getting NoSuchElementError** + Make selectors very specific to avoid too many elements or NoSuchElementError (also use isElementPresent, isDisplayed). You should not run into NoSuchElement errors in the course of testing, only look up things you expect to find. You rarely perform negative testing in this space and if you do you look for elements (like a modal) that are telling the user they failed or are unauthorized. -**My code is always changing how do I maintain multiple suite to match different versions of code:** +**My code is always changing how do I maintain multiple suites to match different versions of code:** - Branch for versions of tests that only will run against specific versions of code. Fiveby will be enhanced shortly to include environment variables (much like tesla property service) + scm branching for versions of tests that only will run against specific versions of code. use [enviroment properties](properties.md) to handle things like different urls and timeouts in various environements. diff --git a/gulpfile.js b/gulpfile.js index dd2f0a6..f696f3f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -8,7 +8,9 @@ gulp.task('test', function (cb) { .on('finish', function () { gulp.src(['test/*.js']) .pipe(mocha()) - .pipe(istanbul.writeReports()) + .pipe(istanbul.writeReports({ + reporters: [ 'text', 'lcov', 'cobertura' ] + })) .on('end', cb); }); }); diff --git a/lib/cache.js b/lib/cache.js deleted file mode 100644 index b93c8cc..0000000 --- a/lib/cache.js +++ /dev/null @@ -1,18 +0,0 @@ -var Cache = require('tesla.lib.cache').RamLru; - -/** - * Expose service. - */ - -module.exports = CacheService; - -function CacheService() { - this._caches = new Cache(); -} - -CacheService.prototype.getCache = function (name, options) { - function store() { - return new Cache(options); - } - return this._caches.readThrough(name, store); -}; diff --git a/lib/fiveby.js b/lib/fiveby.js index c75a020..4b13193 100644 --- a/lib/fiveby.js +++ b/lib/fiveby.js @@ -52,15 +52,17 @@ fiveby.prototype.runSuiteInBrowsers = function (test) { var testComplete = webdriver.promise.defer(); global.testPromise = testComplete.promise; - //create a control flow and driver per test file + //create a 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'] + var cap = self.config.browsers[elem]; + + if (typeof cap === 'object' && cap !== null) { + Object.keys(cap).forEach(function (n) { + capabilities.set(n, cap[n]); }); } @@ -77,12 +79,12 @@ fiveby.prototype.runSuiteInBrowsers = function (test) { //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) { + this.currentTest.parent.file = this.currentTest.file = file; //correct file name bug with async execution and mocha + webdriver.promise.controlFlow().on('uncaughtException', function (e) { //map errors to test when appropriate if(this.currentTest) { this.currentTest.callback(e); } else { - console.error("Failed in setup or teardown, test result may not be valid for this file"); + console.error("Failed in setup or teardown, test result may not be valid for this file"); //failed in non-test throw(e); } }); diff --git a/lib/map.js b/lib/map.js deleted file mode 100644 index 9ecee2b..0000000 --- a/lib/map.js +++ /dev/null @@ -1,262 +0,0 @@ -var util = require('util'), - EventEmitter = require('events').EventEmitter; - -/** - * Expose `Map`. - */ - -exports = module.exports = Map; - -/** - * Map - * - * An an object that maps keys to values. - * A map cannot contain duplicate keys; each key can map to at most one value. - * - * Look at [java.util.Map](http://docs.oracle.com/javase/6/docs/api/java/util/Map.html) - * - * @param {Object} object the keys & values of which will be the keys/values of this map - * - */ - -function Map(object) { - this._map = {}; - this._mapSize = 0; - if (object) { - this._putObject(object); - } -} -util.inherits(Map, EventEmitter); - -/** - * Removes all entries from the map. - * emits the 'clear' event - */ - -Map.prototype.clear = function () { - this._map = {}; - this._mapSize = 0; - this.emit('clear'); -}; - -/** - * Returns true if this map contains a - * mapping for the specified key. - * - * @param {String} key - * @return {Boolean} - */ - -Map.prototype.containsKey = function (key) { - if (!this._isValid(key)) return false; - return !!(this._map[key]); -}; - -/** - * Returns true if this map contains one or more - * keys for the specified value - * - * @param {Object} value - * @return {Boolean} - */ - -Map.prototype.containsValue = function (value) { - if (!this._isValid(value)) return false; - var map = this._map, - hasEquals = ('function' === typeof value.equals); - for (var key in map) { - if (hasOwnProperty.call(map, key)) { - if (hasEquals ? - value.equals(map[key]) : - value === map[key]) { - return true; - } - } - } - return false; -}; - -/** - * Returns an array of the key/value pairs contained in this map. - * - * @return {Array} [{key: String, value: Object}] - */ - -Map.prototype.entrySet = function () { - var map = this._map, - set = []; - for (var key in map) { - if (hasOwnProperty.call(map, key)) { - set.push({ - key: key, - value: map[key] - }); - } - } - return set; -}; - -/** - * Determines whether the keys and values - * of the current map are equal to the one provided. - * - * @param {Map} otherMap to compare - * @return {Boolean} - */ - -Map.prototype.equals = function (otherMap) { - if (!this._isValid(otherMap) || (!(otherMap instanceof Map)) || (otherMap.size() !== this.size())) { - return false; - } - var map = this._map; - for (var key in map) { - if (hasOwnProperty.call(map, key)) { - if (!otherMap.containsKey(key) || !otherMap.containsValue(map[key])) { - return false; - } - } - } - return true; -}; - -/** - * Get the value from the map with the provided key. - * - * @param {String} key - * @return {Object} value or null if one isn't found - */ - -Map.prototype.get = function (key) { - if (!this._isValid(key)) throw new Error('invalid key'); - var value = this._map[key]; - return ('undefined' !== typeof value ? value : null); -}; - -/** - * Put the value from the map with the provided key. - * - * Emits the 'put' event with the key, value as params. - * - * @param {String} key - * @param {Object} value - */ - -Map.prototype.put = function (key, value) { - if (!this._isValid(key)) throw new Error('invalid key'); - this._map[key] = value; - this._mapSize++; - this.emit('put', key, value); -}; - -/** - * Put each key/value of the provided map into the current one. - * - * Emits the 'put' event on every inserted key. - * - * @param {Map} map - */ - -Map.prototype.putAll = function (map) { - if (!this._isValid(map) || !(map instanceof Map)) { - throw new Error('invalid map'); - } - var self = this; - map.keySet().forEach(function (key) { - self.put(key, map.get(key)); - }); -}; - -/** - * Remove a mapping with the provided key. - * - * Emits the 'remove' event with the key, oldValue as params. - * - * @param {String} key - * @return {Object} old value - */ - -Map.prototype.remove = function (key) { - if (!this._isValid(key)) throw new Error('invalid key'); - var oldValue = this._map[key]; - delete this._map[key]; - this._mapSize--; - this.emit('remove', key, oldValue); - return oldValue; -}; - -/** - * Determines whether the map has any mappings. - * - * @return {Boolean} - */ - -Map.prototype.isEmpty = function () { - return (this._mapSize === 0); -}; - -/** - * Get a set of all keys of this map - * - * @return {Array} - */ - -Map.prototype.keySet = function () { - return Object.keys(this._map); -}; - -/** - * @return {Number} size - */ - -Map.prototype.size = function () { - return this._mapSize; -}; - -/** - * Get a set of all values of this map - * - * @return {Array} - */ - -Map.prototype.values = function () { - var map = this._map - , values = []; - for (var key in map) { - if (hasOwnProperty.call(map, key)) { - values.push(map[key]); - } - } - return values; -}; - -/** - * Object is considered valid if - * it is defined and not null - * - * @param {Object} obj - * @return {Boolean} - * @api private - */ - -Map.prototype._isValid = function (obj) { - return (('undefined' !== typeof obj) && (null !== obj)); -}; - -/** - * Add all keys / values of the provided object - * into the current map. - * - * @param {Object} obj the keys & values of \ - * which will be the keys/values of this map - * @api private - */ - -Map.prototype._putObject = function (obj) { - if ('object' !== typeof obj) throw new Error('invalid object'); - for (var key in obj) { - if (hasOwnProperty.call(obj, key)) { - this._map[key] = obj[key]; - this._mapSize++; - } - } -}; diff --git a/lib/properties.js b/lib/properties.js index a44c33c..1f1eeda 100644 --- a/lib/properties.js +++ b/lib/properties.js @@ -1,9 +1,4 @@ -var MapUtil = require('./map'); -var Cache = require('./cache'); - -/** - * Expose service - */ +var Cache = require('lru-cache'); module.exports = PropertyService; @@ -43,8 +38,7 @@ module.exports = PropertyService; function PropertyService(environment) { this._env2rank = this._calculateRanks(environment); this._coreKey2rank = {}; - var cacheSvc = new Cache(); - this._cache = cacheSvc.getCache('core.PropertyService'); + this._cache = Cache({ max: 10000, maxAge: Infinity }); } PropertyService.prototype.getProperties = function (namespace) { @@ -81,7 +75,7 @@ Properties.prototype.set = function (env, key, value) { if (rank < highest) return; this._key2rank[propKey] = rank; - this._cache.put(propKey, value); + this._cache.set(propKey, value); }; /** diff --git a/package.json b/package.json index 1a3b5c9..086b2f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fiveby", - "version": "0.9.1", + "version": "0.9.2", "description": "Package up testing options, levels, apis, and dependencies into one simple lib", "scripts": { "test": "gulp test" @@ -12,16 +12,16 @@ }, "dependencies": { "lodash": "^2.4.1", + "lru-cache": "^2.5.0", "mocha": "^1.21.4", "selenium-webdriver": "2.44.0", - "tesla.lib.cache": "^0.1.1", - "traceback": "^0.3.1" + "traceback": "^0.3.1", + "should": "^4.0.4" }, "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 9648aa5..191922f 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -1,3 +1,10 @@ +Version 0.9.2 - *Pending* +----------------- + +- removed dependency on tesla.lib.cache +- major doc overhaul +- updated versions + Version 0.9.1 - *Staedt'd* ----------------- @@ -10,6 +17,7 @@ Version 0.9.0 - *Dacosta'd* - 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 +- upgraded to selenium 2.44.0 - small bug fixes Version 0.8.0 - *Daypartying* diff --git a/test/fiveby.js b/test/fiveby.js index 5b27e26..8e26fc1 100644 --- a/test/fiveby.js +++ b/test/fiveby.js @@ -188,7 +188,7 @@ describe('runSuiteInBrowsers', function(){ }; } }}); - var fb = new fiveby({browsers:{chrome:1, ie: 1}}); + var fb = new fiveby({browsers:{chrome:{"chromeOptions": {"args": ["--disable-extensions"]}}, ie: 1}}); fb.registerHook = function(name, suite, hookarr, func){ func.apply({currentTest:{parent:{}}}); };